From 5cdfabc038e26f67080037ea0c7ad6ab284d821c Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Tue, 1 Apr 2003 11:35:06 +0000 Subject: [PATCH] Sync with main code line git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@274356 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/tools/ant/IntrospectionHelper.java | 629 +++++++++++++----- .../main/org/apache/tools/ant/Project.java | 440 +++++++++++- 2 files changed, 857 insertions(+), 212 deletions(-) diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java index 4d0f3ee94..1d1e0f7f9 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -1,7 +1,7 @@ /* * The Apache Software License, Version 1.1 * - * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,7 +23,7 @@ * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * - * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * 4. The names "Ant" and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. @@ -54,16 +54,15 @@ package org.apache.tools.ant; -import org.apache.tools.ant.types.Path; -import org.apache.tools.ant.types.EnumeratedAttribute; - -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Constructor; import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; /** * Helper class that collects the methods a task or nested element @@ -75,45 +74,125 @@ import java.util.Locale; public class IntrospectionHelper implements BuildListener { /** - * holds the types of the attributes that could be set. + * Map from attribute names to attribute types + * (String to Class). */ private Hashtable attributeTypes; /** - * holds the attribute setter methods. + * Map from attribute names to attribute setter methods + * (String to AttributeSetter). */ private Hashtable attributeSetters; /** - * Holds the types of nested elements that could be created. + * Map from attribute names to nested types + * (String to Class). */ private Hashtable nestedTypes; /** - * Holds methods to create nested elements. + * Map from attribute names to methods to create nested types + * (String to NestedCreator). */ private Hashtable nestedCreators; /** - * Holds methods to store configured nested elements. + * Map from attribute names to methods to store configured nested types + * (String to NestedStorer). */ private Hashtable nestedStorers; /** - * The method to add PCDATA stuff. + * The method to invoke to add PCDATA. */ private Method addText = null; /** - * The Class that's been introspected. + * The class introspected by this instance. */ private Class bean; /** - * instances we've already created + * Helper instances we've already created (Class to IntrospectionHelper). */ private static Hashtable helpers = new Hashtable(); + /** + * Map from primitive types to wrapper classes for use in + * createAttributeSetter (Class to Class). Note that char + * and boolean are in here even though they get special treatment + * - this way we only need to test for the wrapper class. + */ + private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable(8); + + // Set up PRIMITIVE_TYPE_MAP + static { + Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, + Short.TYPE, Integer.TYPE, Long.TYPE, + Float.TYPE, Double.TYPE}; + Class[] wrappers = {Boolean.class, Byte.class, Character.class, + Short.class, Integer.class, Long.class, + Float.class, Double.class}; + for (int i = 0; i < primitives.length; i++) { + PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]); + } + } + + // XXX: (Jon Skeet) The documentation below doesn't draw a clear + // distinction between addConfigured and add. It's obvious what the + // code *here* does (addConfigured sets both a creator method which + // calls a no-arg constructor and a storer method which calls the + // method we're looking at, whlie add just sets a creator method + // which calls the method we're looking at) but it's not at all + // obvious what the difference in actual *effect* will be later + // on. I can't see any mention of addConfiguredXXX in "Developing + // with Ant" (at least in the version on the web site). Someone + // who understands should update this documentation + // (and preferably the manual too) at some stage. + /** + * Sole constructor, which is private to ensure that all + * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}. + * Introspects the given class for bean-like methods. + * Each method is examined in turn, and the following rules are applied: + *

+ *

