You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by Stefan Reich <do...@drjava.de> on 2001/07/21 19:25:43 UTC

[PATCH] Lazy task class loading

Hi,

currently Ant loads all taskdef classes (as defined in defaults.properties) on startup. This mechanism has at least 3 drawbacks:

-It can take up to one second or longer (if you use ant for everyday development, you will notice the difference)
-It wastes memory
-It completely lacks descriptive error messages. For example, if you use <junit>, but junit.jar isn't in your classpath, you only get a default message telling you something about optional.jar. I've been puzzled as to why tasks couldn't be loaded more than once.

I would like to submit a patch that solves all three problems. I also took care to ensure that the public interface of org.apache.tools.ant.Project is backward compatible.

-Stefan

Here is the patch against recent CVS (I already added @author tags for your convenience ;-):

Index: main/org/apache/tools/ant/Project.java
===================================================================
RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/Project.java,v
retrieving revision 1.64
diff -u -r1.64 Project.java
--- main/org/apache/tools/ant/Project.java 2001/07/17 12:12:39 1.64
+++ main/org/apache/tools/ant/Project.java 2001/07/21 17:03:14
@@ -68,6 +68,7 @@
  * file paths at runtime as well as defining various project properties.
  *
  * @author duncan@x180.com
+ * @author Stefan Reich <a href="mailto:doc@drjava.de">doc@drjava.de</a>
  */
 
 public class Project {
@@ -101,7 +102,8 @@
     private Hashtable references = new Hashtable();
     private String defaultTarget;
     private Hashtable dataClassDefinitions = new Hashtable();
-    private Hashtable taskClassDefinitions = new Hashtable();
+    private TaskDefinitionHashtable taskClassDefinitions = new TaskDefinitionHashtable();
+    
     private Hashtable targets = new Hashtable();
     private Hashtable filters = new Hashtable();
     private File baseDir;
@@ -114,6 +116,27 @@
     /** The system classloader - may be null */    
     private ClassLoader systemLoader = null;
     
+    public static class TaskDefinitionHashtable extends Hashtable {
+      /** lazily call Class.forName */
+      public Object get(Object key) {
+        Object value = super.get(key);
+        if (value instanceof String) {
+          try {
+            value = Class.forName((String) value);
+          } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e.toString());
+          } catch (NoClassDefFoundError e) {
+            throw new RuntimeException(e.toString());
+          }
+        }
+        return value;
+      }
+      
+      public String getClassName(Object key) {
+        return (String) super.get(key);
+      }
+    }
+    
     static {
 
         // Determine the Java version by looking at available classes
@@ -161,19 +184,23 @@
             props.load(in);
             in.close();
 
+            long startTime = System.currentTimeMillis();
             Enumeration enum = props.propertyNames();
             while (enum.hasMoreElements()) {
                 String key = (String) enum.nextElement();
                 String value = props.getProperty(key);
-                try {
+                /*try {
                     Class taskClass = Class.forName(value);
                     addTaskDefinition(key, taskClass);
                 } catch (NoClassDefFoundError ncdfe) {
                     // ignore...
                 } catch (ClassNotFoundException cnfe) {
                     // ignore...
-                }
+                }*/
+                addTaskDefinition(key, value);
             }
+            long endTime = System.currentTimeMillis();
+            System.out.println("Class loading took "+(endTime-startTime)+" ms");
         } catch (IOException ioe) {
             throw new BuildException("Can't load default task list");
         }
@@ -367,13 +394,19 @@
         log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE);
     }
 
+    public void addTaskDefinition(String taskName, String taskClass) {
+        String msg = " +User task: " + taskName + "     " + taskClass;
+        log(msg, MSG_DEBUG);
+        taskClassDefinitions.put(taskName, taskClass);
+    }
+
     public void addTaskDefinition(String taskName, Class taskClass) {
         String msg = " +User task: " + taskName + "     " + taskClass.getName();
         log(msg, MSG_DEBUG);
         taskClassDefinitions.put(taskName, taskClass);
     }
 
