diff --git a/proposal/sandbox/antlib/build.xml b/proposal/sandbox/antlib/build.xml new file mode 100644 index 000000000..c887b2375 --- /dev/null +++ b/proposal/sandbox/antlib/build.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/proposal/sandbox/antlib/docs/manual/CoreTasks/antjar.html b/proposal/sandbox/antlib/docs/manual/CoreTasks/antjar.html index 57f20fe0d..ee6a4a130 100644 --- a/proposal/sandbox/antlib/docs/manual/CoreTasks/antjar.html +++ b/proposal/sandbox/antlib/docs/manual/CoreTasks/antjar.html @@ -12,41 +12,50 @@

An extension of the Jar task with special treatment for the library descriptor file that should end up in the META-INF directory of the Ant Archive.

-

This task validates the provided library descriptor making certain -it specifies the following SYSTEM ID: -"http://jakarta.apache.org/ant/AntlibV1_0.dtd". -This DTD is defined as follows:

+

+Descriptors must follow the following rules, although there is no fix DTD +for them:

-<?xml version='1.0' encoding="UTF8" ?>
-
-<!-- 
-This file defines the XML format for ANT library descriptors.
-Descriptors must especify a DOCTYPE of 
-"http://jakarta.apache.org/ant/Antlib-V1_0.dtd"
-as the SystemId for the document.
--->
 
 <!-- Root element for the Antlib descriptor.                 -->
-<!ELEMENT antlib (task | type)* >
-<!ATTLIST antlib
-          version  CDATA #IMPLIED
->
+<!ELEMENT antlib (role | rolename)* >
 
-<!-- Declaration of tasks contained in the library.          -->
-<!ELEMENT task EMPTY>
-<!ATTLIST task 
+<!-- Declaration of roles contained in the library.          -->
+<!ELEMENT role EMPTY>
+<!ATTLIST role 
           name     CDATA #REQUIRED
           class    CDATA #REQUIRED
+          proxy    CDATA #IMPLIED
 >
 
 <!-- Declaration of datatypes contained in the library       -->
-<!ELEMENT type EMPTY>
-<!ATTLIST type 
+<!ELEMENT rolename EMPTY>
+<!ATTLIST rolename 
           name     CDATA #REQUIRED
           class    CDATA #REQUIRED
 >
 
 
+There are two predefined roles: task and datatype. +

+

Role definition

+The name of the role. This name is used when specifying +elements for this role. +

+The class defining a role must be an interface containing a +unique void method with only one argument whose type is the that of +elements declared on the role. +

+The proxy defined in a role specifies a class that can be used +to bridge between the type expected by the role and the type of +elements declared for that role. +

Element definition

+Any element whose name is that of a role declares an element for that role. +

+The name defined the name of the element to use in the buildfile +to specify the element being declared. +

+The class the class defining the element.

Parameters

@@ -78,7 +87,6 @@ in META-INF/antlib.xml.

Here is a sample META-INF/antlib.xml:

 <?xml version="1.0" encoding="UTF8" ?>
-<!DOCTYPE antlib  SYSTEM "http://jakarta.apache.org/ant/Antlib-V1_0.dtd" >
 
 <antlib version="1.0" >
   <task name="case" class="org.apache.ant.contrib.Case" />
diff --git a/proposal/sandbox/antlib/docs/manual/CoreTasks/antlib.html b/proposal/sandbox/antlib/docs/manual/CoreTasks/antlib.html
index ec1c7c83b..ca4c31836 100644
--- a/proposal/sandbox/antlib/docs/manual/CoreTasks/antlib.html
+++ b/proposal/sandbox/antlib/docs/manual/CoreTasks/antlib.html
@@ -9,16 +9,38 @@
 
 

AntLib

Description

-

Defines and loads any tasks and datatypes contained in an ANT library.

+

Defines and loads elements contained in an ANT library.

It also allows the aliasing of the names being defined in order to avoid collisions and provides means to override definitions with the ones defined in the library.

-Ant libraries can be loaded in the current classloader, which is more efficient, +Ant libraries are associated with ClassLoaders identified by the +loaderid attribute. If no loader is specified a default loader +will be used. Ant libraries specifying the same loader are loaded by the +same ClassLoader as long as the libraries are defined on the same project. +Classloaders with the same ID in a subproject have the corresponding +classloader in the parent project as their parent classloader. +

+Ant libraries can be loaded in the current classloader, +which is more efficient, but requires the tasks to be in the path already (such as in the ant lib directory) - set useCurrentClassloader to true to enable this. It is also possible to add more libraries to the path, such as any libraries the task is dependent on. - +

+Ant libraries define objects of several types: +

    +
  1. Roles: Define an interface to be implemented by elements +(usually tasks) that accept subelements of the specified role. +Roles may also define a proxy class which may be applied to an element +in order to make it compatible with the role. +
  2. +
  3. Tasks: define elements that belong to the predefined +role "task". +
  4. Data types: define elements that belong to the predefined role +"datatype". +
  5. Other role elements: declare elements for other roles that +have been previously defined. +

Parameters

@@ -33,11 +55,13 @@ libraries the task is dependent on. - + - @@ -59,12 +83,20 @@ libraries the task is dependent on. + + + + +
libraryThe name of a library relative to ${ant.home}/lib.The name of a library relative to ${ant.home}/antlib.
overrideReplace any existing definition with the same name. ("true"/"false"). When "false" already defined tasks + Replace any existing definition with the same name. + ("true"/"false"). + When "false" already defined tasks and datatytes take precedence over those in the library. Default is "false" when omitted. No No
loaderidThe ID of the ClassLoader to use to load the classes + defined in this library. If omitted a default per project ClassLoader + will be used. + No

Parameters specified as nested elements

alias

Specifies the usage of a different name from that defined in the library -descriptor.

+descriptor. Applies only to element definitions (not role declarations).

@@ -85,10 +117,9 @@ descriptor.

Specifies the usage of a different name from that defined in the library descriptor. This is used to deal with name clashes

-

classpath

classpath

-A classpath of extra libraries to import to support this task. +A classpath of extra libraries to import to support this library.

classpathref