+ * Note that only one method is retained to create/set/addConfigured/add + * any element or attribute. + * + * @param bean The bean type to introspect. + * Must not be null. + * + * @see #getHelper(Class) + */ private IntrospectionHelper(final Class bean) { attributeTypes = new Hashtable(); attributeSetters = new Hashtable(); @@ -124,7 +203,7 @@ public class IntrospectionHelper implements BuildListener { this.bean = bean; Method[] methods = bean.getMethods(); - for (int i=0; inull. + * + * @return a helper for the specified class */ public static synchronized IntrospectionHelper getHelper(Class c) { IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c); @@ -292,17 +386,63 @@ public class IntrospectionHelper implements BuildListener { } /** - * Sets the named attribute. + * Returns a helper for the given class, either from the cache + * or by creating a new instance. + * + * The method will make sure the helper will be cleaned up at the end of + * the project, and only one instance will be created for each class. + * + * @param c The class for which a helper is required. + * Must not be null. + * + * @return a helper for the specified class + */ + public static synchronized IntrospectionHelper getHelper(Project p, Class c) + { + IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c); + if (ih == null) { + ih = new IntrospectionHelper(c); + helpers.put(c, ih); + // Cleanup at end of project + p.addBuildListener(ih); + } + return ih; + } + + /** + * Sets the named attribute in the given element, which is part of the + * given project. + * + * @param p The project containing the element. This is used when files + * need to be resolved. Must not be null. + * @param element The element to set the attribute in. Must not be + * null. + * @param attributeName The name of the attribute to set. Must not be + * null. + * @param value The value to set the attribute to. This may be interpreted + * or converted to the necessary type if the setter method + * doesn't just take a string. Must not be null. + * + * @exception BuildException if the introspected class doesn't support + * the given attribute, or if the setting + * method fails. */ public void setAttribute(Project p, Object element, String attributeName, - String value) - throws BuildException { - AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName); + String value) throws BuildException { + AttributeSetter as + = (AttributeSetter) attributeSetters.get(attributeName); if (as == null) { - String msg = getElementName(p, element) + - //String msg = "Class " + element.getClass().getName() + - " doesn't support the \"" + attributeName + "\" attribute."; - throw new BuildException(msg); + if (element instanceof DynamicConfigurator) { + DynamicConfigurator dc = (DynamicConfigurator) element; + dc.setDynamicAttribute(attributeName, value); + return; + } + else { + String msg = getElementName(p, element) + + " doesn't support the \"" + attributeName + + "\" attribute."; + throw new BuildException(msg); + } } try { as.set(p, element, value); @@ -319,18 +459,32 @@ public class IntrospectionHelper implements BuildListener { } /** - * Adds PCDATA areas. + * Adds PCDATA to an element, using the element's + * void addText(String) method, if it has one. If no + * such method is present, a BuildException is thrown if the + * given text contains non-whitespace. + * + * @param project The project which the element is part of. + * Must not be null. + * @param element The element to add the text to. + * Must not be null. + * @param text The text to add. + * Must not be null. + * + * @exception BuildException if non-whitespace text is provided and no + * method is available to handle it, or if + * the handling method fails. */ - public void addText(Project project, Object element, String text) { + public void addText(Project project, Object element, String text) + throws BuildException { if (addText == null) { // Element doesn't handle text content - if ( text.trim().length() == 0 ) { + if (text.trim().length() == 0) { // Only whitespace - ignore return; - } - else { + } else { // Not whitespace - fail - String msg = getElementName(project, element) + + String msg = project.getElementName(element) + " doesn't support nested text data."; throw new BuildException(msg); } @@ -350,25 +504,46 @@ public class IntrospectionHelper implements BuildListener { } /** - * Creates a named nested element. + * Creates a named nested element. Depending on the results of the + * initial introspection, either a method in the given parent instance + * or a simple no-arg constructor is used to create an instance of the + * specified element type. + * + * @param project Project to which the parent object belongs. + * Must not be null. If the resulting + * object is an instance of ProjectComponent, its + * Project reference is set to this parameter value. + * @param parent Parent object used to create the instance. + * Must not be null. + * @param elementName Name of the element to create an instance of. + * Must not be null. + * + * @return an instance of the specified element type + * + * @exception BuildException if no method is available to create the + * element instance, or if the creating method + * fails. */ - public Object createElement(Project project, Object element, String elementName) - throws BuildException { - + public Object createElement(Project project, Object parent, + String elementName) throws BuildException { + NestedCreator nc = (NestedCreator) nestedCreators.get(elementName); + if (nc == null && parent instanceof DynamicConfigurator) { + DynamicConfigurator dc = (DynamicConfigurator) parent; + Object nestedElement = dc.createDynamicElement(elementName); + if (nestedElement != null) { + if (nestedElement instanceof ProjectComponent) { + ((ProjectComponent) nestedElement).setProject(project); + } + return nestedElement; + } + } + if (nc == null) { + String msg = project.getElementName(parent) + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException(msg); + } try { - // First check if there are any roles supported by this class - Object nestedElement = project.createInRole(element, elementName); - if (nestedElement == null) { - NestedCreator nc = - (NestedCreator) nestedCreators.get(elementName); - if (nc == null) { - String msg = getElementName(project, element) + - " doesn't support the nested \"" + elementName + - "\" element."; - throw new BuildException(msg); - } - nestedElement = nc.create(element); - } + Object nestedElement = nc.create(parent); if (nestedElement instanceof ProjectComponent) { ((ProjectComponent) nestedElement).setProject(project); } @@ -389,19 +564,48 @@ public class IntrospectionHelper implements BuildListener { } /** - * Creates a named nested element. + * Indicate if this element supports a nested element of the + * given name. + * + * @param elementName the name of the nested element being checked + * + * @return true if the given nested element is supported */ - public void storeElement(Project project, Object element, Object child, String elementName) - throws BuildException { + public boolean supportsNestedElement(String elementName) { + return nestedCreators.containsKey(elementName); + } + + /** + * Stores a named nested element using a storage method determined + * by the initial introspection. If no appropriate storage method + * is available, this method returns immediately. + * + * @param project Ignored in this implementation. + * May be null. + * + * @param parent Parent instance to store the child in. + * Must not be null. + * + * @param child Child instance to store in the parent. + * Should not be null. + * + * @param elementName Name of the child element to store. + * May be null, in which case + * this method returns immediately. + * + * @exception BuildException if the storage method fails. + */ + public void storeElement(Project project, Object parent, Object child, + String elementName) throws BuildException { if (elementName == null) { return; } - NestedStorer ns = (NestedStorer)nestedStorers.get(elementName); + NestedStorer ns = (NestedStorer) nestedStorers.get(elementName); if (ns == null) { return; } try { - ns.store(element, child); + ns.store(parent, child); } catch (IllegalAccessException ie) { // impossible as getMethods should only return public methods throw new BuildException(ie); @@ -418,7 +622,16 @@ public class IntrospectionHelper implements BuildListener { } /** - * returns the type of a named nested element. + * Returns the type of a named nested element. + * + * @param elementName The name of the element to find the type of. + * Must not be null. + * + * @return the type of the nested element with the specified name. + * This will never be null. + * + * @exception BuildException if the introspected class does not + * support the named nested element. */ public Class getElementType(String elementName) throws BuildException { @@ -432,7 +645,16 @@ public class IntrospectionHelper implements BuildListener { } /** - * returns the type of a named attribute. + * Returns the type of a named attribute. + * + * @param attributeName The name of the attribute to find the type of. + * Must not be null. + * + * @return the type of the attribute with the specified name. + * This will never be null. + * + * @exception BuildException if the introspected class does not + * support the named attribute. */ public Class getAttributeType(String attributeName) throws BuildException { @@ -446,35 +668,77 @@ public class IntrospectionHelper implements BuildListener { } /** - * Does the introspected class support PCDATA? + * Returns whether or not the introspected class supports PCDATA. + * + * @return whether or not the introspected class supports PCDATA. */ public boolean supportsCharacters() { return addText != null; } /** - * Return all attribues supported by the introspected class. + * Returns an enumeration of the names of the attributes supported + * by the introspected class. + * + * @return an enumeration of the names of the attributes supported + * by the introspected class. */ public Enumeration getAttributes() { return attributeSetters.keys(); } /** - * Return all nested elements supported by the introspected class. + * Returns an enumeration of the names of the nested elements supported + * by the introspected class. + * + * @return an enumeration of the names of the nested elements supported + * by the introspected class. */ public Enumeration getNestedElements() { return nestedTypes.keys(); } /** - * Create a proper implementation of AttributeSetter for the given - * attribute type. + * Creates an implementation of AttributeSetter for the given + * attribute type. Conversions (where necessary) are automatically + * made for the following types: + * + * + * If none of the above covers the given parameters, a constructor for the + * appropriate class taking a String parameter is used if it is available. + * + * @param m The method to invoke on the bean when the setter is invoked. + * Must not be null. + * @param arg The type of the single argument of the bean's method. + * Must not be null. + * @param attrName the name of the attribute for which the setter is being + * created. + * + * @return an appropriate AttributeSetter instance, or null + * if no appropriate conversion is available. */ private AttributeSetter createAttributeSetter(final Method m, - final Class arg) { + Class arg, + final String attrName) { + // use wrappers for primitive classes, e.g. int and + // Integer are treated identically + final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey (arg) + ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg; // simplest case - setAttribute expects String - if (java.lang.String.class.equals(arg)) { + if (java.lang.String.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { @@ -482,69 +746,23 @@ public class IntrospectionHelper implements BuildListener { } }; - // now for the primitive types, use their wrappers - } else if (java.lang.Character.class.equals(arg) - || java.lang.Character.TYPE.equals(arg)) { + // char and Character get special treatment - take the first character + } else if (java.lang.Character.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { + if (value.length() == 0) { + throw new BuildException("The value \"\" is not a " + + "legal value for attribute \"" + + attrName + "\""); + } m.invoke(parent, new Character[] {new Character(value.charAt(0))}); } }; - } else if (java.lang.Byte.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Byte[] {new Byte(value)}); - } - - }; - } else if (java.lang.Short.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Short[] {new Short(value)}); - } - - }; - } else if (java.lang.Integer.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Integer[] {new Integer(value)}); - } - - }; - } else if (java.lang.Long.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Long[] {new Long(value)}); - } - - }; - } else if (java.lang.Float.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Float[] {new Float(value)}); - } - - }; - } else if (java.lang.Double.TYPE.equals(arg)) { - return new AttributeSetter() { - public void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException { - m.invoke(parent, new Double[] {new Double(value)}); - } - - }; - - // boolean gets an extra treatment, because we have a nice method - // in Project - } else if (java.lang.Boolean.class.equals(arg) - || java.lang.Boolean.TYPE.equals(arg)) { + // boolean and Boolean get special treatment because we + // have a nice method in Project + } else if (java.lang.Boolean.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { @@ -555,7 +773,7 @@ public class IntrospectionHelper implements BuildListener { }; // Class doesn't have a String constructor but a decent factory method - } else if (java.lang.Class.class.equals(arg)) { + } else if (java.lang.Class.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException { @@ -568,7 +786,7 @@ public class IntrospectionHelper implements BuildListener { }; // resolve relative paths through Project - } else if (java.io.File.class.equals(arg)) { + } else if (java.io.File.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { @@ -578,7 +796,7 @@ public class IntrospectionHelper implements BuildListener { }; // resolve relative paths through Project - } else if (org.apache.tools.ant.types.Path.class.equals(arg)) { + } else if (org.apache.tools.ant.types.Path.class.equals(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { @@ -588,12 +806,13 @@ public class IntrospectionHelper implements BuildListener { }; // EnumeratedAttributes have their own helper class - } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(arg)) { + } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) { return new AttributeSetter() { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException { try { - org.apache.tools.ant.types.EnumeratedAttribute ea = (org.apache.tools.ant.types.EnumeratedAttribute)arg.newInstance(); + org.apache.tools.ant.types.EnumeratedAttribute ea = + (org.apache.tools.ant.types.EnumeratedAttribute) reflectedArg.newInstance(); ea.setValue(value); m.invoke(parent, new EnumeratedAttribute[] {ea}); } catch (InstantiationException ie) { @@ -603,11 +822,13 @@ public class IntrospectionHelper implements BuildListener { }; // worst case. look for a public String constructor and use it + // This is used (deliberately) for all primitives/wrappers other than + // char and boolean } else { try { final Constructor c = - arg.getConstructor(new Class[] {java.lang.String.class}); + reflectedArg.getConstructor(new Class[] {java.lang.String.class}); return new AttributeSetter() { public void set(Project p, Object parent, @@ -632,63 +853,76 @@ public class IntrospectionHelper implements BuildListener { return null; } - protected String getElementName(Project project, Object element) - { - Hashtable elements = project.getTaskDefinitions(); - String typeName = "task"; - if (!elements.contains( element.getClass() )) - { - elements = project.getDataTypeDefinitions(); - typeName = "data type"; - if (!elements.contains( element.getClass() )) - { - elements = null; - } - } - - if (elements != null) - { - Enumeration e = elements.keys(); - while (e.hasMoreElements()) - { - String elementName = (String) e.nextElement(); - Class elementClass = (Class) elements.get( elementName ); - if ( element.getClass().equals( elementClass ) ) - { - return "The <" + elementName + "> " + typeName; - } - } - } - - return "Class " + element.getClass().getName(); + /** + * Returns a description of the type of the given element in + * relation to a given project. This is used for logging purposes + * when the element is asked to cope with some data it has no + * way of handling. + * + * @param project The project the element is defined in. + * Must not be null. + * + * @param element The element to describe. + * Must not be null. + * + * @return a description of the element type + */ + protected String getElementName(Project project, Object element) { + return project.getElementName(element); } /** - * extract the name of a property from a method name - subtracting - * a given prefix. + * Extracts the name of a property from a method name by subtracting + * a given prefix and converting into lower case. It is up to calling + * code to make sure the method name does actually begin with the + * specified prefix - no checking is done in this method. + * + * @param methodName The name of the method in question. + * Must not be null. + * @param prefix The prefix to remove. + * Must not be null. + * + * @return the lower-cased method name with the prefix removed. */ private String getPropertyName(String methodName, String prefix) { int start = prefix.length(); return methodName.substring(start).toLowerCase(Locale.US); } + /** + * Internal interface used to create nested elements. Not documented + * in detail for reasons of source code readability. + */ private interface NestedCreator { Object create(Object parent) throws InvocationTargetException, IllegalAccessException, InstantiationException; } + /** + * Internal interface used to storing nested elements. Not documented + * in detail for reasons of source code readability. + */ private interface NestedStorer { void store(Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException; } + /** + * Internal interface used to setting element attributes. Not documented + * in detail for reasons of source code readability. + */ private interface AttributeSetter { void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException; } - public void buildStarted(BuildEvent event) {} + /** + * Clears all storage used by this class, including the static cache of + * helpers. + * + * @param event Ignored in this implementation. + */ public void buildFinished(BuildEvent event) { attributeTypes.clear(); attributeSetters.clear(); @@ -698,9 +932,44 @@ public class IntrospectionHelper implements BuildListener { helpers.clear(); } + /** + * Empty implementation to satisfy the BuildListener interface. + * @param event Ignored in this implementation. + */ + public void buildStarted(BuildEvent event) {} + + /** + * Empty implementation to satisfy the BuildListener interface. + * + * @param event Ignored in this implementation. + */ public void targetStarted(BuildEvent event) {} + + /** + * Empty implementation to satisfy the BuildListener interface. + * + * @param event Ignored in this implementation. + */ public void targetFinished(BuildEvent event) {} + + /** + * Empty implementation to satisfy the BuildListener interface. + * + * @param event Ignored in this implementation. + */ public void taskStarted(BuildEvent event) {} + + /** + * Empty implementation to satisfy the BuildListener interface. + * + * @param event Ignored in this implementation. + */ public void taskFinished(BuildEvent event) {} + + /** + * Empty implementation to satisfy the BuildListener interface. + * + * @param event Ignored in this implementation. + */ public void messageLogged(BuildEvent event) {} } diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java index 118a2d59c..f40ed1b3b 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java @@ -1,7 +1,7 @@ /* * The Apache Software License, Version 1.1 * - * Copyright (c) 2000-2002 The Apache Software Foundation. All rights + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,6 @@ import java.util.Vector; import java.util.Properties; import java.util.Enumeration; import java.util.Stack; -import java.lang.reflect.Modifier; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; @@ -70,8 +69,10 @@ import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FilterSet; import org.apache.tools.ant.types.FilterSetCollection; import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.LazyHashtable; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.taskdefs.Antlib; +import org.apache.tools.ant.input.InputHandler; /** * Central representation of an Ant project. This class defines a Ant project @@ -165,13 +166,16 @@ public class Project { private String name; private String description; + /** Map of references within the project (paths etc) (String to Object). */ private Hashtable properties = new Hashtable(); private Hashtable userProperties = new Hashtable(); - private Hashtable references = new Hashtable(); + private Hashtable references = new AntRefTable(this); private String defaultTarget; - // private Hashtable dataClassDefinitions = new Hashtable(); - // private Hashtable taskClassDefinitions = new Hashtable(); + /** Map from data type names to implementing classes (String to Class). */ + private Hashtable dataClassDefinitions = new AntTaskTable(this, false); + /** Map from task names to implementing classes (String to Class). */ + private Hashtable taskClassDefinitions = new AntTaskTable(this, true); private Hashtable createdTasks = new Hashtable(); private Hashtable targets = new Hashtable(); private FilterSet globalFilterSet = new FilterSet(); @@ -185,11 +189,13 @@ public class Project { */ private ClassLoader coreLoader = null; - /** - * Records the latest task on a thread - */ + /** Records the latest task to be executed on a thread (Thread to Task). */ private Hashtable threadTasks = new Hashtable(); + /** Records the latest task to be executed on a thread Group. */ + private Hashtable threadGroupTasks = new Hashtable(); + + /** * Store symbol tables */ @@ -219,6 +225,48 @@ public class Project { } } + /** + * Called to handle any input requests. + */ + private InputHandler inputHandler = null; + + /** + * The default input stream used to read any input + */ + private InputStream defaultInputStream = null; + + /** + * Sets the input handler + * + * @param handler the InputHandler instance to use for gathering input. + */ + public void setInputHandler(InputHandler handler) { + inputHandler = handler; + } + + /** + * Set the default System input stream. Normally this stream is set to + * System.in. This inputStream is used when no task inptu redirection is + * being performed. + * + * @param defaultInputStream the default input stream to use when input + * is reuested. + * @since Ant 1.6 + */ + public void setDefaultInputStream(InputStream defaultInputStream) { + this.defaultInputStream = defaultInputStream; + } + + /** + * Retrieves the current input handler. + * + * @return the InputHandler instance currently in place for the project + * instance. + */ + public InputHandler getInputHandler() { + return inputHandler; + } + private FileUtils fileUtils; @@ -302,12 +350,17 @@ public class Project { private void autoLoadDefinitions() { DirectoryScanner ds = new DirectoryScanner(); - ds.setBasedir(new File(getProperty("ant.home"),"autolib")); - ds.scan(); - String dirs[] = ds.getIncludedDirectories(); - for (int i = 0; i < dirs.length; i++) { - autoLoad(ds.getBasedir(), dirs[i]); - } + try { + File autolib=new File(getProperty("ant.home"),"autolib"); + log("scanning the autolib directory "+autolib.toString(),MSG_DEBUG); + ds.setBasedir(autolib); + ds.scan(); + String dirs[] = ds.getIncludedDirectories(); + for (int i = 0; i < dirs.length; i++) { + autoLoad(ds.getBasedir(), dirs[i]); + } + } catch (Exception e) {} + } private void autoLoad(File base, String dir) { @@ -343,21 +396,59 @@ public class Project { /** - * Initialise the project. This involves setting the default task - * definitions and loading the system properties. + * Initialises the project. * - *@exception BuildException Description of the Exception + * This involves setting the default task definitions and loading the + * system properties. + * + * @exception BuildException if the default task list cannot be loaded */ public void init() throws BuildException { setJavaVersionProperty(); - setSystemProperties(); if (!isRoleDefined(TASK_ROLE)) { // Top project, need to load the core definitions loadDefinitions(); } + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream(defs); + if (in == null) { + throw new BuildException("Can't load default task list"); + } + props.load(in); + in.close(); + ((AntTaskTable) taskClassDefinitions).addDefinitions(props); + + + } catch (IOException ioe) { + throw new BuildException("Can't load default task list"); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream(dataDefs); + if (in == null) { + throw new BuildException("Can't load default datatype list"); + } + props.load(in); + in.close(); + + ((AntTaskTable) dataClassDefinitions).addDefinitions(props); + + + } catch (IOException ioe) { + throw new BuildException("Can't load default datatype list"); + } + + setSystemProperties(); } + /** * Sets the CoreLoader to the default of the Project object */ @@ -1223,13 +1314,16 @@ public class Project { /** - * Description of the Method + * Demultiplexes output so that each task receives the appropriate + * messages. If the current thread is not currently executing a task, + * the message is logged directly. * - *@param line Description of the Parameter - *@param isError Description of the Parameter + * @param line Message to handle. Should not be null. + * @param isError Whether the text represents an error (true) + * or information (false). */ public void demuxOutput(String line, boolean isError) { - Task task = (Task) threadTasks.get(Thread.currentThread()); + Task task = getThreadTask(Thread.currentThread()); if (task == null) { fireMessageLogged(this, line, isError ? MSG_ERR : MSG_INFO); } else { @@ -1241,6 +1335,75 @@ public class Project { } } + /** + * Read data from the default input stream. If no default has been + * specified, System.in is used. + * + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read + * + * @return the number of bytes read + * + * @exception IOException if the data cannot be read + * @since Ant 1.6 + */ + public int defaultInput(byte[] buffer, int offset, int length) + throws IOException { + if (defaultInputStream != null) { + return defaultInputStream.read(buffer, offset, length); + } else { + return System.in.read(buffer, offset, length); + } + } + + /** + * Demux an input request to the correct task. + * + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read + * + * @return the number of bytes read + * + * @exception IOException if the data cannot be read + * @since Ant 1.6 + */ + public int demuxInput(byte[] buffer, int offset, int length) + throws IOException { + Task task = getThreadTask(Thread.currentThread()); + if (task == null) { + return defaultInput(buffer, offset, length); + } else { + return task.handleInput(buffer, offset, length); + } + } + + /** + * Demultiplexes flush operation so that each task receives the appropriate + * messages. If the current thread is not currently executing a task, + * the message is logged directly. + * + * @since Ant 1.5.2 + * + * @param line Message to handle. Should not be null. + * @param isError Whether the text represents an error (true) + * or information (false). + */ + public void demuxFlush(String line, boolean isError) { + Task task = getThreadTask(Thread.currentThread()); + if (task == null) { + fireMessageLogged(this, line, isError ? MSG_ERR : MSG_INFO); + } else { + if (isError) { + task.handleErrorFlush(line); + } else { + task.handleFlush(line); + } + } + } + + /** * execute the targets and any targets it depends on @@ -1646,21 +1809,35 @@ public class Project { /** - * Adds a feature to the Reference attribute of the Project object + * Adds a reference to the project. * - *@param name The feature to be added to the Reference attribute - *@param value The feature to be added to the Reference attribute + * @param name The name of the reference. Must not be null. + * @param value The value of the reference. Must not be null. */ public void addReference(String name, Object value) { - Object o = references.get(name); - if (null != o && o != value - && (!(o instanceof RoleAdapter) - || ((RoleAdapter) o).getProxy() != value)) { - log("Overriding previous definition of reference to " + name, + synchronized (references) { + Object old = ((AntRefTable) references).getReal(name); + if (old == value) { + // no warning, this is not changing anything + return; + } + if (old != null && !(old instanceof UnknownElement)) { + log("Overriding previous definition of reference to " + name, MSG_WARN); + } + + String valueAsString = ""; + try { + valueAsString = value.toString(); + } catch (Throwable t) { + log("Caught exception (" + t.getClass().getName() + ")" + + " while expanding " + name + ": " + t.getMessage(), + MSG_WARN); + } + log("Adding reference: " + name + " -> " + valueAsString, + MSG_DEBUG); + references.put(name, value); } - log("Adding reference: " + name + " -> " + value, MSG_DEBUG); - references.put(name, value); } @@ -1683,6 +1860,45 @@ public class Project { } + /** + * Returns a description of the type of the given element, with + * special handling for instances of tasks and data types. + *

