From 2ba55667967546b923a7f23fe3f0255e5dc9efdb Mon Sep 17 00:00:00 2001 From: Costin Manolache Date: Sat, 4 Jan 2003 16:36:42 +0000 Subject: [PATCH] Cut&paste of all code related to task/types from Project. This is _not_ an antlib, just a mechansim to make the task/type management pluggable and simplify Project ( which will just delegate for backward compat. ). Please review - and -1 ( or just remove/change ) if you think this is bad. The actual details of the plugin mechanisms are not perfect - we need to settle on them before 1.6 is released ( same for PropertyHelper). PR: Obtained from: Submitted by: Reviewed by: git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@273745 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/tools/ant/ComponentHelper.java | 662 ++++++++++++++++++ 1 file changed, 662 insertions(+) create mode 100644 src/main/org/apache/tools/ant/ComponentHelper.java diff --git a/src/main/org/apache/tools/ant/ComponentHelper.java b/src/main/org/apache/tools/ant/ComponentHelper.java new file mode 100644 index 000000000..05878fcbe --- /dev/null +++ b/src/main/org/apache/tools/ant/ComponentHelper.java @@ -0,0 +1,662 @@ +/* + * 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 org.apache.tools.ant.util.LazyHashtable; +import org.apache.tools.ant.util.WeakishReference; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import java.io.InputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; + +/** + * Component creation and configuration. + * + * This is cut&paste from Project.java of everything related to + * task/type management. Project will just delegate. + * + * A very simple hook mechnism is provided that allows users to plug + * in custom code. It is also possible to replace the default behavior + * ( for example in an app embeding ant ) + * + * @author Costin Manolache + * @since Ant1.6 + */ +public class ComponentHelper { + /** Map from data type names to implementing classes (String to Class). */ + private Hashtable dataClassDefinitions; + /** Map from task names to implementing classes (String to Class). */ + private Hashtable taskClassDefinitions; + /** + * Map from task names to vectors of created tasks + * (String to Vector of Task). This is used to invalidate tasks if + * the task definition changes. + */ + private Hashtable createdTasks = new Hashtable(); + + + protected ComponentHelper next; + protected Project project; + + /** + */ + public static ComponentHelper getComponentHelper(Project project) { + // Singleton for now, it may change ( per/classloader ) + ComponentHelper ph=(ComponentHelper)project.getReference( "ant.ComponentHelper" ); + if( ph!=null ) return ph; + ph=new ComponentHelper(); + ph.setProject( project ); + + project.addReference( "ant.ComponentHelper",ph ); + return ph; + } + + protected ComponentHelper() { + } + + public void setNext( ComponentHelper next ) { + this.next=next; + } + + public ComponentHelper getNext() { + return next; + } + + public void setProject(Project project) { + this.project = project; + dataClassDefinitions= new AntTaskTable(project, false); + taskClassDefinitions= new AntTaskTable(project, true); + } + + + /** Creates an ant component.. + * + * A factory may have knowledge about the tasks it creates. It can return + * an object extending TaskAdapter that emulates Task/DataType. If null is returned, + * the next helper is tried. + * + * @param ns namespace if a SAX2 parser is used, null for 'classical' ant + * @param taskName the (local) name of the task. + */ + public Object createComponent( String ns, + String taskName ) + throws BuildException + { + if( getNext() != null ) { + return getNext().createComponent( ns, taskName); + } + return null; + // XXX class loader ? Can use the ns, but additional hints may be available in taskdef + // + } + + public Object createComponent( UnknownElement ue, + String ns, + String taskName ) + throws BuildException + { + Object component=null; + + // System.out.println("Fallback to project default " + taskName ); + // Can't create component. Default is to use the old methods in project. + + // This policy is taken from 1.5 ProjectHelper. In future the difference between + // task and type should disapear. + if( project.getDataTypeDefinitions().get(taskName) != null ) { + // This is the original policy in ProjectHelper. The 1.5 version of UnkwnonwElement + // used to try first to create a task, and if it failed tried a type. In 1.6 the diff + // should disapear. + component = project.createDataType(taskName); + if( component!=null ) return component; + } + + // from UnkwnonwElement.createTask. The 'top level' case is removed, we're + // allways lazy + component = project.createTask(taskName); + + return component; + } + + public void initDefaultDefinitions() throws BuildException { + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream(defs); + if (in == null) { + throw new BuildException("Can't load default task list"); + } + props.load(in); + in.close(); + ((AntTaskTable)taskClassDefinitions).addDefinitions( props ); + + + } catch (IOException ioe) { + throw new BuildException("Can't load default task list"); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream(dataDefs); + if (in == null) { + throw new BuildException("Can't load default datatype list"); + } + props.load(in); + in.close(); + + ((AntTaskTable)dataClassDefinitions).addDefinitions(props); + + + } catch (IOException ioe) { + throw new BuildException("Can't load default datatype list"); + } + } + + /** + * Adds a new task definition to the project. + * Attempting to override an existing definition with an + * equivalent one (i.e. with the same classname) results in + * a verbose log message. Attempting to override an existing definition + * with a different one results in a warning log message and + * invalidates any tasks which have already been created with the + * old definition. + * + * @param taskName The name of the task to add. + * Must not be null. + * @param taskClass The full name of the class implementing the task. + * Must not be null. + * + * @exception BuildException if the class is unsuitable for being an Ant + * task. An error level message is logged before + * this exception is thrown. + * + * @see #checkTaskClass(Class) + - */ + public void addTaskDefinition(String taskName, Class taskClass) + throws BuildException { + Class old = (Class) taskClassDefinitions.get(taskName); + if (null != old) { + if (old.equals(taskClass)) { +// project.log("Ignoring override for task " + taskName +// + ", it is already defined by the same class.", +// Project.MSG_VERBOSE); + return; + } else { + int logLevel = Project.MSG_WARN; + if (old.getName().equals(taskClass.getName())) { + ClassLoader oldLoader = old.getClassLoader(); + ClassLoader newLoader = taskClass.getClassLoader(); + // system classloader on older JDKs can be null + if (oldLoader != null + && newLoader != null + && oldLoader instanceof AntClassLoader + && newLoader instanceof AntClassLoader + && ((AntClassLoader) oldLoader).getClasspath() + .equals(((AntClassLoader) newLoader).getClasspath()) + ) { + // same classname loaded from the same + // classpath components + logLevel = Project.MSG_VERBOSE; + } + } + + project.log("Trying to override old definition of task " + taskName, + logLevel); + invalidateCreatedTasks(taskName); + } + } + + String msg = " +User task: " + taskName + " " + taskClass.getName(); + project.log(msg, Project.MSG_DEBUG); + checkTaskClass(taskClass); + taskClassDefinitions.put(taskName, taskClass); + } + + /** + * Checks whether or not a class is suitable for serving as Ant task. + * Ant task implementation classes must be public, concrete, and have + * a no-arg constructor. + * + * @param taskClass The class to be checked. + * Must not be null. + * + * @exception BuildException if the class is unsuitable for being an Ant + * task. An error level message is logged before + * this exception is thrown. + */ + public void checkTaskClass(final Class taskClass) throws BuildException { + if (!Modifier.isPublic(taskClass.getModifiers())) { + final String message = taskClass + " is not public"; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + if (Modifier.isAbstract(taskClass.getModifiers())) { + final String message = taskClass + " is abstract"; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + try { + taskClass.getConstructor(null); + // don't have to check for public, since + // getConstructor finds public constructors only. + } catch (NoSuchMethodException e) { + final String message = "No public no-arg constructor in " + + taskClass; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + if (!Task.class.isAssignableFrom(taskClass)) { + TaskAdapter.checkTaskClass(taskClass, project); + } + } + + /** + * Returns the current task definition hashtable. The returned hashtable is + * "live" and so should not be modified. + * + * @return a map of from task name to implementing class + * (String to Class). + */ + public Hashtable getTaskDefinitions() { + return taskClassDefinitions; + } + + /** + * Adds a new datatype definition. + * Attempting to override an existing definition with an + * equivalent one (i.e. with the same classname) results in + * a verbose log message. Attempting to override an existing definition + * with a different one results in a warning log message, but the + * definition is changed. + * + * @param typeName The name of the datatype. + * Must not be null. + * @param typeClass The full name of the class implementing the datatype. + * Must not be null. + */ + public void addDataTypeDefinition(String typeName, Class typeClass) { + synchronized(dataClassDefinitions) { + Class old = (Class) dataClassDefinitions.get(typeName); + if (null != old) { + if (old.equals(typeClass)) { +// project.log("Ignoring override for datatype " + typeName +// + ", it is already defined by the same class.", +// Project.MSG_VERBOSE); + return; + } else { + project.log("Trying to override old definition of datatype " + + typeName, Project.MSG_WARN); + } + } + dataClassDefinitions.put(typeName, typeClass); + } + String msg = " +User datatype: " + typeName + " " + + typeClass.getName(); + project.log(msg, Project.MSG_DEBUG); + } + + /** + * Returns the current datatype definition hashtable. The returned + * hashtable is "live" and so should not be modified. + * + * @return a map of from datatype name to implementing class + * (String to Class). + */ + public Hashtable getDataTypeDefinitions() { + return dataClassDefinitions; + } + + /** + * Creates a new instance of a task, adding it to a list of + * created tasks for later invalidation. This causes all tasks + * to be remembered until the containing project is removed + * @param taskType The name of the task to create an instance of. + * Must not be null. + * + * @return an instance of the specified task, or null if + * the task name is not recognised. + * + * @exception BuildException if the task name is recognised but task + * creation fails. + */ + public Task createTask(String taskType) throws BuildException { + Task task=createNewTask(taskType); + if(task!=null) { + addCreatedTask(taskType, task); + } + return task; + } + + /** + * Creates a new instance of a task. This task is not + * cached in the createdTasks list. + * @since ant1.6 + * @param taskType The name of the task to create an instance of. + * Must not be null. + * + * @return an instance of the specified task, or null if + * the task name is not recognised. + * + * @exception BuildException if the task name is recognised but task + * creation fails. + */ + private Task createNewTask(String taskType) throws BuildException { + Class c = (Class) taskClassDefinitions.get(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(project); + task.setTaskType(taskType); + + // set default value, can be changed by the user + task.setTaskName(taskType); + + String msg = " +Task: " + taskType; + project.log (msg, Project.MSG_DEBUG); + return task; + } catch (Throwable t) { + System.out.println("task CL=" + c.getClassLoader()); + String msg = "Could not create task of type: " + + taskType + " due to " + t; + throw new BuildException(msg, t); + } + } + + /** + * Keeps a record of all tasks that have been created so that they + * can be invalidated if a new task definition overrides the current one. + * + * @param type The name of the type of task which has been created. + * Must not be null. + * + * @param task The freshly created task instance. + * Must not be null. + */ + 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(WeakishReference.createReference(task)); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type + * for a given taskname. + * + * @param type The name of the type of task to invalidate. + * Must not be null. + */ + private void invalidateCreatedTasks(String type) { + synchronized (createdTasks) { + Vector v = (Vector) createdTasks.get(type); + if (v != null) { + Enumeration enum = v.elements(); + while (enum.hasMoreElements()) { + WeakishReference ref= + (WeakishReference) enum.nextElement(); + Task t = (Task) ref.get(); + //being a weak ref, it may be null by this point + if(t!=null) { + t.markInvalid(); + } + } + v.removeAllElements(); + createdTasks.remove(type); + } + } + } + + /** + * Creates a new instance of a data type. + * + * @param typeName The name of the data type to create an instance of. + * Must not be null. + * + * @return an instance of the specified data type, or null if + * the data type name is not recognised. + * + * @exception BuildException if the data type name is recognised but + * instance creation fails. + */ + public Object createDataType(String typeName) throws BuildException { + Class c = (Class) dataClassDefinitions.get(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(project); + } + String msg = " +DataType: " + typeName; + project.log(msg, Project.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); + } + } + + /** + * Returns a description of the type of the given element, with + * special handling for instances of tasks and data types. + *

+ * This is useful for logging purposes. + * + * @param element The element to describe. + * Must not be null. + * + * @return a description of the element type + * + * @since Ant 1.6 + */ + public String getElementName(Object element) { + Hashtable elements = taskClassDefinitions; + Class elementClass = element.getClass(); + String typeName = "task"; + if (!elements.contains(elementClass)) { + elements = dataClassDefinitions; + typeName = "data type"; + if (!elements.contains(elementClass)) { + elements = null; + } + } + + if (elements != null) { + Enumeration e = elements.keys(); + while (e.hasMoreElements()) { + String name = (String) e.nextElement(); + Class clazz = (Class) elements.get(name); + if (elementClass.equals(clazz)) { + return "The <" + name + "> " + typeName; + } + } + } + + return "Class " + elementClass.getName(); + } + + + private static class AntTaskTable extends LazyHashtable { + Project project; + Properties props; + boolean tasks=false; + + public AntTaskTable( Project p, boolean tasks ) { + this.project=p; + this.tasks=tasks; + } + + public void addDefinitions( Properties props ) { + this.props=props; + } + + protected void initAll( ) { + if( initAllDone ) return; + project.log("InitAll", Project.MSG_DEBUG); + if( props==null ) return; + Enumeration enum = props.propertyNames(); + while (enum.hasMoreElements()) { + String key = (String) enum.nextElement(); + Class taskClass=getTask( key ); + if( taskClass!=null ) { + // This will call a get() and a put() + if( tasks ) + project.addTaskDefinition(key, taskClass); + else + project.addDataTypeDefinition(key, taskClass ); + } + } + initAllDone=true; + } + + protected Class getTask(String key) { + if( props==null ) return null; // for tasks loaded before init() + String value=props.getProperty(key); + if( value==null) { + //project.log( "No class name for " + key, Project.MSG_VERBOSE ); + return null; + } + try { + Class taskClass=null; + if( project.getCoreLoader() != null && + !("only".equals(project.getProperty("build.sysclasspath")))) { + try { + project.log("Loading with the core loader " + value, + Project.MSG_DEBUG); + taskClass=project.getCoreLoader().loadClass(value); + if( taskClass != null ) return taskClass; + } catch( Exception ex ) { + } + } + taskClass = Class.forName(value); + return taskClass; + } catch (NoClassDefFoundError ncdfe) { + project.log("Could not load a dependent class (" + + ncdfe.getMessage() + ") for task " + key, Project.MSG_DEBUG); + } catch (ClassNotFoundException cnfe) { + project.log("Could not load class (" + value + + ") for task " + key, Project.MSG_DEBUG); + } + return null; + } + + // Hashtable implementation + public Object get( Object key ) { + Object orig=super.get( key ); + if( orig!= null ) return orig; + if( ! (key instanceof String) ) return null; + project.log("Get task " + key, Project.MSG_DEBUG ); + Object taskClass=getTask( (String) key); + if( taskClass != null) + super.put( key, taskClass ); + return taskClass; + } + + public boolean contains( Object key ) { + return get( key ) != null; + } + + } +}