A reference to an existing classpath. 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 new file mode 100644 index 000000000..7ffdd1849 --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java @@ -0,0 +1,1342 @@ +/* + * 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.InputStream; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Properties; +import java.util.Enumeration; +import java.util.Stack; +import java.lang.reflect.Modifier; + + +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; + +/** + * Central representation of an Ant project. This class defines a + * Ant project with all of it's targets and tasks. It also provides + * the mechanism to kick off a build using a particular target name. + *

+ * This class also encapsulates methods which allow Files to be refered + * to using abstract path names which are translated to native system + * file paths at runtime as well as defining various project properties. + * + * @author duncan@x180.com + */ + +public class Project { + + public final static int MSG_ERR = 0; + public final static int MSG_WARN = 1; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_DEBUG = 4; + + // private set of constants to represent the state + // of a DFS of the Target dependencies + private final static String VISITING = "VISITING"; + private final static String VISITED = "VISITED"; + + private static String javaVersion; + + public final static String JAVA_1_0 = "1.0"; + public final static String JAVA_1_1 = "1.1"; + public final static String JAVA_1_2 = "1.2"; + public final static String JAVA_1_3 = "1.3"; + public final static String JAVA_1_4 = "1.4"; + + public final static String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + public final static String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + private String name; + private String description; + + private Hashtable properties = new Hashtable(); + private Hashtable userProperties = new Hashtable(); + private Hashtable references = new Hashtable(); + private String defaultTarget; + // private Hashtable dataClassDefinitions = new Hashtable(); + // private Hashtable taskClassDefinitions = new Hashtable(); + private Hashtable createdTasks = new Hashtable(); + private Hashtable targets = new Hashtable(); + private FilterSet globalFilterSet = new FilterSet(); + private FilterSetCollection globalFilters = new FilterSetCollection(globalFilterSet); + private File baseDir; + + private Vector listeners = new Vector(); + + /** The Ant core classloader - may be null if using system loader */ + private ClassLoader coreLoader = null; + + /** Records the latest task on a thread */ + private Hashtable threadTasks = new Hashtable(); + + /** Store symbol tables */ + private SymbolTable symbols; + + static { + + // Determine the Java version by looking at available classes + // java.lang.StrictMath was introduced in JDK 1.3 + // java.lang.ThreadLocal was introduced in JDK 1.2 + // java.lang.Void was introduced in JDK 1.1 + // Count up version until a NoClassDefFoundError ends the try + + try { + javaVersion = JAVA_1_0; + Class.forName("java.lang.Void"); + javaVersion = JAVA_1_1; + Class.forName("java.lang.ThreadLocal"); + javaVersion = JAVA_1_2; + Class.forName("java.lang.StrictMath"); + javaVersion = JAVA_1_3; + Class.forName("java.lang.CharSequence"); + javaVersion = JAVA_1_4; + } catch (ClassNotFoundException cnfe) { + // swallow as we've hit the max class version that + // we have + } + } + + private FileUtils fileUtils; + + /** + * create a new ant project + */ + public Project() { + fileUtils = FileUtils.newFileUtils(); + symbols = new SymbolTable(); + symbols.setProject(this); + } + + /** + * create a new ant project that inherits from caler project + * @param p the calling project + */ + public Project(Project p) { + fileUtils = FileUtils.newFileUtils(); + symbols = new SymbolTable(p); + symbols.setProject(this); + } + + /** + * Initialise the project. + * + * This involves setting the default task definitions and loading the + * system properties. + */ + public void init() throws BuildException { + setJavaVersionProperty(); + + // Initialize simbol table just in case + symbols.addRole("task", TaskContainer.class, TaskAdapter.class); + symbols.addRole("datatype", TaskContainer.class, + DataTypeAdapterTask.class); + + 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(); + + Enumeration enum = props.propertyNames(); + while (enum.hasMoreElements()) { + String key = (String) enum.nextElement(); + String value = props.getProperty(key); + try { + Class taskClass = Class.forName(value); + addTaskDefinition(key, taskClass); + } catch (NoClassDefFoundError ncdfe) { + log("Could not load a dependent class (" + + ncdfe.getMessage() + ") for task " + key, MSG_DEBUG); + } catch (ClassNotFoundException cnfe) { + log("Could not load class (" + + value + ") for task " + key, MSG_DEBUG); + } + } + } 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(); + + Enumeration enum = props.propertyNames(); + while (enum.hasMoreElements()) { + String key = (String) enum.nextElement(); + String value = props.getProperty(key); + try { + Class dataClass = Class.forName(value); + addDataTypeDefinition(key, dataClass); + } catch (NoClassDefFoundError ncdfe) { + // ignore... + } catch (ClassNotFoundException cnfe) { + // ignore... + } + } + } catch (IOException ioe) { + throw new BuildException("Can't load default datatype list"); + } + + setSystemProperties(); + } + + public void setCoreLoader(ClassLoader coreLoader) { + this.coreLoader = coreLoader; + } + + public ClassLoader getCoreLoader() { + return coreLoader; + } + + public void addBuildListener(BuildListener listener) { + listeners.addElement(listener); + } + + public void removeBuildListener(BuildListener listener) { + listeners.removeElement(listener); + } + + public Vector getBuildListeners() { + return listeners; + } + + /** + * Get the symbols associated with this project. + */ + public SymbolTable getSymbols() { + return symbols; + } + + /** + * Output a message to the log with the default log level + * of MSG_INFO + * @param msg text to log + */ + + public void log(String msg) { + log(msg, MSG_INFO); + } + + /** + * Output a message to the log with the given log level + * and an event scope of project + * @param msg text to log + * @param msgLevel level to log at + */ + public void log(String msg, int msgLevel) { + fireMessageLogged(this, msg, msgLevel); + } + + /** + * Output a message to the log with the given log level + * and an event scope of a task + * @param task task to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log(Task task, String msg, int msgLevel) { + fireMessageLogged(task, msg, msgLevel); + } + + /** + * Output a message to the log with the given log level + * and an event scope of a target + * @param target target to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log(Target target, String msg, int msgLevel) { + fireMessageLogged(target, msg, msgLevel); + } + + + public FilterSet getGlobalFilterSet() { + return globalFilterSet; + } + + /** + * set a property. Any existing property of the same name + * is overwritten, unless it is a user property. + * @param name name of property + * @param value new value of the property + */ + public void setProperty(String name, String value) { + // command line properties take precedence + if (null != userProperties.get(name)) { + log("Override ignored for user property " + name, MSG_VERBOSE); + return; + } + + if (null != properties.get(name)) { + log("Overriding previous definition of property " + name, + MSG_VERBOSE); + } + + log("Setting project property: " + name + " -> " + + value, MSG_DEBUG); + properties.put(name, value); + } + + /** + * set a property. An existing property of the same name + * will not be overwritten. + * @param name name of property + * @param value new value of the property + * @since 1.5 + */ + public void setNewProperty(String name, String value) { + if (null != properties.get(name)) { + log("Override ignored for property " + name, MSG_VERBOSE); + return; + } + log("Setting project property: " + name + " -> " + + value, MSG_DEBUG); + properties.put(name, value); + } + + /** + * set a user property, which can not be overwritten by + * set/unset property calls + * @see #setProperty(String,String) + */ + public void setUserProperty(String name, String value) { + log("Setting ro project property: " + name + " -> " + + value, MSG_DEBUG); + userProperties.put(name, value); + properties.put(name, value); + } + + /** + * Allows Project and subclasses to set a property unless its + * already defined as a user property. There are a few cases + * internally to Project that need to do this currently. + */ + private void setPropertyInternal(String name, String value) { + if (null != userProperties.get(name)) { + return; + } + properties.put(name, value); + } + + /** + * query a property. + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getProperty(String name) { + if (name == null) { + return null; + } + String property = (String) properties.get(name); + return property; + } + + /** + * 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 String replaceProperties(String value) + throws BuildException { + return ProjectHelper.replaceProperties(this, value, properties); + } + + /** + * query a user property. + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getUserProperty(String name) { + if (name == null) { + return null; + } + String property = (String) userProperties.get(name); + return property; + } + + /** + * get a copy of the property hashtable + * @return the hashtable containing all properties, user included + */ + public Hashtable getProperties() { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = properties.keys(); + while (e.hasMoreElements()) { + Object name = e.nextElement(); + Object value = properties.get(name); + propertiesCopy.put(name, value); + } + + return propertiesCopy; + } + + /** + * get a copy of the user property hashtable + * @return the hashtable user properties only + */ + public Hashtable getUserProperties() { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = userProperties.keys(); + while (e.hasMoreElements()) { + Object name = e.nextElement(); + Object value = properties.get(name); + propertiesCopy.put(name, value); + } + + return propertiesCopy; + } + + /** + * set the default target of the project + * @deprecated, use setDefault + * @see #setDefault(String) + */ + public void setDefaultTarget(String defaultTarget) { + this.defaultTarget = defaultTarget; + } + + /** + * get the default target of the project + * @return default target or null + */ + public String getDefaultTarget() { + return defaultTarget; + } + + + /** + * set the default target of the project + * XML attribute name. + */ + public void setDefault(String defaultTarget) { + this.defaultTarget = defaultTarget; + } + + /** + * ant xml property. Set the project name as + * an attribute of this class, and of the property + * ant.project.name + */ + public void setName(String name) { + setUserProperty("ant.project.name", name); + this.name = name; + } + + /** get the project name + * @return name string + */ + public String getName() { + return name; + } + + /** set the project description + * @param description text + */ + public void setDescription(String description) { + this.description = description; + } + + /** get the project description + * @return description or null if no description has been set + */ + public String getDescription() { + return description; + } + + /** @deprecated */ + public void addFilter(String token, String value) { + if (token == null) { + return; + } + + globalFilterSet.addFilter(new FilterSet.Filter(token, value)); + } + + /** @deprecated */ + public Hashtable getFilters() { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + /** + * match basedir attribute in xml + * @param baseD project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBasedir(String baseD) throws BuildException { + setBaseDir(new File(baseD)); + } + + /** + * set the base directory; XML attribute. + * checks for the directory existing and being a directory type + * @param baseDir project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBaseDir(File baseDir) throws BuildException { + baseDir = fileUtils.normalize(baseDir.getAbsolutePath()); + if (!baseDir.exists()) { + throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " does not exist"); + } + if (!baseDir.isDirectory()) { + throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " is not a directory"); + } + this.baseDir = baseDir; + setPropertyInternal( "basedir", this.baseDir.getPath()); + String msg = "Project base dir set to: " + this.baseDir; + log(msg, MSG_VERBOSE); + } + + /** + * get the base directory of the project as a file object + * @return the base directory. If this is null, then the base + * dir is not valid + */ + public File getBaseDir() { + if (baseDir == null) { + try { + setBasedir("."); + } catch (BuildException ex) { + ex.printStackTrace(); + } + } + return baseDir; + } + + /** + * static query of the java version + * @return something like "1.1" or "1.3" + */ + public static String getJavaVersion() { + return javaVersion; + } + + /** + * set the ant.java.version property, also tests for + * unsupported JVM versions, prints the verbose log messages + * @throws BuildException if this Java version is not supported + */ + public void setJavaVersionProperty() throws BuildException { + setPropertyInternal("ant.java.version", javaVersion); + + // sanity check + if (javaVersion == JAVA_1_0) { + throw new BuildException("Ant cannot work on Java 1.0"); + } + + log("Detected Java version: " + javaVersion + " in: " + System.getProperty("java.home"), MSG_VERBOSE); + + log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE); + } + + /** + * turn all the system properties into ant properties. + * user properties still override these values + */ + public void setSystemProperties() { + Properties systemP = System.getProperties(); + Enumeration e = systemP.keys(); + while (e.hasMoreElements()) { + Object name = e.nextElement(); + String value = systemP.get(name).toString(); + this.setPropertyInternal(name.toString(), value); + } + } + + /** + * add a new task definition, complain if there is an overwrite attempt + * @param taskName name of the task + * @param taskClass full task classname + * @throws BuildException and logs as Project.MSG_ERR for + * conditions, that will cause the task execution to fail. + */ + 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); + } + + /** + * 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. + */ + public void checkTaskClass(final Class taskClass) throws BuildException { + if( !Task.class.isAssignableFrom(taskClass) ) { + TaskAdapter.checkTaskClass(taskClass, this); + } + } + + /** + * get the current task definition hashtable + */ + public Hashtable getTaskDefinitions() { + return symbols.getTaskDefinitions(); + } + + /** + * add a new datatype + * @param typeName name of the datatype + * @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); + } + + /** + * get the current task definition hashtable + */ + public Hashtable getDataTypeDefinitions() { + return symbols.getDataTypeDefinitions(); + } + + /** + * This call expects to add a new Target. + * @param target is the Target to be added to the current + * Project. + * @exception BuildException if the Target already exists + * in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget(Target target) { + String name = target.getName(); + if (targets.get(name) != null) { + throw new BuildException("Duplicate target: `"+name+"'"); + } + addOrReplaceTarget(name, target); + } + + /** + * This call expects to add a new Target. + * @param target is the Target to be added to the current + * Project. + * @param targetName is the name to use for the Target + * @exception BuildException if the Target already exists + * in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget(String targetName, Target target) + throws BuildException { + if (targets.get(targetName) != null) { + throw new BuildException("Duplicate target: `"+targetName+"'"); + } + addOrReplaceTarget(targetName, target); + } + + /** + * @param target is the Target to be added or replaced in + * the current Project. + */ + public void addOrReplaceTarget(Target target) { + addOrReplaceTarget(target.getName(), target); + } + + /** + * @param target is the Target to be added/replaced in + * the current Project. + * @param targetName is the name to use for the Target + */ + public void addOrReplaceTarget(String targetName, Target target) { + String msg = " +Target: " + targetName; + log(msg, MSG_DEBUG); + target.setProject(this); + targets.put(targetName, target); + } + + /** + * get the target hashtable + * @return hashtable, the contents of which can be cast to Target + */ + public Hashtable getTargets() { + return targets; + } + + /** + * create a new task instance + * @param taskType name of the task + * @throws BuildException when task creation goes bad + * @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); + } + } + + /** + * Keep a record of all tasks that have been created so that they + * can be invalidated if a taskdef overrides the definition. + */ + private void addCreatedTask(String type, Task task) { + synchronized (createdTasks) { + Vector v = (Vector) createdTasks.get(type); + if (v == null) { + v = new Vector(); + createdTasks.put(type, v); + } + v.addElement(task); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type + * for a given taskname. + */ + private void invalidateCreatedTasks(String type) { + synchronized (createdTasks) { + Vector v = (Vector) createdTasks.get(type); + if (v != null) { + Enumeration enum = v.elements(); + while (enum.hasMoreElements()) { + Task t = (Task) enum.nextElement(); + t.markInvalid(); + } + v.removeAllElements(); + createdTasks.remove(type); + } + } + } + + /** + * create a new DataType instance + * @param typeName name of the datatype + * @throws BuildException when datatype creation goes bad + * @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); + } + } + + /** + * execute the sequence of targets, and the targets they depend on + * @param Vector a vector of target name strings + * @throws BuildException if the build failed + */ + public void executeTargets(Vector targetNames) throws BuildException { + Throwable error = null; + + for (int i = 0; i < targetNames.size(); i++) { + executeTarget((String)targetNames.elementAt(i)); + } + } + + public void demuxOutput(String line, boolean isError) { + Task task = (Task)threadTasks.get(Thread.currentThread()); + if (task == null) { + fireMessageLogged(this, line, isError ? MSG_ERR : MSG_INFO); + } + else { + if (isError) { + task.handleErrorOutput(line); + } + else { + task.handleOutput(line); + } + } + } + + /** + * execute the targets and any targets it depends on + * @param targetName the target to execute + * @throws BuildException if the build failed + */ + public void executeTarget(String targetName) throws BuildException { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if (targetName == null) { + String msg = "No target specified"; + throw new BuildException(msg); + } + + // Sort the dependency tree, and run everything from the + // beginning until we hit our targetName. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + Vector sortedTargets = topoSort(targetName, targets); + + int curidx = 0; + Target curtarget; + + do { + curtarget = (Target) sortedTargets.elementAt(curidx++); + curtarget.performTasks(); + } while (!curtarget.getName().equals(targetName)); + } + + /** + * Return the canonical form of fileName as an absolute path. + * + *

If fileName is a relative file name, resolve it relative to + * rootDir.

+ * + * @deprecated + */ + public File resolveFile(String fileName, File rootDir) { + return fileUtils.resolveFile(rootDir, fileName); + } + + public File resolveFile(String fileName) { + return fileUtils.resolveFile(baseDir, fileName); + } + + /** + * Translate a path into its native (platform specific) format. + *

+ * This method uses the PathTokenizer class to separate the input path + * into its components. This handles DOS style paths in a relatively + * sensible way. The file separators are then converted to their platform + * specific versions. + * + * @param to_process the path to be converted + * + * @return the native version of to_process or + * an empty string if to_process is null or empty + */ + public static String translatePath(String to_process) { + if ( to_process == null || to_process.length() == 0 ) { + return ""; + } + + StringBuffer path = new StringBuffer(to_process.length() + 50); + PathTokenizer tokenizer = new PathTokenizer(to_process); + while (tokenizer.hasMoreTokens()) { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace('/', File.separatorChar); + pathComponent = pathComponent.replace('\\', File.separatorChar); + if (path.length() != 0) { + path.append(File.pathSeparatorChar); + } + path.append(pathComponent); + } + + return path.toString(); + } + + /** + * Convienence method to copy a file from a source to a destination. + * No filtering is performed. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(String sourceFile, String destFile) throws IOException { + fileUtils.copyFile(sourceFile, destFile); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(String sourceFile, String destFile, boolean filtering) + throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null); + } + + /** + * Convienence method to copy a file from a source to a + * destination specifying if token filtering must be used and if + * source files may overwrite newer destination files. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(String sourceFile, String destFile, boolean filtering, + boolean overwrite) throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null, overwrite); + } + + /** + * Convienence method to copy a file from a source to a + * destination specifying if token filtering must be used, if + * source files may overwrite newer destination files and the + * last modified time of destFile file should be made equal + * to the last modified time of sourceFile. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(String sourceFile, String destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified) + throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified); + } + + /** + * Convienence method to copy a file from a source to a destination. + * No filtering is performed. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(File sourceFile, File destFile) throws IOException { + fileUtils.copyFile(sourceFile, destFile); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(File sourceFile, File destFile, boolean filtering) + throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null); + } + + /** + * Convienence method to copy a file from a source to a + * destination specifying if token filtering must be used and if + * source files may overwrite newer destination files. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(File sourceFile, File destFile, boolean filtering, + boolean overwrite) throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null, overwrite); + } + + /** + * Convienence method to copy a file from a source to a + * destination specifying if token filtering must be used, if + * source files may overwrite newer destination files and the + * last modified time of destFile file should be made equal + * to the last modified time of sourceFile. + * + * @throws IOException + * + * @deprecated + */ + public void copyFile(File sourceFile, File destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified) + throws IOException { + fileUtils.copyFile(sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified); + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @deprecated + */ + public void setFileLastModified(File file, long time) throws BuildException { + if (getJavaVersion() == JAVA_1_1) { + log("Cannot change the modification time of " + file + + " in JDK 1.1", Project.MSG_WARN); + return; + } + fileUtils.setFileLastModified(file, time); + log("Setting modification time for " + file, MSG_VERBOSE); + } + + /** + * returns the boolean equivalent of a string, which is considered true + * if either "on", "true", or "yes" is found, ignoring case. + */ + public static boolean toBoolean(String s) { + return (s.equalsIgnoreCase("on") || + s.equalsIgnoreCase("true") || + s.equalsIgnoreCase("yes")); + } + + /** + * Topologically sort a set of Targets. + * @param root is the (String) name of the root Target. The sort is + * created in such a way that the sequence of Targets uptil the root + * target is the minimum possible such sequence. + * @param targets is a Hashtable representing a "name to Target" mapping + * @return a Vector of Strings with the names of the targets in + * sorted order. + * @exception BuildException if there is a cyclic dependency among the + * Targets, or if a Target does not exist. + */ + public final Vector topoSort(String root, Hashtable targets) + throws BuildException { + Vector ret = new Vector(); + Hashtable state = new Hashtable(); + Stack visiting = new Stack(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + tsort(root, targets, state, visiting, ret); + log("Build sequence for target `"+root+"' is "+ret, MSG_VERBOSE); + for (Enumeration en=targets.keys(); en.hasMoreElements();) { + String curTarget = (String)(en.nextElement()); + String st = (String) state.get(curTarget); + if (st == null) { + tsort(curTarget, targets, state, visiting, ret); + } + else if (st == VISITING) { + throw new RuntimeException("Unexpected node in visiting state: "+curTarget); + } + } + log("Complete build sequence is "+ret, MSG_VERBOSE); + return ret; + } + + // one step in a recursive DFS traversal of the Target dependency tree. + // - The Hashtable "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The Stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + private final void tsort(String root, Hashtable targets, + Hashtable state, Stack visiting, + Vector ret) + throws BuildException { + state.put(root, VISITING); + visiting.push(root); + + Target target = (Target)(targets.get(root)); + + // Make sure we exist + if (target == null) { + StringBuffer sb = new StringBuffer("Target `"); + sb.append(root); + sb.append("' does not exist in this project. "); + visiting.pop(); + if (!visiting.empty()) { + String parent = (String)visiting.peek(); + sb.append("It is used from target `"); + sb.append(parent); + sb.append("'."); + } + + throw new BuildException(new String(sb)); + } + + for (Enumeration en=target.getDependencies(); en.hasMoreElements();) { + String cur = (String) en.nextElement(); + String m=(String)state.get(cur); + if (m == null) { + // Not been visited + tsort(cur, targets, state, visiting, ret); + } + else if (m == VISITING) { + // Currently visiting this node, so have a cycle + throw makeCircularException(cur, visiting); + } + } + + String p = (String) visiting.pop(); + if (root != p) { + throw new RuntimeException("Unexpected internal error: expected to pop "+root+" but got "+p); + } + state.put(root, VISITED); + ret.addElement(target); + } + + private static BuildException makeCircularException(String end, Stack stk) { + StringBuffer sb = new StringBuffer("Circular dependency: "); + sb.append(end); + String c; + do { + c = (String)stk.pop(); + sb.append(" <- "); + sb.append(c); + } while(!c.equals(end)); + return new BuildException(new String(sb)); + } + + public void addReference(String name, Object value) { + if (null != references.get(name)) { + log("Overriding previous definition of reference to " + name, + MSG_WARN); + } + log("Adding reference: " + name + " -> " + value, MSG_DEBUG); + references.put(name,value); + } + + public Hashtable getReferences() { + return references; + } + + /** + * @return The object with the "id" key. + */ + public Object getReference(String key) { + return references.get(key); + } + + /** + * send build started event to the listeners + */ + protected void fireBuildStarted() { + BuildEvent event = new BuildEvent(this); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.buildStarted(event); + } + } + + /** + * send build finished event to the listeners + * @param exception exception which indicates failure if not null + */ + protected void fireBuildFinished(Throwable exception) { + BuildEvent event = new BuildEvent(this); + event.setException(exception); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.buildFinished(event); + } + } + + + /** + * send target started event to the listeners + */ + protected void fireTargetStarted(Target target) { + BuildEvent event = new BuildEvent(target); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.targetStarted(event); + } + } + + /** + * send build finished event to the listeners + * @param exception exception which indicates failure if not null + */ + protected void fireTargetFinished(Target target, Throwable exception) { + BuildEvent event = new BuildEvent(target); + event.setException(exception); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.targetFinished(event); + } + } + + protected void fireTaskStarted(Task task) { + // register this as the current task on the current thread. + threadTasks.put(Thread.currentThread(), task); + BuildEvent event = new BuildEvent(task); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.taskStarted(event); + } + } + + protected void fireTaskFinished(Task task, Throwable exception) { + threadTasks.remove(Thread.currentThread()); + System.out.flush(); + System.err.flush(); + BuildEvent event = new BuildEvent(task); + event.setException(exception); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.taskFinished(event); + } + } + + private void fireMessageLoggedEvent(BuildEvent event, String message, int priority) { + event.setMessage(message, priority); + for (int i = 0; i < listeners.size(); i++) { + BuildListener listener = (BuildListener) listeners.elementAt(i); + listener.messageLogged(event); + } + } + + protected void fireMessageLogged(Project project, String message, int priority) { + BuildEvent event = new BuildEvent(project); + fireMessageLoggedEvent(event, message, priority); + } + + protected void fireMessageLogged(Target target, String message, int priority) { + BuildEvent event = new BuildEvent(target); + fireMessageLoggedEvent(event, message, priority); + } + + protected void fireMessageLogged(Task task, String message, int priority) { + BuildEvent event = new BuildEvent(task); + fireMessageLoggedEvent(event, message, priority); + } +} 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 new file mode 100644 index 000000000..e028ebe92 --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/RoleAdapter.java @@ -0,0 +1,69 @@ +/* + * 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", "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; + +public interface RoleAdapter { + + /** + * Set the object being adapted. + * @param o the object being adapted + */ + public void setProxy(Object o); + + /** + * Get the object adapted by this class. + * @return the object being adapted, if any. + */ + public Object getProxy(); +} 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 new file mode 100644 index 000000000..e6b8c843b --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/SymbolTable.java @@ -0,0 +1,491 @@ +/* + * 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", "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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +import org.apache.tools.ant.types.Path; + +public class SymbolTable { + + /** Parent symbol table */ + private SymbolTable parentTable; + + /** Project associated with this symbol table */ + private Project project; + + /** The table of roles available to this Project */ + private Hashtable roles = new Hashtable(); + + /** The table of loaders active on this Project */ + private Hashtable loaders = new Hashtable(); + + /** + * Table of per role definitions. + */ + private Hashtable defs = new Hashtable(); + + /** + * Parameters for checking adapters. + */ + private static final Class[] CHECK_ADAPTER_PARAMS = + new Class[]{Class.class, Project.class}; + + /** + * Create a top level Symbol table. + */ + public SymbolTable() { + } + + /** + * Create a symbol table inheriting the definitions + * from that defined in the calling Project. + * @param p the calling project + */ + public SymbolTable(Project p) { + parentTable = p.getSymbols(); + } + + /** + * Set the project associated with this symbol table. + * @param p the project for this symbol table + */ + public void setProject(Project p) { + this.project = p; + } + + /** + * Find all the roles supported by a Class + * on this symbol table. + * @param clz the class to analyze + * @return an array of roles supported by the class + */ + public String[] findRoles(final Class clz) { + Vector list = new Vector(); + findRoles(clz, list); + return (String[])list.toArray(new String[list.size()]); + } + + /** + * Collect the roles for the class + * @param clz the class being inspected + * @param list the roles collected up to this point + */ + private void findRoles(final Class clz, Vector list) { + for (Enumeration e = roles.keys(); e.hasMoreElements();) { + String role = (String) e.nextElement(); + + if (((Role) roles.get(role)).isImplementedBy(clz)) { + list.addElement(role); + } + } + if (parentTable != null) findRoles(clz, list); + } + + /** + * Get the Role definition + * @param role the name of the role + * @return the method used to support objects on this role + */ + public Role getRole(String role) { + Role r = (Role) roles.get(role); + if (r == null && parentTable != null) { + return parentTable.getRole(role); + } + return r; + } + + /** + * Add a new role definition to this project. + * @param role the name of the role + * @param rclz the interface used to specify support for the role. + * @param aclz the optional adapter class + * @return whether the role replaced a different definition + */ + public boolean addRole(String role, Class rclz, Class aclz) { + // Check if role already declared + Role old = getRole(role); + if (old != null && old.isSameAsFor(rclz, aclz) + ) { + project.log("Ignoring override for role " + role + + ", it is already defined by the same definition.", + project.MSG_VERBOSE); + return false; + } + // Role interfaces should only contain one method + roles.put(role, new Role(rclz, aclz)); + 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. + * @param name the name of the element for this Class + * @param clz the Class being declared + * @return the old definition + */ + public Class add(String role, String name, Class clz) { + // Find the role definition + Role r = getRole(role); + if (r == null) { + throw new BuildException("Unknown role: " + role); + } + // Check if it is already defined + Class old = get(role, name); + if (old != null) { + if (old.equals(clz)) { + project.log("Ignoring override for "+ role + " " + name + + ", it is already defined by the same class.", + project.MSG_VERBOSE); + return old; + } + else { + project.log("Trying to override old definition of " + + role + " " + name, + project.MSG_WARN); + } + } + checkClass(clz); + // Check that the Class is compatible with the role definition + r.verifyAdaptability(role, clz); + // 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; + } + + /** + * Checks a class, whether it is suitable for serving in ANT. + * @throws BuildException and logs as Project.MSG_ERR for + * conditions, that will cause execution to fail. + */ + void checkClass(final Class clz) + throws BuildException { + if(!Modifier.isPublic(clz.getModifiers())) { + final String message = clz + " is not public"; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + if(Modifier.isAbstract(clz.getModifiers())) { + final String message = clz + " is abstract"; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + try { + // Class can have a "no arg" constructor or take a single + // Project argument. + // don't have to check for public, since + // getConstructor finds public constructors only. + try { + clz.getConstructor(new Class[0]); + } catch (NoSuchMethodException nse) { + clz.getConstructor(new Class[] {Project.class}); + } + } catch(NoSuchMethodException e) { + final String message = + "No valid public constructor in " + clz; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + } + + /** + * Get the class in the role identified with the element name. + * @param role the role to look into. + * @param name the name of the element to sea + * @return the Class implementation + */ + public Class 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; + } + if (parentTable != null) { + return parentTable.get(role, name); + } + return null; + } + + /** + * Get a Hashtable that is usable for manipulating Tasks, + * @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 implementation that delegates + * the search operations to the Symbol table + */ + private class SymbolHashtable extends Hashtable { + final String role; + SymbolHashtable(String role) { + this.role = role; + } + + public synchronized Object put(Object key, Object value) { + return SymbolTable.this.add(role, (String) key, (Class) value); + } + + public synchronized Object get(Object key) { + return SymbolTable.this.get(role, (String)key); + } + } + + /** + * The definition of a role + */ + public class Role { + private Method interfaceMethod; + private Method adapterVerifier; + + /** + * Creates a new Role object + * @param roleClz the class that defines the role + * @param adapterClz the class for the adapter, or null if none + */ + Role(Class roleClz, Class adapterClz) { + interfaceMethod = validInterface(roleClz); + adapterVerifier = validAdapter(adapterClz, interfaceMethod); + } + + /** + * Get the method used to set on interface + */ + public Method getInterfaceMethod() { + return interfaceMethod; + } + + /** + * Instantiate a new adapter for this role. + */ + public RoleAdapter createAdapter() { + if (adapterVerifier == null) return null; + + try { + return (RoleAdapter) + adapterVerifier.getDeclaringClass().newInstance(); + } + catch(BuildException be) { + throw be; + } + catch(Exception e) { + throw new BuildException(e); + } + } + + /** + * 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 + */ + public void verifyAdaptability(String role, Class clz) { + if (interfaceMethod.getParameterTypes()[0].isAssignableFrom(clz)) { + return; + } + if (adapterVerifier == null) { + String msg = "Class " + clz.getName() + + " incompatible with role: " + role; + throw new BuildException(msg); + } + try { + try { + adapterVerifier.invoke(null, + new Object[]{clz, project}); + } + catch (InvocationTargetException ite) { + throw ite.getTargetException(); + } + } + catch(BuildException be) { throw be; } + catch(Error err) {throw err; } + catch(Throwable t) { + throw new BuildException(t); + } + } + + public boolean isSameAsFor(Class clz, Class pclz) { + return interfaceMethod.getDeclaringClass().equals(clz) && + ((adapterVerifier == null && pclz == null) || + adapterVerifier.getDeclaringClass().equals(pclz)); + } + + public boolean isImplementedBy(Class clz) { + return interfaceMethod.getDeclaringClass().isAssignableFrom(clz); + } + } +} 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 new file mode 100644 index 000000000..213f4f014 --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/TaskAdapter.java @@ -0,0 +1,168 @@ +/* + * 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 java.lang.reflect.Method; + + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with similar + * patterns). + * + * @author costin@dnt.ro + * @author j_a_fernandez@yahoo.com + */ +public class TaskAdapter extends Task implements RoleAdapter { + + Object proxy; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by + * {@link Project#checkTaskClass}. + * + * Throws a BuildException and logs as Project.MSG_ERR for + * conditions, that will cause the task execution to fail. + * Logs other suspicious conditions with Project.MSG_WARN. + */ + public static void checkTaskClass(final Class taskClass, final Project project) { + // This code is for backward compatibility + checkClass(taskClass, project); + } + + /** + * Checks a class, whether it is suitable to be adapted. + * + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. + * + * Throws a BuildException and logs as Project.MSG_ERR for + * conditions, that will cause the task execution to fail. + * Logs other suspicious conditions with Project.MSG_WARN. + */ + public static void checkClass(final Class taskClass, final Project project) { + // don't have to check for interface, since then + // taskClass would be abstract too. + try { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if(!Void.TYPE.equals(executeM.getReturnType())) { + final String message = "return type of execute() should be void but was \""+executeM.getReturnType()+"\" in " + taskClass; + project.log(message, Project.MSG_WARN); + } + } catch(NoSuchMethodException e) { + final String message = "No public execute() in " + taskClass; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + } + + /** + * Do the execution. + */ + public void execute() throws BuildException { + Method setProjectM = null; + try { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[] {Project.class}); + if(setProjectM != null) { + setProjectM.invoke(proxy, new Object[] {project}); + } + } catch (NoSuchMethodException e) { + // ignore this if the class being used as a task does not have + // a set project method. + } catch( Exception ex ) { + log("Error setting project in " + proxy.getClass(), + Project.MSG_ERR); + throw new BuildException( ex ); + } + + + Method executeM=null; + try { + Class c=proxy.getClass(); + executeM=c.getMethod( "execute", new Class[0] ); + if( executeM == null ) { + log("No public execute() in " + proxy.getClass(), Project.MSG_ERR); + throw new BuildException("No public execute() in " + proxy.getClass()); + } + executeM.invoke(proxy, null); + return; + } catch( Exception ex ) { + log("Error in " + proxy.getClass(), Project.MSG_ERR); + throw new BuildException( ex ); + } + + } + + /** + * Set the target object class + */ + public void setProxy(Object o) { + this.proxy = o; + } + + public Object getProxy() { + return this.proxy ; + } + +} 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 new file mode 100644 index 000000000..13d42380d --- /dev/null +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -0,0 +1,475 @@ +/* + * 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.taskdefs; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.util.FileUtils; +import java.io.File; +import java.io.PrintStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * Call Ant in a sub-project + * + *

+ *  <target name="foo" depends="init">
+ *    <ant antfile="build.xml" target="bar" >
+ *      <property name="property1" value="aaaaa" />
+ *      <property name="foo" value="baz" />
+ *    </ant>
+ *  </target>
+ *
+ *  <target name="bar" depends="init">
+ *    <echo message="prop is ${property1} ${foo}" />
+ *  </target>
+ * 
+ * + * + * @author costin@dnt.ro + */ +public class Ant extends Task { + + /** the basedir where is executed the build file */ + private File dir = null; + + /** the build.xml file (can be absolute) in this case dir will be ignored */ + private String antFile = null; + + /** the target to call if any */ + private String target = null; + + /** the output */ + private String output = null; + + /** should we inherit properties from the parent ? */ + private boolean inheritAll = true; + + /** should we inherit references from the parent ? */ + private boolean inheritRefs = false; + + /** the properties to pass to the new project */ + private Vector properties = new Vector(); + + /** the references to pass to the new project */ + private Vector references = new Vector(); + + /** the temporary project created to run the build file */ + private Project newProject; + + /** + * If true, inherit all properties from parent Project + * If false, inherit only userProperties and those defined + * inside the ant call itself + */ + public void setInheritAll(boolean value) { + inheritAll = value; + } + + /** + * If true, inherit all references from parent Project + * If false, inherit only those defined + * inside the ant call itself + */ + public void setInheritRefs(boolean value) { + inheritRefs = value; + } + + public void init() { + newProject = new Project(project); + newProject.setJavaVersionProperty(); +// newProject.addTaskDefinition("property", +// (Class)project.getTaskDefinitions().get("property")); + } + + private void reinit() { + init(); + final int count = properties.size(); + for (int i = 0; i < count; i++) { + Property p = (Property) properties.elementAt(i); + Property newP = (Property) newProject.createTask("property"); + newP.setName(p.getName()); + if (p.getValue() != null) { + newP.setValue(p.getValue()); + } + if (p.getFile() != null) { + newP.setFile(p.getFile()); + } + if (p.getResource() != null) { + newP.setResource(p.getResource()); + } + properties.setElementAt(newP, i); + } + } + + private void initializeProject() { + Vector listeners = project.getBuildListeners(); + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + newProject.addBuildListener((BuildListener)listeners.elementAt(i)); + } + + if (output != null) { + try { + PrintStream out = new PrintStream(new FileOutputStream(output)); + DefaultLogger logger = new DefaultLogger(); + logger.setMessageOutputLevel(Project.MSG_INFO); + logger.setOutputPrintStream(out); + logger.setErrorPrintStream(out); + newProject.addBuildListener(logger); + } + catch( IOException ex ) { + log( "Ant: Can't set output to " + output ); + } + } + +// 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) { + prop1 = project.getProperties(); + } else { + prop1 = project.getUserProperties(); + + // set Java built-in properties separately, + // b/c we won't inherit them. + newProject.setSystemProperties(); + } + + Enumeration e = prop1.keys(); + while (e.hasMoreElements()) { + String arg = (String) e.nextElement(); + if ("basedir".equals(arg) || "ant.file".equals(arg)) { + // basedir and ant.file get special treatment in execute() + continue; + } + + String value = (String) prop1.get(arg); + if (inheritAll){ + newProject.setProperty(arg, value); + } else { + newProject.setUserProperty(arg, value); + } + } + } + + protected void handleOutput(String line) { + if (newProject != null) { + newProject.demuxOutput(line, false); + } else { + super.handleOutput(line); + } + } + + protected void handleErrorOutput(String line) { + if (newProject != null) { + newProject.demuxOutput(line, true); + } else { + super.handleErrorOutput(line); + } + } + + /** + * Do the execution. + */ + public void execute() throws BuildException { + try { + if (newProject == null) { + reinit(); + } + + if ( (dir == null) && (inheritAll) ) { + dir = project.getBaseDir(); + } + + initializeProject(); + + if (dir != null) { + newProject.setBaseDir(dir); + newProject.setUserProperty("basedir" , dir.getAbsolutePath()); + } else { + dir = project.getBaseDir(); + } + + overrideProperties(); + + if (antFile == null) { + antFile = "build.xml"; + } + + File file = FileUtils.newFileUtils().resolveFile(dir, antFile); + antFile = file.getAbsolutePath(); + + newProject.setUserProperty( "ant.file" , antFile ); + ProjectHelper.configureProject(newProject, new File(antFile)); + + if (target == null) { + target = newProject.getDefaultTarget(); + } + + addReferences(); + + // Are we trying to call the target in which we are defined? + if (newProject.getBaseDir().equals(project.getBaseDir()) && + newProject.getProperty("ant.file").equals(project.getProperty("ant.file")) && + getOwningTarget() != null && + target.equals(this.getOwningTarget().getName())) { + + throw new BuildException("ant task calling its own parent target"); + } + + newProject.executeTarget(target); + } finally { + // help the gc + newProject = null; + } + } + + /** + * Override the properties in the new project with the one + * explicitly defined as nested elements here. + */ + private void overrideProperties() throws BuildException { + Enumeration e = properties.elements(); + while (e.hasMoreElements()) { + Property p = (Property) e.nextElement(); + p.setProject(newProject); + p.execute(); + } + } + + /** + * Add the references explicitly defined as nested elements to the + * new project. Also copy over all references that don't override + * existing references in the new project if inheritall has been + * requested. + */ + private void addReferences() throws BuildException { + Hashtable thisReferences = (Hashtable) project.getReferences().clone(); + Hashtable newReferences = newProject.getReferences(); + Enumeration e; + if (references.size() > 0) { + for(e = references.elements(); e.hasMoreElements();) { + Reference ref = (Reference)e.nextElement(); + String refid = ref.getRefId(); + if (refid == null) { + throw new BuildException("the refid attribute is required for reference elements"); + } + if (!thisReferences.containsKey(refid)) { + log("Parent project doesn't contain any reference '" + + refid + "'", + Project.MSG_WARN); + continue; + } + + thisReferences.remove(refid); + String toRefid = ref.getToRefid(); + if (toRefid == null) { + toRefid = refid; + } + copyReference(refid, toRefid); + } + } + + // Now add all references that are not defined in the + // subproject, if inheritRefs is true + if (inheritRefs) { + for(e = thisReferences.keys(); e.hasMoreElements();) { + String key = (String)e.nextElement(); + if (newReferences.containsKey(key)) { + continue; + } + copyReference(key, key); + } + } + } + + /** + * Try to clone and reconfigure the object referenced by oldkey in + * the parent project and add it to the new project with the key + * newkey. + * + *

If we cannot clone it, copy the referenced object itself and + * keep our fingers crossed.

+ */ + private void copyReference(String oldKey, String newKey) { + Object orig = project.getReference(oldKey); + Class c = orig.getClass(); + Object copy = orig; + try { + Method cloneM = c.getMethod("clone", new Class[0]); + if (cloneM != null) { + copy = cloneM.invoke(orig, new Object[0]); + } + } catch (Exception e) { + // not Clonable + } + + + if (copy instanceof ProjectComponent) { + ((ProjectComponent) copy).setProject(newProject); + } else { + try { + Method setProjectM = + c.getMethod( "setProject", new Class[] {Project.class}); + if(setProjectM != null) { + setProjectM.invoke(copy, new Object[] {newProject}); + } + } catch (NoSuchMethodException e) { + // ignore this if the class being referenced does not have + // a set project method. + } catch(Exception e2) { + String msg = "Error setting new project instance for reference with id " + + oldKey; + throw new BuildException(msg, e2, location); + } + } + newProject.addReference(newKey, copy); + } + + /** + * ... + */ + public void setDir(File d) { + this.dir = d; + } + + /** + * set the build file, it can be either absolute or relative. + * If it is absolute, dir will be ignored, if it is + * relative it will be resolved relative to dir. + */ + public void setAntfile(String s) { + // @note: it is a string and not a file to handle relative/absolute + // otherwise a relative file will be resolved based on the current + // basedir. + this.antFile = s; + } + + /** + * set the target to execute. If none is defined it will + * execute the default target of the build file + */ + public void setTarget(String s) { + this.target = s; + } + + public void setOutput(String s) { + this.output = s; + } + + /** create a property to pass to the new project as a 'user property' */ + public Property createProperty() { + if (newProject == null) { + reinit(); + } + Property p = new Property(true); + p.setProject(newProject); + p.setTaskName("property"); + properties.addElement( p ); + return p; + } + + /** + * create a reference element that identifies a data type that + * should be carried over to the new project. + */ + public void addReference(Reference r) { + references.addElement(r); + } + + /** + * Helper class that implements the nested <reference> + * element of <ant> and <antcall>. + */ + public static class Reference + extends org.apache.tools.ant.types.Reference { + + public Reference() {super();} + + private String targetid=null; + public void setToRefid(String targetid) { this.targetid=targetid; } + public String getToRefid() { return targetid; } + } +} diff --git a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antjar.java b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antjar.java index e0113476a..16ef3ca7b 100644 --- a/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antjar.java +++ b/proposal/sandbox/antlib/src/main/org/apache/tools/ant/taskdefs/Antjar.java @@ -107,9 +107,6 @@ public class Antjar extends Jar { throw new BuildException("Deployment descriptor: " + libraryDescriptor + " does not exist."); } - //check - validateDescriptor(); - // Create a ZipFileSet for this file, and pass it up. ZipFileSet fs = new ZipFileSet(); fs.setDir(new File(libraryDescriptor.getParent())); @@ -130,7 +127,7 @@ public class Antjar extends Jar { throws IOException, BuildException { // If no antxml file is specified, it's an error. if (libraryDescriptor == null) { - throw new BuildException("webxml attribute is required", location); + throw new BuildException("antxml attribute is required", location); } super.initZipOutputStream(zOut); @@ -177,140 +174,4 @@ public class Antjar extends Jar { super.cleanUp(); } - - /** - * validate the descriptor against the DTD - * - * @exception BuildException failure to validate - */ - protected void validateDescriptor() - throws BuildException { - SAXParserFactory saxFactory = SAXParserFactory.newInstance(); - saxFactory.setValidating(true); - InputStream is = null; - try { - SAXParser saxParser = saxFactory.newSAXParser(); - Parser parser = saxParser.getParser(); - is = new FileInputStream(libraryDescriptor); - InputSource inputSource = new InputSource(is); - inputSource.setSystemId("file:" + libraryDescriptor); - project.log("Validating library descriptor: " + libraryDescriptor, - Project.MSG_VERBOSE); - saxParser.parse(inputSource, new AntLibraryValidator()); - } - catch (ParserConfigurationException exc) { - throw new BuildException("Parser has not been configured correctly", exc); - } - catch (SAXParseException exc) { - Location location = - new Location(libraryDescriptor.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 (IOException exc) { - throw new BuildException("Error reading library descriptor", exc); - } - finally { - if (is != null) { - try { - is.close(); - } - catch (IOException ioe) { - // ignore this - } - } - } - } - - - /** - * Parses the document describing the content of the library. - */ - private class AntLibraryValidator extends HandlerBase { - - /** - * flag to track whether the DOCTYPE was hit in the prolog - */ - private boolean doctypePresent = false; - - /** - * doc locator - */ - private Locator locator = null; - - /** - * Sets the DocumentLocator attribute of the AntLibraryValidator - * object - * - * @param locator The new DocumentLocator value - */ - public void setDocumentLocator(Locator locator) { - this.locator = locator; - } - - /** - * SAX callback handler - * - * @param tag XML tag - * @param attrs attributes - * @exception SAXParseException parse trouble - */ - public void startElement(String tag, AttributeList attrs) - throws SAXParseException { - // By the time an element is found - // the DOCTYPE should have been found. - if (!doctypePresent) { - String msg = "Missing DOCTYPE declaration or wrong SYSTEM ID"; - throw new SAXParseException(msg, locator); - } - } - - /** - * Recognizes the DTD declaration for antlib and returns the corresponding - * DTD definition from a resource.

- * - * To allow for future versions of the DTD format it will search - * for any DTDs of the form "Antlib-V.*\.dtd". - * - * @param publicId public ID (ignored) - * @param systemId system ID (matched against) - * @return local DTD instance - */ - public InputSource resolveEntity(String publicId, - String systemId) { - - log("Looking for entity with PublicID=" + publicId + - " and SystemId=" + systemId, Project.MSG_VERBOSE); - if (Antlib.matchDtdId(systemId)) { - String resId = - systemId.substring(Antlib.ANTLIB_DTD_URL.length()); - InputSource is = - new InputSource(this.getClass().getResourceAsStream(resId)); - - is.setSystemId(systemId); - doctypePresent = true; - return is; - } - return null; - } - //end inner class AntLibraryValidator - } } - 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 57c117f47..c084791c4 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 @@ -76,9 +76,11 @@ import java.io.*; * @since ant1.5 */ public class Antlib extends Task { - /* - * implements DeclaringTask + /** + * The named classloader to use. + * Defaults to the default classLoader. */ + private String loaderId = ""; /** * library attribute @@ -177,6 +179,15 @@ public class Antlib extends Task { this.file = file; } + /** + * Set the ClassLoader to use for this library. + * + * @param id the id for the ClassLoader to use, + * if other than the default. + */ + public void setLoaderid(String id) { + this.loaderId = id; + } /** * Set whether to override any existing definitions. @@ -189,8 +200,9 @@ public class Antlib extends Task { /** - * Set whether to use a new classloader or not. Default is false - * . This property is mostly used by the core when loading core tasks. + * Set whether to use a new classloader or not. + * Default is false. + * This property is mostly used by the core when loading core tasks. * * @param useCurrentClassloader if true the current classloader will * be used to load the definitions. @@ -264,7 +276,7 @@ public class Antlib extends Task { String msg = "You cannot specify both file and library."; throw new BuildException(msg, location); } - // For the time being libraries live in $ANT_HOME/lib. + // For the time being libraries live in $ANT_HOME/antlib. // The idea being that we would not load all the jars there anymore String home = project.getProperty("ant.home"); @@ -272,7 +284,7 @@ public class Antlib extends Task { throw new BuildException("ANT_HOME not set as required."); } - realFile = new File(new File(home, "lib"), library); + realFile = new File(new File(home, "antlib"), library); } else if (file == null) { String msg = "Must specify either library or file attribute."; @@ -406,8 +418,7 @@ public class Antlib extends Task { if (classpath != null) { clspath.append(classpath); } - AntClassLoader al = new AntClassLoader(project, clspath, true); - return al; + return project.getSymbols().addToLoader(loaderId, clspath); } @@ -473,30 +484,6 @@ public class Antlib extends Task { } - /** - * get a DTD URI from url, prefix and extension - * - * @return URI for this dtd version - */ - public static String dtdVersion() { - return ANTLIB_DTD_URL + ANTLIB_DTD_PREFIX + - ANTLIB_DTD_VERSION + ANTLIB_DTD_EXT; - } - - - /** - * compare system ID with the dtd string - * -ignoring any version number - * @param systemId Description of Parameter - * @return true if this is a an ant library descriptor - */ - public static boolean matchDtdId(String systemId) { - return (systemId != null && - systemId.startsWith(ANTLIB_DTD_URL + ANTLIB_DTD_PREFIX) && - systemId.endsWith(ANTLIB_DTD_EXT)); - } - - /** * Parses the document describing the content of the * library. An inner class for access to Project.log @@ -516,6 +503,14 @@ public class Antlib extends Task { */ private Locator locator = null; + private int level = 0; + + private SymbolTable symbols = null; + + private String name = null; + private String className = null; + private String adapter = null; + /** * Constructor for the AntLibraryHandler object * @@ -525,9 +520,9 @@ public class Antlib extends Task { AntLibraryHandler(ClassLoader classloader, Properties als) { this.classloader = classloader; this.aliasMap = als; + this.symbols = project.getSymbols(); } - /** * Sets the DocumentLocator attribute of the AntLibraryHandler * object @@ -538,6 +533,35 @@ public class Antlib extends Task { this.locator = locator; } + private void parseAttributes(String tag, AttributeList attrs) + throws SAXParseException { + name = null; + className = null; + adapter = null; + + for (int i = 0, last = attrs.getLength(); i < last; i++) { + String key = attrs.getName(i); + String value = attrs.getValue(i); + + if (key.equals("name")) { + name = value; + } + else if (key.equals("class")) { + className = value; + } + else if ("role".equals(tag) && key.equals("adapter")) { + adapter = value; + } + else { + throw new SAXParseException("Unexpected attribute \"" + + key + "\"", locator); + } + } + if (name == null || className == null) { + String msg = "Underspecified " + tag + " declaration."; + throw new SAXParseException(msg, locator); + } + } /** * SAX callback handler @@ -548,121 +572,105 @@ public class Antlib extends Task { */ public void startElement(String tag, AttributeList attrs) throws SAXParseException { + level ++; if ("antlib".equals(tag)) { + if (level > 1) { + throw new SAXParseException("Unexpected element: " + tag, + locator); + } // No attributes to worry about return; } - if ("task".equals(tag) || "type".equals(tag)) { - String name = null; - String className = null; - - for (int i = 0, last = attrs.getLength(); i < last; i++) { - String key = attrs.getName(i); - String value = attrs.getValue(i); - - if (key.equals("name")) { - name = value; - } - else if (key.equals("class")) { - className = value; - } - else { - throw new SAXParseException("Unexpected attribute \"" - + key + "\"", locator); - } - } - if (name == null || className == null) { - String msg = "Underspecified " + tag + " declaration."; - throw new SAXParseException(msg, locator); - } - - try { - //check for name alias - String alias = aliasMap.getProperty(name); - if (alias != null) { - name = alias; - } - //catch an attempted override of an existing name - if (!override && inUse(name)) { - String msg = "Cannot override " + tag + ": " + name; - log(msg, Project.MSG_WARN); - return; - } - - //load the named class - Class cls; - if(classloader==null) { - cls=Class.forName(className); - } - else { - cls=classloader.loadClass(className); - } - - //register it as a task or a datatype - if (tag.equals("task")) { - project.addTaskDefinition(name, cls); - } - else { - project.addDataTypeDefinition(name, cls); - } - } - catch (ClassNotFoundException cnfe) { - String msg = "Class " + className + - " cannot be found"; - throw new SAXParseException(msg, locator, cnfe); - } - catch (NoClassDefFoundError ncdfe) { - String msg = "Class " + className + - " cannot be found"; - throw new SAXParseException(msg, locator); - } - } - else { - throw new SAXParseException("Unexpected element \"" + - tag + "\"", - locator); - } + if (level == 1) { + throw new SAXParseException("Missing antlib root element", + locator); + } + + // Must have the two attributes declared + parseAttributes(tag, attrs); + + try { + if ("role".equals(tag)) { + if (isRoleInUse(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))); + return; + } + + // Defining a new element kind + //check for name alias + String alias = aliasMap.getProperty(name); + if (alias != null) { + name = alias; + } + //catch an attempted override of an existing name + if (!override && isInUse(tag, name)) { + String msg = "Cannot override " + tag + ": " + name; + log(msg, Project.MSG_WARN); + return; + } + symbols.add(tag, name, loadClass(className)); + } + catch(BuildException be) { + throw new SAXParseException(be.getMessage(), locator, be); + } } + public void endElement(String tag) { + level--; + } + + private Class loadClass(String className) + throws SAXParseException { + try { + //load the named class + Class cls; + if(classloader==null) { + cls=Class.forName(className); + } + else { + cls=classloader.loadClass(className); + } + return cls; + } + catch (ClassNotFoundException cnfe) { + String msg = "Class " + className + + " cannot be found"; + throw new SAXParseException(msg, locator, cnfe); + } + catch (NoClassDefFoundError ncdfe) { + String msg = "Class " + className + + " cannot be found"; + throw new SAXParseException(msg, locator); + } + } /** - * test for a name being in use already + * 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 inUse(String name) { - return (project.getTaskDefinitions().get(name) != null || - project.getDataTypeDefinitions().get(name) != null); + private boolean isInUse(String role, String name) { + return (symbols.get(role, name) != null); } - /** - * Recognizes the DTD declaration for antlib and returns the corresponding - * DTD definition from a resource.

- * - * To allow for future versions of the DTD format it will search - * for any DTDs of the form "Antlib-V.*\.dtd". + * test for a role name being in use already * - * @param publicId public ID (ignored) - * @param systemId system ID (matched against) - * @return local DTD instance + * @param name the name to test + * @return true if it is a task or a datatype */ - public InputSource resolveEntity(String publicId, - String systemId) { - - log("Looking for entiry with PublicID=" + publicId + - " and SystemId=" + systemId, Project.MSG_VERBOSE); - if (matchDtdId(systemId)) { - String resId = systemId.substring(ANTLIB_DTD_URL.length()); - InputSource is = - new InputSource(this.getClass().getResourceAsStream(resId)); - - is.setSystemId(systemId); - return is; - } - return null; + private boolean isRoleInUse(String name) { + return (symbols.getRole(name) != null); } + //end inner class AntLibraryHandler }

Attribute