View Javadoc

1   /*
2    $Id: AntBuilder.java,v 1.10 2005/09/13 11:36:29 dierk 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.10 $
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             String objStr;
331             try {
332                 objStr = object.toString(); // e.g. Fileset may throw Exception here when 'dir' is not yet set
333             } catch (Exception e) {
334                 objStr = object.getClass().getName();
335             }
336             log.fine("Setting bean property on: " + objStr + " name: " + name + " value: " + value);
337         }
338 
339         IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
340 
341         if (value instanceof String) {
342             try {
343                 ih.setAttribute(getAntProject(), object, name.toLowerCase(), (String) value);
344                 return;
345             }
346             catch (Exception e) {
347                 // ignore: not a valid property
348             }
349         }
350 
351         try {
352 
353             ih.storeElement(getAntProject(), object, value, name);
354         }
355         catch (Exception e) {
356 
357             InvokerHelper.setProperty(object, name, value);
358         }
359     }
360 
361     /***
362      * Creates a nested object of the given object with the specified name
363      */
364     protected Object createNestedObject(Object object, String name) {
365         Object dataType = null;
366         if (object != null) {
367             IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
368 
369             if (ih != null) {
370                 try {
371                     // dk: the line below resolves the deprecation warning but may not work
372                     // properly with namespaces.
373                     String namespaceUri = "";               // todo: how to set this?
374                     UnknownElement unknownElement = null;   // todo: what is expected here?
375                     dataType = ih.getElementCreator(getAntProject(), namespaceUri, object, name.toLowerCase(), unknownElement).create();
376                 }
377                 catch (BuildException be) {
378                     log.log(Level.SEVERE, "Caught: " + be, be);
379                 }
380             }
381         }
382         if (dataType == null) {
383             dataType = createDataType(name);
384         }
385         return dataType;
386     }
387 
388     protected Object createDataType(String name) {
389         Object dataType = null;
390 
391         Class type = (Class) getAntProject().getDataTypeDefinitions().get(name);
392 
393         if (type != null) {
394 
395             Constructor ctor = null;
396             boolean noArg = false;
397 
398             // DataType can have a "no arg" constructor or take a single
399             // Project argument.
400             try {
401                 ctor = type.getConstructor(new Class[0]);
402                 noArg = true;
403             }
404             catch (NoSuchMethodException nse) {
405                 try {
406                     ctor = type.getConstructor(new Class[] { Project.class });
407                     noArg = false;
408                 }
409                 catch (NoSuchMethodException nsme) {
410                     log.log(Level.INFO, "datatype '" + name + "' didn't have a constructor with an Ant Project", nsme);
411                 }
412             }
413 
414             if (noArg) {
415                 dataType = createDataType(ctor, new Object[0], name, "no-arg constructor");
416             }
417             else {
418                 dataType = createDataType(ctor, new Object[] { getAntProject()}, name, "an Ant project");
419             }
420             if (dataType != null) {
421                 ((DataType) dataType).setProject(getAntProject());
422             }
423         }
424 
425         return dataType;
426     }
427 
428     /***
429      * @return an object create with the given constructor and args.
430      * @param ctor a constructor to use creating the object
431      * @param args the arguments to pass to the constructor
432      * @param name the name of the data type being created
433      * @param argDescription a human readable description of the args passed
434      */
435     protected Object createDataType(Constructor ctor, Object[] args, String name, String argDescription) {
436         try {
437             Object datatype = ctor.newInstance(args);
438             return datatype;
439         }
440         catch (InstantiationException ie) {
441             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ie);
442         }
443         catch (IllegalAccessException iae) {
444             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, iae);
445         }
446         catch (InvocationTargetException ite) {
447             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ite);
448         }
449         return null;
450     }
451 
452     /***
453      * @param taskName the name of the task to create
454      * @return a newly created task
455      */
456     protected Task createTask(String taskName) {
457         return createTask(taskName, (Class) getAntProject().getTaskDefinitions().get(taskName));
458     }
459 
460     protected Task createTask(String taskName, Class taskType) {
461         if (taskType == null) {
462             return null;
463         }
464         try {
465             Object o = taskType.newInstance();
466             Task task = null;
467             if (o instanceof Task) {
468                 task = (Task) o;
469             }
470             else {
471                 TaskAdapter taskA = new TaskAdapter();
472                 taskA.setProxy(o);
473                 task = taskA;
474             }
475 
476             task.setProject(getAntProject());
477             task.setTaskName(taskName);
478 
479             return task;
480         }
481         catch (Exception e) {
482             log.log(Level.WARNING, "Could not create task: " + taskName + ". Reason: " + e, e);
483             return null;
484         }
485     }
486 
487     protected Task getParentTask() {
488         Object current = getCurrent();
489         if (current instanceof Task) {
490             return (Task) current;
491         }
492         return null;
493     }
494 }