-    public Hashtable getTaskDefinitions() {
+    public TaskDefinitionHashtable getTaskDefinitions() {
         return taskClassDefinitions;
     }
 
@@ -445,7 +478,12 @@
     }
 
     public Task createTask(String taskType) throws BuildException {
-        Class c = (Class) taskClassDefinitions.get(taskType);
+        Class c;
+        try {
+          c = (Class) taskClassDefinitions.get(taskType);
+        } catch (RuntimeException e) {
+          throw new BuildException(e.getMessage());
+        }
 
         if (c == null)
             return null;
Index: main/org/apache/tools/ant/taskdefs/Ant.java
===================================================================
RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Ant.java,v
retrieving revision 1.23
diff -u -r1.23 Ant.java
--- main/org/apache/tools/ant/taskdefs/Ant.java 2001/07/06 11:57:29 1.23
+++ main/org/apache/tools/ant/taskdefs/Ant.java 2001/07/21 17:03:15
@@ -76,6 +76,7 @@
  *
  *
  * @author costin@dnt.ro
+ * @author Stefan Reich <a href="mailto:doc@drjava.de">doc@drjava.de</a>
  */
 public class Ant extends Task {
 
@@ -143,11 +144,11 @@
             }
         }
 
-        Hashtable taskdefs = project.getTaskDefinitions();
+        Project.TaskDefinitionHashtable taskdefs = project.getTaskDefinitions();
         Enumeration et = taskdefs.keys();
         while (et.hasMoreElements()) {
             String taskName = (String) et.nextElement();
-            Class taskClass = (Class) taskdefs.get(taskName);
+            String taskClass = taskdefs.getClassName(taskName);
             p1.addTaskDefinition(taskName, taskClass);
         }



Re: [PATCH] Lazy task class loading

Posted by Stefan Bodewig <bo...@apache.org>.
Guten Morgen,

On Tue, 7 Aug 2001, Stefan Reich <do...@drjava.de> wrote:

> I have to admit I'm not entirely firm with how Ant uses
> classloaders. Will the subbuild always have a different classloader?
> Won't it have the outer classloader as a parent and automatically
> inherit all classes loaded by the parent?

Tasks defined by <taskdef> will be loaded by a different classloader
than the one used to load plain Ant tasks - and after your changes the
subbuild won't know about these other classloaders.  The subbuild will
have the same "main" classloader as plain Ant.

> Either way, it seems to me this problem will be solved if I just
> pass the contents of the TaskDefinitionHashtable as-is.

Yep.

>> And then you want to apply the same logic to data types as well 8-)
> 
> Data types...?

FileSet, PatternSet, Path, Mapper and more recent FilterSet and
FileList.  See dataClassDefinitions, addDataTypeDefinition,
getDataTypeDefinitions and createDataType in Project.

Since Ant 1.4 will have a <typedef> to accompany <taskdef>
it has the same classloader issues as tasks.

Finally, the <antstructure> task will have to be rewritten to make
sure the classes have actually been loaded, no big deal.

> 
>> Stefan
> 
> -Stefan (hehe, if this conversation goes on people will be really
> confused about who said what)

Stefan ;-)

Re: [PATCH] Lazy task class loading

Posted by Stefan Reich <do...@drjava.de>.
Stefan,

> OK, I'll admit that your reminder triggers this response (I shouldn't
> be sitting here on front of the screen anyway, but that's a different
> story).

No problem, sometimes we all need two or more kicks until we do something
*g*

> I like the changes to Project, but the way you've implemented the Ant
> task, it will only pass on the class names of already loaded tasks,
> not the actual classes - this not only means you'll have to load them
> once again in the subbuild if you need the same task, but also that
> any custom classloader a user may have specified in <taskdef> will be
> lost.
>
> As a result, tasks that used to work in the main build may throw a
> ClassNotFoundException in the subbuild - or even worse be a different
> implementation of the same class.

I have to admit I'm not entirely firm with how Ant uses classloaders. Will
the subbuild always have a different classloader? Won't it have the outer
classloader as a parent and automatically inherit all classes loaded by the
parent?

Either way, it seems to me this problem will be solved if I just pass the
contents of the TaskDefinitionHashtable as-is. Strings as Strings, Classes
as Classes.

If I'm wrong here, please stop me - otherwise, I'll make that change and
submit a new patch.

> And then you want to apply the same logic to data types as well 8-)

Data types...?

> Stefan

-Stefan (hehe, if this conversation goes on people will be really confused
about who said what)


Re: [PATCH] Lazy task class loading

Posted by Stefan Bodewig <bo...@apache.org>.
OK, I'll admit that your reminder triggers this response (I shouldn't
be sitting here on front of the screen anyway, but that's a different
story).

I like the changes to Project, but the way you've implemented the Ant
task, it will only pass on the class names of already loaded tasks,
not the actual classes - this not only means you'll have to load them
once again in the subbuild if you need the same task, but also that
any custom classloader a user may have specified in <taskdef> will be
lost.

As a result, tasks that used to work in the main build may throw a
ClassNotFoundException in the subbuild - or even worse be a different
implementation of the same class.

And then you want to apply the same logic to data types as well 8-)

Stefan