View Javadoc

    1   /*
    2    $Id: AntBuilder.java,v 1.9 2004/12/13 23:48:21 glaforge Exp $
    3
    4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6    Redistribution and use of this software and associated documentation
    7    ("Software"), with or without modification, are permitted provided
    8    that the following conditions are met:
    9
    10   1. Redistributions of source code must retain copyright
    11      statements and notices.  Redistributions must also contain a
    12      copy of this document.
    13
    14   2. Redistributions in binary form must reproduce the
    15      above copyright notice, this list of conditions and the
    16      following disclaimer in the documentation and/or other
    17      materials provided with the distribution.
    18
    19   3. The name "groovy" must not be used to endorse or promote
    20      products derived from this Software without prior written
    21      permission of The Codehaus.  For written permission,
    22      please contact info@codehaus.org.
    23
    24   4. Products derived from this Software may not be called "groovy"
    25      nor may "groovy" appear in their names without prior written
    26      permission of The Codehaus. "groovy" is a registered
    27      trademark of The Codehaus.
    28
    29   5. Due credit should be given to The Codehaus -
    30      http://groovy.codehaus.org/
    31
    32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43   OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45   */
    46  package groovy.util;
    47
    48
    49  import java.lang.reflect.Constructor;
    50  import java.lang.reflect.InvocationTargetException;
    51  import java.lang.reflect.Method;
    52  import java.util.Collections;
    53  import java.util.Iterator;
    54  import java.util.Map;
    55  import java.util.logging.Level;
    56  import java.util.logging.Logger;
    57
    58  import org.apache.tools.ant.*;
    59  import org.apache.tools.ant.types.DataType;
    60  import org.codehaus.groovy.ant.FileScanner;
    61  import org.codehaus.groovy.runtime.InvokerHelper;
    62
    63  /***
    64   * Allows Ant tasks to be used with GroovyMarkup
    65   *
    66   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>, changes by Dierk Koenig (dk)
    67   * @version $Revision: 1.9 $
    68   */
    69  public class AntBuilder extends BuilderSupport {
    70
    71      private static final Class[] addTaskParamTypes = { String.class };
    72
    73      private Logger log = Logger.getLogger(getClass().getName());
    74      private Project project;
    75
    76      public AntBuilder() {
    77          this.project = createProject();
    78      }
    79
    80      public AntBuilder(Project project) {
    81          this.project = project;
    82      }
    83
    84      // dk: introduced for convenience in subclasses
    85      protected Project getProject() {
    86          return project;
    87      }
    88
    89      /***
    90       * @return Factory method to create new Project instances
    91       */
    92      protected Project createProject() {
    93          Project project = new Project();
    94          BuildLogger logger = new NoBannerLogger();
    95
    96          logger.setMessageOutputLevel(org.apache.tools.ant.Project.MSG_INFO);
    97          logger.setOutputPrintStream(System.out);
    98          logger.setErrorPrintStream(System.err);
    99
    100         project.addBuildListener(logger);
    101
    102         project.init();
    103         project.getBaseDir();
    104         return project;
    105     }
    106
    107     protected void setParent(Object parent, Object child) {
    108     }
    109
    110     /***
    111      * Determines, when the ANT Task that is represented by the "node" should perform.
    112      * Node must be an ANT Task or no "perform" is called.
    113      * If node is an ANT Task, it performs right after complete contstruction.
    114      * If node is nested in a TaskContainer, calling "perform" is delegated to that
    115      * TaskContainer.
    116      * @param parent note: null when node is root
    117      * @param node the node that now has all its children applied
    118      */
    119     protected void nodeCompleted(Object parent, Object node) {
    120         if (parent instanceof TaskContainer) {
    121             log.finest("parent is TaskContainer: no perform on nodeCompleted");
    122             return; // parent will care about when children perform
    123         }
    124         if (node instanceof Task) {
    125             Task task = (Task) node;
    126             task.perform();
    127         }
    128     }
    129
    130     protected Object createNode(Object tagName) {
    131         return createNode(tagName.toString(), Collections.EMPTY_MAP);
    132     }
    133
    134     protected Object createNode(Object name, Object value) {
    135         Object task = createNode(name);
    136         setText(task, value.toString());
    137         return task;
    138     }
    139
    140     protected Object createNode(Object name, Map attributes, Object value) {
    141         Object task = createNode(name, attributes);
    142         setText(task, value.toString());
    143         return task;
    144     }
    145
    146     protected Object createNode(Object name, Map attributes) {
    147
    148         if (name.equals("fileScanner")) {
    149             return new FileScanner(project);
    150         }
    151
    152         String tagName = name.toString();
    153         Object answer = null;
    154
    155         Object parentObject = getCurrent();
    156         Object parentTask = getParentTask();
    157
    158         // lets assume that Task instances are not nested inside other Task instances
    159         // for example <manifest> inside a <jar> should be a nested object, where as
    160         // if the parent is not a Task the <manifest> should create a ManifestTask
    161         //
    162         // also its possible to have a root Ant tag which isn't a task, such as when
    163         // defining <fileset id="...">...</fileset>
    164
    165         Object nested = null;
    166         if (parentObject != null && !(parentTask instanceof TaskContainer)) {
    167             nested = createNestedObject(parentObject, tagName);
    168         }
    169
    170         Task task = null;
    171         if (nested == null) {
    172             task = createTask(tagName);
    173             if (task != null) {
    174                 if (log.isLoggable(Level.FINE)) {
    175                     log.fine("Creating an ant Task for name: " + tagName);
    176                 }
    177
    178                 // the following algorithm follows the lifetime of a tag
    179                 // http://jakarta.apache.org/ant/manual/develop.html#writingowntask
    180                 // kindly recommended by Stefan Bodewig
    181
    182                 // create and set its project reference
    183                 if (task instanceof TaskAdapter) {
    184                     answer = ((TaskAdapter) task).getProxy();
    185                 }
    186                 else {
    187                     answer = task;
    188                 }
    189
    190                 // set the task ID if one is given
    191                 Object id = attributes.remove("id");
    192                 if (id != null) {
    193                     project.addReference((String) id, task);
    194                 }
    195
    196                 // now lets initialize
    197                 task.init();
    198
    199                 // now lets set any attributes of this tag...
    200                 setBeanProperties(task, attributes);
    201
    202                 // dk: TaskContainers have their own adding logic
    203                 if (parentObject instanceof TaskContainer){
    204                     ((TaskContainer)parentObject).addTask(task);
    205                 }
    206             }
    207         }
    208
    209         if (task == null) {
    210             if (nested == null) {
    211                 if (log.isLoggable(Level.FINE)) {
    212                     log.fine("Trying to create a data type for tag: " + tagName);
    213                 }
    214                 nested = createDataType(tagName);
    215             }
    216             else {
    217                 if (log.isLoggable(Level.FINE)) {
    218                     log.fine("Created nested property tag: " + tagName);
    219                 }
    220             }
    221
    222             if (nested != null) {
    223                 answer = nested;
    224
    225                 // set the task ID if one is given
    226                 Object id = attributes.remove("id");
    227                 if (id != null) {
    228                     project.addReference((String) id, nested);
    229                 }
    230
    231                 try {
    232                     InvokerHelper.setProperty(nested, "name", tagName);
    233                 }
    234                 catch (Exception e) {
    235                 }
    236
    237                 // now lets set any attributes of this tag...
    238                 setBeanProperties(nested, attributes);
    239
    240                 // now lets add it to its parent
    241                 if (parentObject != null) {
    242                     IntrospectionHelper ih = IntrospectionHelper.getHelper(parentObject.getClass());
    243                     try {
    244                         if (log.isLoggable(Level.FINE)) {
    245                             log.fine(
    246                                 "About to set the: "
    247                                     + tagName
    248                                     + " property on: "
    249                                     + parentObject
    250                                     + " to value: "
    251                                     + nested
    252                                     + " with type: "
    253                                     + nested.getClass());
    254                         }
    255
    256                         ih.storeElement(project, parentObject, nested, tagName);
    257                     }
    258                     catch (Exception e) {
    259                         log.log(Level.WARNING, "Caught exception setting nested: " + tagName, e);
    260                     }
    261
    262                     // now try to set the property for good measure
    263                     // as the storeElement() method does not
    264                     // seem to call any setter methods of non-String types
    265                     try {
    266                         InvokerHelper.setProperty(parentObject, tagName, nested);
    267                     }
    268                     catch (Exception e) {
    269                         log.fine("Caught exception trying to set property: " + tagName + " on: " + parentObject);
    270                     }
    271                 }
    272             }
    273             else {
    274                 log.log(Level.WARNING, "Could not convert tag: " + tagName + " into an Ant task, data type or property. Maybe the task is not on the classpath?");
    275             }
    276         }
    277
    278         return answer;
    279     }
    280
    281     protected void setText(Object task, String text) {
    282         // now lets set the addText() of the body content, if its applicaable
    283         Method method = getAccessibleMethod(task.getClass(), "addText", addTaskParamTypes);
    284         if (method != null) {
    285             Object[] args = { text };
    286             try {
    287                 method.invoke(task, args);
    288             }
    289             catch (Exception e) {
    290                 log.log(Level.WARNING, "Cannot call addText on: " + task + ". Reason: " + e, e);
    291             }
    292         }
    293     }
    294
    295     protected Method getAccessibleMethod(Class theClass, String name, Class[] paramTypes) {
    296         while (true) {
    297             try {
    298                 Method answer = theClass.getDeclaredMethod(name, paramTypes);
    299                 if (answer != null) {
    300                     return answer;
    301                 }
    302             }
    303             catch (Exception e) {
    304                 // ignore
    305             }
    306             theClass = theClass.getSuperclass();
    307             if (theClass == null) {
    308                 return null;
    309             }
    310         }
    311     }
    312
    313     public Project getAntProject() {
    314         return project;
    315     }
    316
    317     // Implementation methods
    318     //-------------------------------------------------------------------------
    319     protected void setBeanProperties(Object object, Map map) {
    320         for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
    321             Map.Entry entry = (Map.Entry) iter.next();
    322             String name = (String) entry.getKey();
    323             Object value = entry.getValue();
    324             setBeanProperty(object, name, ((value == null) ? null : value.toString()));
    325         }
    326     }
    327
    328     protected void setBeanProperty(Object object, String name, Object value) {
    329         if (log.isLoggable(Level.FINE)) {
    330             log.fine("Setting bean property on: " + object + " name: " + name + " value: " + value);
    331         }
    332
    333         IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
    334
    335         if (value instanceof String) {
    336             try {
    337                 ih.setAttribute(getAntProject(), object, name.toLowerCase(), (String) value);
    338                 return;
    339             }
    340             catch (Exception e) {
    341                 // ignore: not a valid property
    342             }
    343         }
    344
    345         try {
    346
    347             ih.storeElement(getAntProject(), object, value, name);
    348         }
    349         catch (Exception e) {
    350
    351             InvokerHelper.setProperty(object, name, value);
    352         }
    353     }
    354
    355     /***
    356      * Creates a nested object of the given object with the specified name
    357      */
    358     protected Object createNestedObject(Object object, String name) {
    359         Object dataType = null;
    360         if (object != null) {
    361             IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
    362
    363             if (ih != null) {
    364                 try {
    365                     // dk: the line below resolves the deprecation warning but may not work
    366                     // properly with namespaces.
    367                     String namespaceUri = "";               // todo: how to set this?
    368                     UnknownElement unknownElement = null;   // todo: what is expected here?
    369                     dataType = ih.getElementCreator(getAntProject(), namespaceUri, object, name.toLowerCase(), unknownElement).create();
    370                 }
    371                 catch (BuildException be) {
    372                     log.log(Level.SEVERE, "Caught: " + be, be);
    373                 }
    374             }
    375         }
    376         if (dataType == null) {
    377             dataType = createDataType(name);
    378         }
    379         return dataType;
    380     }
    381
    382     protected Object createDataType(String name) {
    383         Object dataType = null;
    384
    385         Class type = (Class) getAntProject().getDataTypeDefinitions().get(name);
    386
    387         if (type != null) {
    388
    389             Constructor ctor = null;
    390             boolean noArg = false;
    391
    392             // DataType can have a "no arg" constructor or take a single
    393             // Project argument.
    394             try {
    395                 ctor = type.getConstructor(new Class[0]);
    396                 noArg = true;
    397             }
    398             catch (NoSuchMethodException nse) {
    399                 try {
    400                     ctor = type.getConstructor(new Class[] { Project.class });
    401                     noArg = false;
    402                 }
    403                 catch (NoSuchMethodException nsme) {
    404                     log.log(Level.INFO, "datatype '" + name + "' didn't have a constructor with an Ant Project", nsme);
    405                 }
    406             }
    407
    408             if (noArg) {
    409                 dataType = createDataType(ctor, new Object[0], name, "no-arg constructor");
    410             }
    411             else {
    412                 dataType = createDataType(ctor, new Object[] { getAntProject()}, name, "an Ant project");
    413             }
    414             if (dataType != null) {
    415                 ((DataType) dataType).setProject(getAntProject());
    416             }
    417         }
    418
    419         return dataType;
    420     }
    421
    422     /***
    423      * @return an object create with the given constructor and args.
    424      * @param ctor a constructor to use creating the object
    425      * @param args the arguments to pass to the constructor
    426      * @param name the name of the data type being created
    427      * @param argDescription a human readable description of the args passed
    428      */
    429     protected Object createDataType(Constructor ctor, Object[] args, String name, String argDescription) {
    430         try {
    431             Object datatype = ctor.newInstance(args);
    432             return datatype;
    433         }
    434         catch (InstantiationException ie) {
    435             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ie);
    436         }
    437         catch (IllegalAccessException iae) {
    438             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, iae);
    439         }
    440         catch (InvocationTargetException ite) {
    441             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ite);
    442         }
    443         return null;
    444     }
    445
    446     /***
    447      * @param taskName the name of the task to create
    448      * @return a newly created task
    449      */
    450     protected Task createTask(String taskName) {
    451         return createTask(taskName, (Class) getAntProject().getTaskDefinitions().get(taskName));
    452     }
    453
    454     protected Task createTask(String taskName, Class taskType) {
    455         if (taskType == null) {
    456             return null;
    457         }
    458         try {
    459             Object o = taskType.newInstance();
    460             Task task = null;
    461             if (o instanceof Task) {
    462                 task = (Task) o;
    463             }
    464             else {
    465                 TaskAdapter taskA = new TaskAdapter();
    466                 taskA.setProxy(o);
    467                 task = taskA;
    468             }
    469
    470             task.setProject(getAntProject());
    471             task.setTaskName(taskName);
    472
    473             return task;
    474         }
    475         catch (Exception e) {
    476             log.log(Level.WARNING, "Could not create task: " + taskName + ". Reason: " + e, e);
    477             return null;
    478         }
    479     }
    480
    481     protected Task getParentTask() {
    482         Object current = getCurrent();
    483         if (current instanceof Task) {
    484             return (Task) current;
    485         }
    486         return null;
    487     }
    488 }