From 4ad59f0fc5eb69936f4322c1394993d33299959b Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Mon, 18 Feb 2002 08:55:17 +0000 Subject: [PATCH] Antlib proposal Second installment Submitted by: Jose Alberto Fernandez git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271407 13f79535-47bb-0310-9956-ffa450edef68 --- proposal/sandbox/antlib/.cvsignore | 2 + proposal/sandbox/antlib/build.xml | 9 + .../apache/tools/ant/IntrospectionHelper.java | 706 ++++++++++++++++ .../main/org/apache/tools/ant/Project.java | 274 +++--- .../org/apache/tools/ant/ProjectHelper.java | 797 ++++++++++++++++++ .../org/apache/tools/ant/RoleAdapter.java | 5 + .../org/apache/tools/ant/SymbolTable.java | 354 ++++---- .../org/apache/tools/ant/TaskAdapter.java | 1 + .../org/apache/tools/ant/taskdefs/Ant.java | 24 +- .../org/apache/tools/ant/taskdefs/Antlib.java | 37 +- .../tools/ant/types/DataTypeAdapterTask.java | 24 +- .../sandbox/antlib/src/testcases/build.xml | 30 + .../antlib/src/testcases/case-antlib.xml | 5 + .../sandbox/antlib/src/testcases/case.xml | 39 + .../org/apache/ant/contrib/Case.java | 169 ++++ 15 files changed, 2172 insertions(+), 304 deletions(-) create mode 100644 proposal/sandbox/antlib/.cvsignore create mode 100644 proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java create mode 100644 proposal/sandbox/antlib/src/main/org/apache/tools/ant/ProjectHelper.java create mode 100644 proposal/sandbox/antlib/src/testcases/build.xml create mode 100644 proposal/sandbox/antlib/src/testcases/case-antlib.xml create mode 100644 proposal/sandbox/antlib/src/testcases/case.xml create mode 100644 proposal/sandbox/antlib/src/testcases/org/apache/ant/contrib/Case.java diff --git a/proposal/sandbox/antlib/.cvsignore b/proposal/sandbox/antlib/.cvsignore new file mode 100644 index 000000000..9d0b71a3c --- /dev/null +++ b/proposal/sandbox/antlib/.cvsignore @@ -0,0 +1,2 @@ +build +dist diff --git a/proposal/sandbox/antlib/build.xml b/proposal/sandbox/antlib/build.xml index c887b2375..465ee809c 100644 --- a/proposal/sandbox/antlib/build.xml +++ b/proposal/sandbox/antlib/build.xml @@ -6,6 +6,7 @@ + @@ -19,6 +20,8 @@ + + @@ -54,6 +57,12 @@ + + + + + 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 new file mode 100644 index 000000000..c7d1b7e87 --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,706 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * 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 + * 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. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +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.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; + +/** + * Helper class that collects the methods a task or nested element + * holds to set attributes, create nested elements or hold PCDATA + * elements. + * + * @author Stefan Bodewig + */ +public class IntrospectionHelper implements BuildListener { + + /** + * holds the types of the attributes that could be set. + */ + private Hashtable attributeTypes; + + /** + * holds the attribute setter methods. + */ + private Hashtable attributeSetters; + + /** + * Holds the types of nested elements that could be created. + */ + private Hashtable nestedTypes; + + /** + * Holds methods to create nested elements. + */ + private Hashtable nestedCreators; + + /** + * Holds methods to store configured nested elements. + */ + private Hashtable nestedStorers; + + /** + * The method to add PCDATA stuff. + */ + private Method addText = null; + + /** + * The Class that's been introspected. + */ + private Class bean; + + /** + * instances we've already created + */ + private static Hashtable helpers = new Hashtable(); + + private IntrospectionHelper(final Class bean) { + attributeTypes = new Hashtable(); + attributeSetters = new Hashtable(); + nestedTypes = new Hashtable(); + nestedCreators = new Hashtable(); + nestedStorers = new Hashtable(); + + this.bean = bean; + + Method[] methods = bean.getMethods(); + for (int i=0; i " + typeName; + } + } + } + + return "Class " + element.getClass().getName(); + } + + /** + * extract the name of a property from a method name - subtracting + * a given prefix. + */ + private String getPropertyName(String methodName, String prefix) { + int start = prefix.length(); + return methodName.substring(start).toLowerCase(Locale.US); + } + + private interface NestedCreator { + Object create(Object parent) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface NestedStorer { + void store(Object parent, Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface AttributeSetter { + void set(Project p, Object parent, String value) + throws InvocationTargetException, IllegalAccessException, + BuildException; + } + + public void buildStarted(BuildEvent event) {} + public void buildFinished(BuildEvent event) { + attributeTypes.clear(); + attributeSetters.clear(); + nestedTypes.clear(); + nestedCreators.clear(); + addText = null; + helpers.clear(); + } + + public void targetStarted(BuildEvent event) {} + public void targetFinished(BuildEvent event) {} + public void taskStarted(BuildEvent event) {} + public void taskFinished(BuildEvent event) {} + 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 7ffdd1849..ab3a23b84 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 @@ -63,12 +63,15 @@ 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; import org.apache.tools.ant.types.DataTypeAdapterTask; 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.types.Path; /** * Central representation of an Ant project. This class defines a @@ -90,6 +93,9 @@ public class Project { public final static int MSG_VERBOSE = 3; public final static int MSG_DEBUG = 4; + public final static String TASK_ROLE = "task"; + public final static String DATATYPE_ROLE = "datatype"; + // private set of constants to represent the state // of a DFS of the Target dependencies private final static String VISITING = "VISITING"; @@ -165,30 +171,26 @@ public class Project { fileUtils = FileUtils.newFileUtils(); symbols = new SymbolTable(); symbols.setProject(this); + loadDefinitions(); } /** * create a new ant project that inherits from caler project * @param p the calling project */ - public Project(Project p) { + private Project(Project p) { fileUtils = FileUtils.newFileUtils(); - symbols = new SymbolTable(p); + symbols = new SymbolTable(p.getSymbols()); symbols.setProject(this); } /** - * Initialise the project. - * - * This involves setting the default task definitions and loading the - * system properties. + * Loads the core definitions into the Root project. */ - public void init() throws BuildException { - setJavaVersionProperty(); - - // Initialize simbol table just in case - symbols.addRole("task", TaskContainer.class, TaskAdapter.class); - symbols.addRole("datatype", TaskContainer.class, + private void loadDefinitions() { + // Initialize symbol table just in case + symbols.addRole(TASK_ROLE, TaskContainer.class, TaskAdapter.class); + symbols.addRole(DATATYPE_ROLE, TaskContainer.class, DataTypeAdapterTask.class); String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; @@ -248,7 +250,23 @@ public class Project { } catch (IOException ioe) { throw new BuildException("Can't load default datatype list"); } + } + /** + * Creates a subproject of the current project. + */ + public Project createSubProject() { + return new Project(this); + } + + /** + * Initialise the project. + * + * This involves setting the default task definitions and loading the + * system properties. + */ + public void init() throws BuildException { + setJavaVersionProperty(); setSystemProperties(); } @@ -275,7 +293,7 @@ public class Project { /** * Get the symbols associated with this project. */ - public SymbolTable getSymbols() { + private SymbolTable getSymbols() { // Package protected on purpose return symbols; } @@ -618,6 +636,46 @@ public class Project { } } + public ClassLoader addToLoader(String loader, Path path) { + return symbols.addToLoader(loader, path); + } + + public boolean addRoleDefinition(String role, + Class roleClass, Class adapter) + { + return symbols.addRole(role, roleClass, adapter); + } + + /** + * test for a role name being in use already + * + * @param name the name to test + * @return true if it is a task or a datatype + */ + public boolean isRoleDefined(String name) { + return (symbols.getRole(name) != null); + } + + public void addDefinitionOnRole(String role, + String type, Class clz) + { + Class old = symbols.add(role, type, clz); + // Special management for Tasks + if (TASK_ROLE.equals(role) && null != old && !old.equals(clz)) { + invalidateCreatedTasks(type); + } + } + + /** + * test for a name being in use already on this role + * + * @param name the name to test + * @return true if it is a task or a datatype + */ + public boolean isDefinedOnRole(String role, String name) { + return (symbols.get(role, name) != null); + } + /** * add a new task definition, complain if there is an overwrite attempt * @param taskName name of the task @@ -627,21 +685,14 @@ public class Project { */ public void addTaskDefinition(String taskName, Class taskClass) throws BuildException { - Class old = symbols.add("task", taskName, taskClass); - if (null != old && !old.equals(taskClass)) { - invalidateCreatedTasks(taskName); - } - - String msg = - " +User task: " + taskName + " " + taskClass.getName(); - log(msg, MSG_DEBUG); - checkTaskClass(taskClass); + addDefinitionOnRole(TASK_ROLE, taskName, taskClass); } /** * Checks a class, whether it is suitable for serving as ant task. * @throws BuildException and logs as Project.MSG_ERR for * conditions, that will cause the task execution to fail. + * @deprecated this is done now when added to SymbolTable */ public void checkTaskClass(final Class taskClass) throws BuildException { if( !Task.class.isAssignableFrom(taskClass) ) { @@ -653,7 +704,7 @@ public class Project { * get the current task definition hashtable */ public Hashtable getTaskDefinitions() { - return symbols.getTaskDefinitions(); + return symbols.getDefinitions(TASK_ROLE); } /** @@ -662,18 +713,14 @@ public class Project { * @param typeClass full datatype classname */ public void addDataTypeDefinition(String typeName, Class typeClass) { - symbols.add("datatype", typeName, typeClass); - - String msg = - " +User datatype: " + typeName + " " + typeClass.getName(); - log(msg, MSG_DEBUG); + addDefinitionOnRole(DATATYPE_ROLE, typeName, typeClass); } /** * get the current task definition hashtable */ public Hashtable getDataTypeDefinitions() { - return symbols.getDataTypeDefinitions(); + return symbols.getDefinitions(DATATYPE_ROLE); } /** @@ -701,7 +748,7 @@ public class Project { * in the project. * @see Project#addOrReplaceTarget to replace existing Targets. */ - public void addTarget(String targetName, Target target) + public void addTarget(String targetName, Target target) throws BuildException { if (targets.get(targetName) != null) { throw new BuildException("Duplicate target: `"+targetName+"'"); @@ -737,6 +784,88 @@ public class Project { return targets; } + /** + * Create a new element instance on a Role + * @param role name of the role to use + * @param type name of the element to create + * @return null if element unknown on this role + */ + public Object createForRole(String role, String type) { + SymbolTable.Factory f = symbols.get(role, type); + if (f == null) return null; + + try { + Object o = f.create(this); + // Do special book keeping for ProjectComponents + if ( o instanceof ProjectComponent ) { + ((ProjectComponent)o).setProject(this); + if (o instanceof Task) { + Task task = (Task) o; + task.setTaskType(type); + + // set default value, can be changed by the user + task.setTaskName(type); + addCreatedTask(type, task); + } + } + String msg = " +" + role + ": " + type; + log (msg, MSG_DEBUG); + return o; + } + catch (Throwable t) { + String msg = "Could not create " + role + " of type: " + + type + " due to " + t; + throw new BuildException(msg, t); + } + } + + /** + * + */ + public Object createInRole(Object container, String type) { + Class clz = container.getClass(); + String roles[] = symbols.findRoles(clz); + Object theOne = null; + Method add = null; + + for(int i = 0; i < roles.length; i++) { + Object o = createForRole(roles[i], type); + if (o != null) { + if (theOne != null) { + String msg = "Element " + type + + " is ambiguous for container " + clz.getName(); + if (theOne instanceof RoleAdapter) + theOne = ((RoleAdapter)theOne).getProxy(); + if (o instanceof RoleAdapter) + o = ((RoleAdapter)o).getProxy(); + + log(msg, MSG_ERR); + log("cannot distinguish between " + + theOne.getClass().getName() + + " and " + o.getClass().getName(), MSG_ERR); + throw new BuildException(msg); + } + theOne = o; + add = symbols.getRole(roles[i]).getInterfaceMethod(); + } + } + if (theOne != null) { + try { + add.invoke(container, new Object[]{theOne}); + } + catch(InvocationTargetException ite) { + if (ite.getTargetException() instanceof BuildException) { + throw (BuildException)ite.getTargetException(); + } + throw new BuildException(ite.getTargetException()); + } + catch(Exception e) { + throw new BuildException(e); + } + } + return theOne; + } + /** * create a new task instance * @param taskType name of the task @@ -744,39 +873,7 @@ public class Project { * @return null if the task name is unknown */ public Task createTask(String taskType) throws BuildException { - Class c = symbols.get("task", taskType); - - if (c == null) { - return null; - } - - try { - Object o = c.newInstance(); - Task task = null; - if( o instanceof Task ) { - task=(Task)o; - } else { - // "Generic" Bean - use the setter pattern - // and an Adapter - TaskAdapter taskA=new TaskAdapter(); - taskA.setProxy( o ); - task=taskA; - } - task.setProject(this); - task.setTaskType(taskType); - - // set default value, can be changed by the user - task.setTaskName(taskType); - - String msg = " +Task: " + taskType; - log (msg, MSG_DEBUG); - addCreatedTask(taskType, task); - return task; - } catch (Throwable t) { - String msg = "Could not create task of type: " - + taskType + " due to " + t; - throw new BuildException(msg, t); - } + return (Task) createForRole(TASK_ROLE, taskType); } /** @@ -820,47 +917,11 @@ public class Project { * @return null if the datatype name is unknown */ public Object createDataType(String typeName) throws BuildException { - Class c = symbols.get("datatype", typeName); - - if (c == null) { - return null; - } - - try { - java.lang.reflect.Constructor ctor = null; - boolean noArg = false; - // DataType can have a "no arg" constructor or take a single - // Project argument. - try { - ctor = c.getConstructor(new Class[0]); - noArg = true; - } catch (NoSuchMethodException nse) { - ctor = c.getConstructor(new Class[] {Project.class}); - noArg = false; - } - - Object o = null; - if (noArg) { - o = ctor.newInstance(new Object[0]); - } else { - o = ctor.newInstance(new Object[] {this}); - } - if (o instanceof ProjectComponent) { - ((ProjectComponent)o).setProject(this); - } - String msg = " +DataType: " + typeName; - log (msg, MSG_DEBUG); - return o; - } catch (java.lang.reflect.InvocationTargetException ite) { - Throwable t = ite.getTargetException(); - String msg = "Could not create datatype of type: " - + typeName + " due to " + t; - throw new BuildException(msg, t); - } catch (Throwable t) { - String msg = "Could not create datatype of type: " - + typeName + " due to " + t; - throw new BuildException(msg, t); - } + // This is to make the function backward compatible + // Since we know if it returning an adapter for it + DataTypeAdapterTask dt = + (DataTypeAdapterTask) createForRole(DATATYPE_ROLE, typeName); + return (dt != null? dt.getProxy() : null); } /** @@ -1227,7 +1288,10 @@ public class Project { } public void addReference(String name, Object value) { - if (null != references.get(name)) { + 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, MSG_WARN); } diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/ProjectHelper.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/ProjectHelper.java new file mode 100644 index 000000000..f832bce3e --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/ProjectHelper.java @@ -0,0 +1,797 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * 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 + * 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. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Locale; +import org.xml.sax.Locator; +import org.xml.sax.InputSource; +import org.xml.sax.HandlerBase; +import org.xml.sax.SAXParseException; +import org.xml.sax.SAXException; +import org.xml.sax.DocumentHandler; +import org.xml.sax.AttributeList; + + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.ParserConfigurationException; + +/** + * Configures a Project (complete with Targets and Tasks) based on + * a XML build file. + * + * @author duncan@x180.com + */ + +public class ProjectHelper { + + private static SAXParserFactory parserFactory = null; + + private org.xml.sax.Parser parser; + private Project project; + private File buildFile; + private File buildFileParent; + private Locator locator; + + /** + * Configures the Project with the contents of the specified XML file. + */ + public static void configureProject(Project project, File buildFile) throws BuildException { + new ProjectHelper(project, buildFile).parse(); + } + + /** + * Constructs a new Ant parser for the specified XML file. + */ + private ProjectHelper(Project project, File buildFile) { + this.project = project; + this.buildFile = new File(buildFile.getAbsolutePath()); + buildFileParent = new File(this.buildFile.getParent()); + } + + /** + * Parses the project file. + */ + private void parse() throws BuildException { + FileInputStream inputStream = null; + InputSource inputSource = null; + + try { + SAXParser saxParser = getParserFactory().newSAXParser(); + parser = saxParser.getParser(); + + String uri = "file:" + buildFile.getAbsolutePath().replace('\\', '/'); + for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) { + uri = uri.substring(0, index) + "%23" + uri.substring(index+1); + } + + inputStream = new FileInputStream(buildFile); + inputSource = new InputSource(inputStream); + inputSource.setSystemId(uri); + project.log("parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE); + saxParser.parse(inputSource, new RootHandler()); + } + catch(ParserConfigurationException exc) { + throw new BuildException("Parser has not been configured correctly", exc); + } + catch(SAXParseException exc) { + Location location = + new Location(buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber()); + + Throwable t = exc.getException(); + if (t instanceof BuildException) { + BuildException be = (BuildException) t; + if (be.getLocation() == Location.UNKNOWN_LOCATION) { + be.setLocation(location); + } + throw be; + } + + throw new BuildException(exc.getMessage(), t, location); + } + catch(SAXException exc) { + Throwable t = exc.getException(); + if (t instanceof BuildException) { + throw (BuildException) t; + } + throw new BuildException(exc.getMessage(), t); + } + catch(FileNotFoundException exc) { + throw new BuildException(exc); + } + catch(IOException exc) { + throw new BuildException("Error reading project file", exc); + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (IOException ioe) { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically + * throws an exception in each method, so subclasses should override + * what they can handle. + * + * Each type of xml element (task, target, etc) in ant will + * have its own subclass of AbstractHandler. + * + * In the constructor, this class takes over the handling of sax + * events from the parent handler, and returns + * control back to the parent in the endElement method. + */ + private class AbstractHandler extends HandlerBase { + protected DocumentHandler parentHandler; + + public AbstractHandler(DocumentHandler parentHandler) { + this.parentHandler = parentHandler; + + // Start handling SAX events + parser.setDocumentHandler(this); + } + + public void startElement(String tag, AttributeList attrs) throws SAXParseException { + throw new SAXParseException("Unexpected element \"" + tag + "\"", locator); + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + String s = new String(buf, start, end).trim(); + + if (s.length() > 0) { + throw new SAXParseException("Unexpected text \"" + s + "\"", locator); + } + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() {} + + public void endElement(String name) throws SAXException { + + finished(); + // Let parent resume handling SAX events + parser.setDocumentHandler(parentHandler); + } + } + + /** + * Handler for the root element. It's only child must be the "project" element. + */ + private class RootHandler extends HandlerBase { + + /** + * resolve file: URIs as relative to the build file. + */ + public InputSource resolveEntity(String publicId, + String systemId) { + + project.log("resolving systemId: " + systemId, Project.MSG_VERBOSE); + + if (systemId.startsWith("file:")) { + String path = systemId.substring(5); + int index = path.indexOf("file:"); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while (index != -1) { + path = path.substring(0, index) + path.substring(index + 5); + index = path.indexOf("file:"); + } + + String entitySystemId = path; + index = path.indexOf("%23"); + // convert these to # + while (index != -1) { + path = path.substring(0, index) + "#" + path.substring(index + 3); + index = path.indexOf("%23"); + } + + File file = new File(path); + if (!file.isAbsolute()) { + file = new File(buildFileParent, path); + } + + try { + InputSource inputSource = new InputSource(new FileInputStream(file)); + inputSource.setSystemId("file:" + entitySystemId); + return inputSource; + } catch (FileNotFoundException fne) { + project.log(file.getAbsolutePath()+" could not be found", + Project.MSG_WARN); + } + } + // use default if not file or file not found + return null; + } + + public void startElement(String tag, AttributeList attrs) throws SAXParseException { + if (tag.equals("project")) { + new ProjectHandler(this).init(tag, attrs); + } else { + throw new SAXParseException("Config file is not of expected XML type", locator); + } + } + + public void setDocumentLocator(Locator locator) { + ProjectHelper.this.locator = locator; + } + } + + /** + * Handler for the top level "project" element. + */ + private class ProjectHandler extends AbstractHandler { + public ProjectHandler(DocumentHandler parentHandler) { + super(parentHandler); + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for (int i = 0; i < attrs.getLength(); i++) { + String key = attrs.getName(i); + String value = attrs.getValue(i); + + if (key.equals("default")) { + def = value; + } else if (key.equals("name")) { + name = value; + } else if (key.equals("id")) { + id = value; + } else if (key.equals("basedir")) { + baseDir = value; + } else { + throw new SAXParseException("Unexpected attribute \"" + attrs.getName(i) + "\"", locator); + } + } + + if (def == null) { + throw new SAXParseException("The default attribute of project is required", + locator); + } + + + project.setDefaultTarget(def); + + if (name != null) { + project.setName(name); + project.addReference(name, project); + } + + if (id != null) { + project.addReference(id, project); + } + + if (project.getProperty("basedir") != null) { + project.setBasedir(project.getProperty("basedir")); + } else { + if (baseDir == null) { + project.setBasedir(buildFileParent.getAbsolutePath()); + } else { + // check whether the user has specified an absolute path + if ((new File(baseDir)).isAbsolute()) { + project.setBasedir(baseDir); + } else { + project.setBaseDir(project.resolveFile(baseDir, buildFileParent)); + } + } + } + + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (name.equals("taskdef")) { + handleTopTask(name, attrs); + } else if (name.equals("typedef")) { + handleTopTask(name, attrs); + } else if (name.equals("antlib")) { + handleTopTask(name, attrs); + } else if (name.equals("property")) { + handleTopTask(name, attrs); + } else if (name.equals("target")) { + handleTarget(name, attrs); + } else if (project.isDefinedOnRole(Project.DATATYPE_ROLE, name)) { + handleTopTask(name, attrs); + } else { + throw new SAXParseException("Unexpected element \"" + name + "\"", locator); + } + } + + private void handleTopTask(String name, AttributeList attrs) + throws SAXParseException { + InmediateTarget target = new InmediateTarget(name); + (new TaskHandler(this, target, null, target)).init(name, attrs); + } + + private void handleTarget(String tag, AttributeList attrs) throws SAXParseException { + new TargetHandler(this).init(tag, attrs); + } + + } + + /** + * Handler for "target" elements. + */ + private class TargetHandler extends AbstractHandler { + private Target target; + + public TargetHandler(DocumentHandler parentHandler) { + super(parentHandler); + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for (int i = 0; i < attrs.getLength(); i++) { + String key = attrs.getName(i); + String value = attrs.getValue(i); + + if (key.equals("name")) { + name = value; + } else if (key.equals("depends")) { + depends = value; + } else if (key.equals("if")) { + ifCond = value; + } else if (key.equals("unless")) { + unlessCond = value; + } else if (key.equals("id")) { + id = value; + } else if (key.equals("description")) { + description = value; + } else { + throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator); + } + } + + if (name == null) { + throw new SAXParseException("target element appears without a name attribute", locator); + } + + target = new Target(); + target.setName(name); + target.setIf(ifCond); + target.setUnless(unlessCond); + target.setDescription(description); + project.addTarget(name, target); + + if (id != null && !id.equals("")) { + project.addReference(id, target); + } + + // take care of dependencies + + if (depends.length() > 0) { + target.setDepends(depends); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + new TaskHandler(this, target, null, target).init(name, attrs); + } + } + + /** + * Handler for all task elements. + */ + private class TaskHandler extends AbstractHandler { + private Target target; + private TaskContainer container; + private Task task; + private RuntimeConfigurable parentWrapper; + private RuntimeConfigurable wrapper = null; + + public TaskHandler(DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target) { + super(parentHandler); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + try { + task = (Task)project.createInRole(container, tag); + } catch (BuildException e) { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if (task == null) { + task = new UnknownElement(tag); + task.setProject(project); + task.setTaskType(tag); + task.setTaskName(tag); + container.addTask(task); + } + + task.setLocation(new Location(buildFile.toString(), + locator.getLineNumber(), + locator.getColumnNumber())); + configureId(task, attrs); + + task.setOwningTarget(target); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes(attrs); + if (parentWrapper != null) { + parentWrapper.addChild(wrapper); + } + } + + protected void finished() { + if (container instanceof InmediateTarget) { + ((InmediateTarget)container).execute(); + } + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + if (wrapper == null) { + try { + addText(project, task, buf, start, end); + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), locator, exc); + } + } else { + wrapper.addText(buf, start, end); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (task instanceof TaskContainer) { + // task can contain other tasks - no other nested elements possible + new TaskHandler(this, (TaskContainer)task, wrapper, target).init(name, attrs); + } + else { + new NestedElementHandler(this, task, wrapper, target).init(name, attrs); + } + } + } + + /** + * Handler for all nested properties. + */ + private class NestedElementHandler extends AbstractHandler { + private Object parent; + private Object child; + private RuntimeConfigurable parentWrapper; + private RuntimeConfigurable childWrapper = null; + private Target target; + + public NestedElementHandler(DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target) { + super(parentHandler); + + if (parent instanceof RoleAdapter) { + this.parent = ((RoleAdapter) parent).getProxy(); + } else { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void init(String propType, AttributeList attrs) throws SAXParseException { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper(parentClass); + + try { + String elementName = propType.toLowerCase(Locale.US); + if (parent instanceof UnknownElement) { + UnknownElement uc = new UnknownElement(elementName); + uc.setProject(project); + ((UnknownElement) parent).addChild(uc); + // Set this parameters just in case is a Task + uc.setTaskType(elementName); + uc.setTaskName(elementName); + child = uc; + } else { + child = ih.createElement(project, parent, elementName); + } + + configureId(child, attrs); + + if (parentWrapper != null) { + childWrapper = new RuntimeConfigurable(child, propType); + childWrapper.setAttributes(attrs); + parentWrapper.addChild(childWrapper); + } else { + configure(child, attrs, project); + ih.storeElement(project, parent, child, elementName); + } + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), locator, exc); + } + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + if (parentWrapper == null) { + try { + addText(project, child, buf, start, end); + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), locator, exc); + } + } else { + childWrapper.addText(buf, start, end); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (child instanceof TaskContainer) { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler(this, (TaskContainer)child, childWrapper, target).init(name, attrs); + } + else { + new NestedElementHandler(this, child, childWrapper, target).init(name, attrs); + } + } + } + + /** + * Special target type for top level Tasks and Datatypes. + * This will allow eliminating special cases. + */ + private class InmediateTarget extends Target { + /** + * Create a target for a top level task or datatype. + * @param name the name of the task to be run on this target. + */ + InmediateTarget(String name) { + super(); + setProject(project); + setName("Top level " + name); + } + } + + public static void configure(Object target, AttributeList attrs, + Project project) throws BuildException { + if( target instanceof RoleAdapter ) { + target=((RoleAdapter)target).getProxy(); + } + + IntrospectionHelper ih = + IntrospectionHelper.getHelper(target.getClass()); + + project.addBuildListener(ih); + + for (int i = 0; i < attrs.getLength(); i++) { + // reflect these into the target + String value=replaceProperties(project, attrs.getValue(i), + project.getProperties() ); + try { + ih.setAttribute(project, target, + attrs.getName(i).toLowerCase(Locale.US), value); + + } catch (BuildException be) { + // id attribute must be set externally + if (!attrs.getName(i).equals("id")) { + throw be; + } + } + } + } + + /** + * Adds the content of #PCDATA sections to an element. + */ + public static void addText(Project project, Object target, char[] buf, int start, int end) + throws BuildException { + addText(project, target, new String(buf, start, end)); + } + + /** + * Adds the content of #PCDATA sections to an element. + */ + public static void addText(Project project, Object target, String text) + throws BuildException { + + if (text == null ) { + return; + } + + if(target instanceof RoleAdapter) { + target = ((RoleAdapter) target).getProxy(); + } + + IntrospectionHelper.getHelper(target.getClass()).addText(project, target, text); + } + + /** + * Stores a configured child element into its parent object + */ + public static void storeChild(Project project, Object parent, Object child, String tag) { + IntrospectionHelper ih = IntrospectionHelper.getHelper(parent.getClass()); + ih.storeElement(project, parent, child, tag); + } + + /** + * Replace ${} style constructions in the given value with the string value of + * the corresponding data types. + * + * @param value the string to be scanned for property references. + * @since 1.5 + */ + public static String replaceProperties(Project project, String value) + throws BuildException { + return project.replaceProperties(value); + } + + /** + * Replace ${} style constructions in the given value with the string value of + * the corresponding data types. + * + * @param value the string to be scanned for property references. + */ + public static String replaceProperties(Project project, String value, Hashtable keys) + throws BuildException { + if (value == null) { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString(value, fragments, propertyRefs); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while (i.hasMoreElements()) { + String fragment = (String)i.nextElement(); + if (fragment == null) { + String propertyName = (String)j.nextElement(); + if (!keys.containsKey(propertyName)) { + project.log("Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE); + } + fragment = (keys.containsKey(propertyName)) ? (String) keys.get(propertyName) + : "${" + propertyName + "}"; + } + sb.append(fragment); + } + + return sb.toString(); + } + + /** + * This method will parse a string containing ${value} style + * property values into two lists. The first list is a collection + * of text fragments, while the other is a set of string property names + * null entries in the first list indicate a property reference from the + * second list. + */ + public static void parsePropertyString(String value, Vector fragments, Vector propertyRefs) + throws BuildException { + int prev = 0; + int pos; + while ((pos = value.indexOf("$", prev)) >= 0) { + if (pos > 0) { + fragments.addElement(value.substring(prev, pos)); + } + + if( pos == (value.length() - 1)) { + fragments.addElement("$"); + prev = pos + 1; + } + else if (value.charAt(pos + 1) != '{' ) { + fragments.addElement(value.substring(pos + 1, pos + 2)); + prev = pos + 2; + } else { + int endName = value.indexOf('}', pos); + if (endName < 0) { + throw new BuildException("Syntax error in property: " + + value ); + } + String propertyName = value.substring(pos + 2, endName); + fragments.addElement(null); + propertyRefs.addElement(propertyName); + prev = endName + 1; + } + } + + if (prev < value.length()) { + fragments.addElement(value.substring(prev)); + } + } + + private static SAXParserFactory getParserFactory() { + if (parserFactory == null) { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a + * reference to project. + * + *

Moved out of {@link #configure configure} to make it happen + * at parser time.

+ */ + private void configureId(Object target, AttributeList attr) { + String id = attr.getValue("id"); + if (id != null) { + if( target instanceof RoleAdapter ) { + ((RoleAdapter)target).setId(id); + } + project.addReference(id, target); + } + } + +} diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/RoleAdapter.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/RoleAdapter.java index e028ebe92..57c1f62d9 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/RoleAdapter.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/RoleAdapter.java @@ -55,6 +55,11 @@ package org.apache.tools.ant; public interface RoleAdapter { + /** + * Obtain the id in case it is needed. + */ + public void setId(String id); + /** * Set the object being adapted. * @param o the object being adapted diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/SymbolTable.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/SymbolTable.java index e6b8c843b..e009e1438 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/SymbolTable.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/SymbolTable.java @@ -54,6 +54,7 @@ package org.apache.tools.ant; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; @@ -96,8 +97,8 @@ public class SymbolTable { * from that defined in the calling Project. * @param p the calling project */ - public SymbolTable(Project p) { - parentTable = p.getSymbols(); + public SymbolTable(SymbolTable st) { + parentTable = st; } /** @@ -108,6 +109,54 @@ public class SymbolTable { this.project = p; } + /** + * Get the specified loader for the project. + * @param name the name of the loader + * @return the corresponding ANT classloader + */ + private AntClassLoader getLoader(String name) { + AntClassLoader cl = (AntClassLoader) loaders.get(name); + if (cl == null && parentTable != null) { + return parentTable.getLoader(name); + } + return cl; + } + + /** + * Add the specified class-path to a loader. + * If the loader is defined in an ancestor project then a new + * classloader inheritin from the one already existing + * will be created, otherwise the path willbe added to the existing + * ClassLoader. + * @param name the name of the loader to use. + * @param clspath the path to be added to the classloader + */ + public ClassLoader addToLoader(String name, Path clspath) { + // Find if the loader is already defined in the current project + AntClassLoader cl = (AntClassLoader) loaders.get(name); + if (cl == null) { + // Is it inherited from the calling project + if (parentTable != null) { + cl = parentTable.getLoader(name); + } + cl = new AntClassLoader(cl, project, clspath, true); + loaders.put(name, cl); + } + else { + // Add additional path to the existing definition + String[] pathElements = clspath.list(); + for (int i = 0; i < pathElements.length; ++i) { + try { + cl.addPathElement(pathElements[i]); + } + catch (BuildException e) { + // ignore path elements invalid relative to the project + } + } + } + return cl; + } + /** * Find all the roles supported by a Class * on this symbol table. @@ -133,13 +182,13 @@ public class SymbolTable { list.addElement(role); } } - if (parentTable != null) findRoles(clz, list); + if (parentTable != null) parentTable.findRoles(clz, list); } /** * Get the Role definition * @param role the name of the role - * @return the method used to support objects on this role + * @return the Role description */ public Role getRole(String role) { Role r = (Role) roles.get(role); @@ -171,112 +220,6 @@ public class SymbolTable { return (old != null); } - /** - * Verify if the interface is valid. - * @param clz the interface to validate - * @return the method defined by the interface - */ - private Method validInterface(Class clz) { - Method m[] = clz.getDeclaredMethods(); - if (m.length == 1 - && java.lang.Void.TYPE.equals(m[0].getReturnType())) { - Class args[] = m[0].getParameterTypes(); - if (args.length == 1 - && !java.lang.String.class.equals(args[0]) - && !args[0].isArray() - && !args[0].isPrimitive()) { - return m[0]; - } - else { - throw new BuildException("Invalid role interface method in: " - + clz.getName()); - } - } - else { - throw new BuildException("More than one method on role interface"); - } - } - - /** - * Verify if the adapter is valid with respect to the interface. - * @param clz the class adapter to validate - * @param mtd the method whose only argument must match - * @return the static method to use for validating adaptees - */ - private Method validAdapter(Class clz, Method mtd) { - if (clz == null) return null; - - checkClass(clz); - if (!mtd.getParameterTypes()[0].isAssignableFrom(clz)) { - String msg = "Adapter " + clz.getName() + - " is incompatible with role interface " + - mtd.getDeclaringClass().getName(); - throw new BuildException(msg); - } - String msg = "Class " + clz.getName() + " is not an adapter: "; - if (!RoleAdapter.class.isAssignableFrom(clz)) { - throw new BuildException(msg + "does not implement RoleAdapter"); - } - try { - Method chk = clz.getMethod("checkClass", CHECK_ADAPTER_PARAMS); - if (!Modifier.isStatic(chk.getModifiers())) { - throw new BuildException(msg + "checkClass() is not static"); - } - return chk; - } - catch(NoSuchMethodException nme){ - throw new BuildException(msg + "checkClass() not found", nme); - } - } - - /** - * Get the specified loader for the project. - * @param name the name of the loader - * @return the corresponding ANT classloader - */ - private AntClassLoader getLoader(String name) { - AntClassLoader cl = (AntClassLoader) loaders.get(name); - if (cl == null && parentTable != null) { - return parentTable.getLoader(name); - } - return cl; - } - - /** - * Add the specified class-path to a loader. - * If the loader is defined in an ancestor project then a new - * classloader inheritin from the one already existing - * will be created, otherwise the path willbe added to the existing - * ClassLoader. - * @param name the name of the loader to use. - * @param clspath the path to be added to the classloader - */ - public ClassLoader addToLoader(String name, Path clspath) { - // Find if the loader is already defined in the current project - AntClassLoader cl = (AntClassLoader) loaders.get(name); - if (cl == null) { - // Is it inherited from the calling project - if (parentTable != null) { - cl = parentTable.getLoader(name); - } - cl = new AntClassLoader(cl, project, clspath, true); - loaders.put(name, cl); - } - else { - // Add additional path to the existing definition - String[] pathElements = clspath.list(); - for (int i = 0; i < pathElements.length; ++i) { - try { - cl.addPathElement(pathElements[i]); - } - catch (BuildException e) { - // ignore path elements invalid relative to the project - } - } - } - return cl; - } - /** * Add a new type of element to a role. * @param role the role for this Class. @@ -291,13 +234,13 @@ public class SymbolTable { throw new BuildException("Unknown role: " + role); } // Check if it is already defined - Class old = get(role, name); + Factory old = get(role, name); if (old != null) { - if (old.equals(clz)) { + if (old.getOriginalClass().equals(clz)) { project.log("Ignoring override for "+ role + " " + name + ", it is already defined by the same class.", project.MSG_VERBOSE); - return old; + return old.getOriginalClass(); } else { project.log("Trying to override old definition of " + @@ -305,26 +248,33 @@ public class SymbolTable { project.MSG_WARN); } } - checkClass(clz); + Factory f = checkClass(clz); // Check that the Class is compatible with the role definition - r.verifyAdaptability(role, clz); + f = r.verifyAdaptability(role, f); // Record the new type Hashtable defTable = (Hashtable)defs.get(role); if (defTable == null) { defTable = new Hashtable(); defs.put(role, defTable); } - defTable.put(name, clz); - return old; + defTable.put(name, f); + + String msg = + " +User " + role + ": " + name + " " + clz.getName(); + project.log(msg, project.MSG_DEBUG); + return (old != null ? old.getOriginalClass() : null); } /** * Checks a class, whether it is suitable for serving in ANT. + * @return the factory to use when instantiating the class * @throws BuildException and logs as Project.MSG_ERR for * conditions, that will cause execution to fail. */ - void checkClass(final Class clz) + Factory checkClass(final Class clz) // Package on purpose throws BuildException { + if (clz == null) return null; + if(!Modifier.isPublic(clz.getModifiers())) { final String message = clz + " is not public"; project.log(message, Project.MSG_ERR); @@ -342,8 +292,37 @@ public class SymbolTable { // getConstructor finds public constructors only. try { clz.getConstructor(new Class[0]); + return new Factory(){ + public Object create(Project p) { + try { + return clz.newInstance(); + } + catch(Exception e) { + throw new BuildException(e); + } + } + + public Class getOriginalClass() { + return clz; + } + }; } catch (NoSuchMethodException nse) { - clz.getConstructor(new Class[] {Project.class}); + final Constructor c = + clz.getConstructor(new Class[] {Project.class}); + return new Factory(){ + public Object create(Project p) { + try { + return c.newInstance(new Object[]{p}); + } + catch(Exception e) { + throw new BuildException(e); + } + } + + public Class getOriginalClass() { + return clz; + } + }; } } catch(NoSuchMethodException e) { final String message = @@ -359,11 +338,11 @@ public class SymbolTable { * @param name the name of the element to sea * @return the Class implementation */ - public Class get(String role, String name) { + public Factory get(String role, String name) { Hashtable defTable = (Hashtable)defs.get(role); if (defTable != null) { - Class clz = (Class)defTable.get(name); - if (clz != null) return clz; + Factory f = (Factory)defTable.get(name); + if (f != null) return f; } if (parentTable != null) { return parentTable.get(role, name); @@ -372,19 +351,12 @@ public class SymbolTable { } /** - * Get a Hashtable that is usable for manipulating Tasks, + * Get a Hashtable that is usable for manipulating elements on Role. + * @param role the role of the elements in the table * @return a Hashtable that delegates to the Symbol table. */ - public Hashtable getTaskDefinitions() { - return new SymbolHashtable("task"); - } - - /** - * Get a Hashtable that is usable for manipulating Datatypes, - * @return a Hashtable that delegates to the Symbol table. - */ - public Hashtable getDataTypeDefinitions() { - return new SymbolHashtable("datatype"); + Hashtable getDefinitions(String role) { // package scope on purpose + return new SymbolHashtable(role); } /** @@ -402,16 +374,43 @@ public class SymbolTable { } public synchronized Object get(Object key) { - return SymbolTable.this.get(role, (String)key); + Factory f = SymbolTable.this.get(role, (String)key); + return (f == null? null : f.getOriginalClass()); } } + /** + * Factory for creating ANT objects. + * Class objects are not instanciated directly but through a Factory + * which is able to resolve issues such as proxys and such. + */ + public static interface Factory { + /** + * Creates an object for the Role + * @param the project in which it is created + * @return the instantiated object with a proxy if necessary + */ + public Object create(Project p); + + /** + * Creates an object for the Role, adapted if necessary + * for a particular interface. + */ +// public Object adaptFor(Class clz, Project p, Object o); + + /** + * The original class of the object without proxy. + */ + public Class getOriginalClass(); + } + /** * The definition of a role */ public class Role { private Method interfaceMethod; private Method adapterVerifier; + private Factory adapterFactory; /** * Creates a new Role object @@ -420,6 +419,7 @@ public class SymbolTable { */ Role(Class roleClz, Class adapterClz) { interfaceMethod = validInterface(roleClz); + adapterFactory = checkClass(adapterClz); adapterVerifier = validAdapter(adapterClz, interfaceMethod); } @@ -433,12 +433,11 @@ public class SymbolTable { /** * Instantiate a new adapter for this role. */ - public RoleAdapter createAdapter() { - if (adapterVerifier == null) return null; + public RoleAdapter createAdapter(Project p) { + if (adapterFactory == null) return null; try { - return (RoleAdapter) - adapterVerifier.getDeclaringClass().newInstance(); + return (RoleAdapter) adapterFactory.create(p); } catch(BuildException be) { throw be; @@ -451,11 +450,12 @@ public class SymbolTable { /** * Verify if the class can be adapted to use by the role * @param role the name of the role to verify - * @param clz the class to verify + * @param f the factory for the class to verify */ - public void verifyAdaptability(String role, Class clz) { + public Factory verifyAdaptability(String role, final Factory f) { + final Class clz = f.getOriginalClass(); if (interfaceMethod.getParameterTypes()[0].isAssignableFrom(clz)) { - return; + return f; } if (adapterVerifier == null) { String msg = "Class " + clz.getName() + @@ -464,8 +464,18 @@ public class SymbolTable { } try { try { - adapterVerifier.invoke(null, - new Object[]{clz, project}); + adapterVerifier.invoke(null, new Object[]{clz, project}); + return new Factory(){ + public Object create(Project p) { + RoleAdapter ra = createAdapter(p); + ra.setProxy(f.create(p)); + return ra; + } + + public Class getOriginalClass() { + return clz; + } + }; } catch (InvocationTargetException ite) { throw ite.getTargetException(); @@ -487,5 +497,63 @@ public class SymbolTable { public boolean isImplementedBy(Class clz) { return interfaceMethod.getDeclaringClass().isAssignableFrom(clz); } + + /** + * Verify if the interface is valid. + * @param clz the interface to validate + * @return the method defined by the interface + */ + private Method validInterface(Class clz) { + Method m[] = clz.getDeclaredMethods(); + if (m.length == 1 + && java.lang.Void.TYPE.equals(m[0].getReturnType())) { + Class args[] = m[0].getParameterTypes(); + if (args.length == 1 + && !java.lang.String.class.equals(args[0]) + && !args[0].isArray() + && !args[0].isPrimitive()) { + return m[0]; + } + else { + throw new BuildException("Invalid role interface method in: " + + clz.getName()); + } + } + else { + throw new BuildException("More than one method on role interface"); + } + } + + /** + * Verify if the adapter is valid with respect to the interface. + * @param clz the class adapter to validate + * @param mtd the method whose only argument must match + * @return the static method to use for validating adaptees + */ + private Method validAdapter(Class clz, Method mtd) { + if (clz == null) return null; + + if (!mtd.getParameterTypes()[0].isAssignableFrom(clz)) { + String msg = "Adapter " + clz.getName() + + " is incompatible with role interface " + + mtd.getDeclaringClass().getName(); + throw new BuildException(msg); + } + String msg = "Class " + clz.getName() + " is not an adapter: "; + if (!RoleAdapter.class.isAssignableFrom(clz)) { + throw new BuildException(msg + "does not implement RoleAdapter"); + } + try { + Method chk = clz.getMethod("checkClass", CHECK_ADAPTER_PARAMS); + if (!Modifier.isStatic(chk.getModifiers())) { + throw new BuildException(msg + "checkClass() is not static"); + } + return chk; + } + catch(NoSuchMethodException nme){ + throw new BuildException(msg + "checkClass() not found", nme); + } + } + } } diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/TaskAdapter.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/TaskAdapter.java index 213f4f014..d083a92bd 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/TaskAdapter.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/TaskAdapter.java @@ -165,4 +165,5 @@ public class TaskAdapter extends Task implements RoleAdapter { return this.proxy ; } + public void setId(String id) {} } diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Ant.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Ant.java index 13d42380d..5d6d8d699 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Ant.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -138,10 +138,8 @@ public class Ant extends Task { } public void init() { - newProject = new Project(project); + newProject = project.createSubProject(); newProject.setJavaVersionProperty(); -// newProject.addTaskDefinition("property", -// (Class)project.getTaskDefinitions().get("property")); } private void reinit() { @@ -185,26 +183,6 @@ public class Ant extends Task { } } -// Hashtable taskdefs = project.getTaskDefinitions(); -// Enumeration et = taskdefs.keys(); -// while (et.hasMoreElements()) { -// String taskName = (String) et.nextElement(); -// if (taskName.equals("property")) { -// // we have already added this taskdef in #init -// continue; -// } -// Class taskClass = (Class) taskdefs.get(taskName); -// newProject.addTaskDefinition(taskName, taskClass); -// } - -// Hashtable typedefs = project.getDataTypeDefinitions(); -// Enumeration e = typedefs.keys(); -// while (e.hasMoreElements()) { -// String typeName = (String) e.nextElement(); -// Class typeClass = (Class) typedefs.get(typeName); -// newProject.addDataTypeDefinition(typeName, typeClass); -// } - // set user-defined or all properties from calling project Hashtable prop1; if (inheritAll) { diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antlib.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antlib.java index c084791c4..924b81315 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antlib.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antlib.java @@ -418,7 +418,7 @@ public class Antlib extends Task { if (classpath != null) { clspath.append(classpath); } - return project.getSymbols().addToLoader(loaderId, clspath); + return project.addToLoader(loaderId, clspath); } @@ -505,8 +505,6 @@ public class Antlib extends Task { private int level = 0; - private SymbolTable symbols = null; - private String name = null; private String className = null; private String adapter = null; @@ -520,7 +518,6 @@ public class Antlib extends Task { AntLibraryHandler(ClassLoader classloader, Properties als) { this.classloader = classloader; this.aliasMap = als; - this.symbols = project.getSymbols(); } /** @@ -591,15 +588,15 @@ public class Antlib extends Task { try { if ("role".equals(tag)) { - if (isRoleInUse(name)) { + if (project.isRoleDefined(name)) { String msg = "Cannot override role: " + name; log(msg, Project.MSG_WARN); return; } // Defining a new role - symbols.addRole(name, loadClass(className), - (adapter == null? - null : loadClass(adapter))); + project.addRoleDefinition(name, loadClass(className), + (adapter == null? + null : loadClass(adapter))); return; } @@ -610,12 +607,12 @@ public class Antlib extends Task { name = alias; } //catch an attempted override of an existing name - if (!override && isInUse(tag, name)) { + if (!override && project.isDefinedOnRole(tag, name)) { String msg = "Cannot override " + tag + ": " + name; log(msg, Project.MSG_WARN); return; } - symbols.add(tag, name, loadClass(className)); + project.addDefinitionOnRole(tag, name, loadClass(className)); } catch(BuildException be) { throw new SAXParseException(be.getMessage(), locator, be); @@ -651,26 +648,6 @@ public class Antlib extends Task { } } - /** - * test for a name being in use already on this role - * - * @param name the name to test - * @return true if it is a task or a datatype - */ - private boolean isInUse(String role, String name) { - return (symbols.get(role, name) != null); - } - - /** - * test for a role name being in use already - * - * @param name the name to test - * @return true if it is a task or a datatype - */ - private boolean isRoleInUse(String name) { - return (symbols.getRole(name) != null); - } - //end inner class AntLibraryHandler } diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/types/DataTypeAdapterTask.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/types/DataTypeAdapterTask.java index 18dc7c542..4669a0789 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/types/DataTypeAdapterTask.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/types/DataTypeAdapterTask.java @@ -66,6 +66,7 @@ import org.apache.tools.ant.*; public class DataTypeAdapterTask extends Task implements RoleAdapter { Object proxy; + String id = null; /** * Checks a class, whether it is suitable to be adapted. @@ -83,14 +84,27 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { * Do the execution. */ public void execute() throws BuildException { + if (id != null) { + // Need to re-register this reference + // The container has register the Adapter instead + project.addReference(id, proxy); + } + } + + /** + * Propagate configuration of Project + */ + public void setProject(Project p) { + super.setProject(p); + // Check to see if the DataType has a setProject method to set if (proxy instanceof ProjectComponent) { - ((ProjectComponent)proxy).setProject(project); + ((ProjectComponent)proxy).setProject(p); return; } // This may not be needed - // We are trying to set project even it is was not declared + // We are trying to set project even if is was not declared // just like TaskAdapter does for beans, this is not done // by the original code Method setProjectM = null; @@ -99,7 +113,7 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { setProjectM = c.getMethod( "setProject", new Class[] {Project.class}); if(setProjectM != null) { - setProjectM.invoke(proxy, new Object[] {project}); + setProjectM.invoke(proxy, new Object[] {p}); } } catch (NoSuchMethodException e) { // ignore this if the class being used as a task does not have @@ -122,4 +136,8 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { return this.proxy ; } + public void setId(String id) { + log("Setting adapter id to: " + id, Project.MSG_DEBUG); + this.id = id; + } } diff --git a/proposal/sandbox/antlib/src/testcases/build.xml b/proposal/sandbox/antlib/src/testcases/build.xml new file mode 100644 index 000000000..e0f34f241 --- /dev/null +++ b/proposal/sandbox/antlib/src/testcases/build.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proposal/sandbox/antlib/src/testcases/case-antlib.xml b/proposal/sandbox/antlib/src/testcases/case-antlib.xml new file mode 100644 index 000000000..e5ae1e847 --- /dev/null +++ b/proposal/sandbox/antlib/src/testcases/case-antlib.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/proposal/sandbox/antlib/src/testcases/case.xml b/proposal/sandbox/antlib/src/testcases/case.xml new file mode 100644 index 000000000..b289106d5 --- /dev/null +++ b/proposal/sandbox/antlib/src/testcases/case.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proposal/sandbox/antlib/src/testcases/org/apache/ant/contrib/Case.java b/proposal/sandbox/antlib/src/testcases/org/apache/ant/contrib/Case.java new file mode 100644 index 000000000..e2363eb71 --- /dev/null +++ b/proposal/sandbox/antlib/src/testcases/org/apache/ant/contrib/Case.java @@ -0,0 +1,169 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", 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. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.ant.contrib; + +import java.io.*; +import java.util.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.types.*; + +/** + * Will set one of the given properties depending on the result of testing + * the value of another property. + * + * + * The name of the property to test + * + * The value to compare and set prop. + * The name of the property to set + * + * The name of the property to set otherwise + * The value to set; default "true". + * + * @author Jose Alberto Fernandez jfernandez@viquity.com + */ + +public class Case extends Task { + + public class When { + private String property; + private String value; + + public void setProperty(String name) { + property = name; + } + + public String getProperty() { + return property; + } + + public void setValue(String val) { + value = val; + } + + public String getValue() { + return value; + } + + public boolean tryCase(String caseValue) throws BuildException { + if (property == null) + throw new BuildException("Property attribute is mandatory"); + + if (value == null) + throw new BuildException("Value attribute is mandatory"); + + if (!value.equals(caseValue)) return false; + + if (getProject().getProperty(property) == null) { + getProject().setProperty(property, value); + } else { + log("Override ignored for " + property, Project.MSG_VERBOSE); + } + return true; + } + + public void doElse() throws BuildException { + if (property == null) + throw new BuildException("Property attribute is mandatory"); + + String elseValue = (value == null) ? "true" : value; + + if (getProject().getProperty(property) == null) { + getProject().setProperty(property, elseValue); + } else { + log("Override ignored for " + property, Project.MSG_VERBOSE); + } + } + } + + private String caseProperty; + private Vector whenList = new Vector(); + private When elseCase = null; + + public When createWhen() throws BuildException { + When w = new When(); + whenList.addElement(w); + return w; + } + + public When createElse() throws BuildException { + if (elseCase != null) + throw new BuildException("Only one else element allowed per case"); + + return (elseCase = new When()); + } + + public void setProperty(String property) { + this.caseProperty = property; + } + + public void execute() throws BuildException { + if (caseProperty == null) { + throw new BuildException("property attribute is required", + location); + } + + String caseValue = getProject().getProperty(caseProperty); + + for (Enumeration e = whenList.elements(); e.hasMoreElements(); ) { + When w = (When)e.nextElement(); + if (w.tryCase(caseValue)) return; + } + + if (elseCase != null) + elseCase.doElse(); + } + +}