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 }