+ * This is useful for logging purposes. + * + * @param element The element to describe. + * Must not be null. + * + * @return a description of the element type + * + * @since 1.95, Ant 1.5 + */ + public String getElementName(Object element) { + Hashtable elements = taskClassDefinitions; + Class elementClass = element.getClass(); + String typeName = "task"; + if (!elements.contains(elementClass)) { + elements = dataClassDefinitions; + typeName = "data type"; + if (!elements.contains(elementClass)) { + elements = null; + } + } + + if (elements != null) { + Enumeration e = elements.keys(); + while (e.hasMoreElements()) { + String name = (String) e.nextElement(); + Class clazz = (Class) elements.get(name); + if (elementClass.equals(clazz)) { + return "The <" + name + "> " + typeName; + } + } + } + + return "Class " + elementClass.getName(); + } + /** * send build started event to the listeners */ @@ -1828,5 +2044,165 @@ public class Project { BuildEvent event = new BuildEvent(task); fireMessageLoggedEvent(event, message, priority); } + /** + * Register a task as the current task for a thread. + * If the task is null, the thread's entry is removed. + * + * @param thread the thread on which the task is registered. + * @param task the task to be registered. + * @since Ant 1.5 + */ + public synchronized void registerThreadTask(Thread thread, Task task) { + if (task != null) { + threadTasks.put(thread, task); + threadGroupTasks.put(thread.getThreadGroup(), task); + } else { + threadTasks.remove(thread); + threadGroupTasks.remove(thread.getThreadGroup()); + } + } + + /** + * Get the current task assopciated with a thread, if any + * + * @param thread the thread for which the task is required. + * @return the task which is currently registered for the given thread or + * null if no task is registered. + */ + public Task getThreadTask(Thread thread) { + Task task = (Task) threadTasks.get(thread); + if (task == null) { + ThreadGroup group = thread.getThreadGroup(); + while (task == null && group != null) { + task = (Task) threadGroupTasks.get(group); + group = group.getParent(); + } + } + return task; + } + + + // Should move to a separate public class - and have API to add + // listeners, etc. + private static class AntRefTable extends Hashtable { + Project project; + public AntRefTable(Project project) { + super(); + this.project = project; + } + + /** Returns the unmodified original object. + * This method should be called internally to + * get the 'real' object. + * The normal get method will do the replacement + * of UnknownElement ( this is similar with the JDNI + * refs behavior ) + */ + public Object getReal(Object key) { + return super.get(key); + } + + /** Get method for the reference table. + * It can be used to hook dynamic references and to modify + * some references on the fly - for example for delayed + * evaluation. + * + * It is important to make sure that the processing that is + * done inside is not calling get indirectly. + * + * @param key + * @return + */ + public Object get(Object key) { + //System.out.println("AntRefTable.get " + key); + Object o = super.get(key); + if (o instanceof UnknownElement) { + // Make sure that + ((UnknownElement) o).maybeConfigure(); + o = ((UnknownElement) o).getTask(); + } + return o; + } + } + + private static class AntTaskTable extends LazyHashtable { + Project project; + Properties props; + boolean tasks = false; + + public AntTaskTable(Project p, boolean tasks) { + this.project = p; + this.tasks = tasks; + } + + public void addDefinitions(Properties props) { + this.props = props; + } + + protected void initAll() { + if (initAllDone ) return; + project.log("InitAll", Project.MSG_DEBUG); + if (props==null ) return; + Enumeration enum = props.propertyNames(); + while (enum.hasMoreElements()) { + String key = (String) enum.nextElement(); + Class taskClass=getTask( key ); + if (taskClass!=null ) { + // This will call a get() and a put() + if (tasks ) + project.addTaskDefinition(key, taskClass); + else + project.addDataTypeDefinition(key, taskClass ); + } + } + initAllDone=true; + } + + protected Class getTask(String key) { + if (props==null ) return null; // for tasks loaded before init() + String value=props.getProperty(key); + if (value==null) { + //project.log( "No class name for " + key, Project.MSG_VERBOSE ); + return null; + } + try { + Class taskClass=null; + if (project.getCoreLoader() != null && + !("only".equals(project.getProperty("build.sysclasspath")))) { + try { + taskClass=project.getCoreLoader().loadClass(value); + if (taskClass != null ) return taskClass; + } catch( Exception ex ) { + } + } + taskClass = Class.forName(value); + return taskClass; + } catch (NoClassDefFoundError ncdfe) { + project.log("Could not load a dependent class (" + + ncdfe.getMessage() + ") for task " + key, Project.MSG_DEBUG); + } catch (ClassNotFoundException cnfe) { + project.log("Could not load class (" + value + + ") for task " + key, Project.MSG_DEBUG); + } + return null; + } + + // Hashtable implementation + public Object get( Object key ) { + Object orig=super.get( key ); + if (orig!= null ) return orig; + if (! (key instanceof String) ) return null; + project.log("Get task " + key, Project.MSG_DEBUG ); + Object taskClass=getTask( (String) key); + if (taskClass != null) + super.put( key, taskClass ); + return taskClass; + } + + public boolean containsKey(Object key) { + return get(key) != null; + } + + } }