View Javadoc

1   /*
2    $Id: AntBuilder.java,v 1.8 2004/07/13 12:17:45 jstrachan 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.BuildException;
59  import org.apache.tools.ant.BuildLogger;
60  import org.apache.tools.ant.IntrospectionHelper;
61  import org.apache.tools.ant.NoBannerLogger;
62  import org.apache.tools.ant.Project;
63  import org.apache.tools.ant.Task;
64  import org.apache.tools.ant.TaskAdapter;
65  import org.apache.tools.ant.TaskContainer;
66  import org.apache.tools.ant.types.DataType;
67  import org.codehaus.groovy.ant.FileScanner;
68  import org.codehaus.groovy.runtime.InvokerHelper;
69  
70  /***
71   * Allows Ant tasks to be used with GroovyMarkup 
72   * 
73   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
74   * @version $Revision: 1.8 $
75   */
76  public class AntBuilder extends BuilderSupport {
77  
78      private static final Class[] addTaskParamTypes = { String.class };
79  
80      private Logger log = Logger.getLogger(getClass().getName());
81      private Project project;
82  
83      public AntBuilder() {
84          this.project = createProject();
85      }
86  
87      public AntBuilder(Project project) {
88          this.project = project;
89      }
90  
91      /***
92       * @return Factory method to create new Project instances
93       */
94      protected Project createProject() {
95          Project project = new Project();
96          BuildLogger logger = new NoBannerLogger();
97  
98          logger.setMessageOutputLevel(org.apache.tools.ant.Project.MSG_INFO);
99          logger.setOutputPrintStream(System.out);
100         logger.setErrorPrintStream(System.err);
101 
102         project.addBuildListener(logger);
103 
104         project.init();
105         project.getBaseDir();
106         return project;
107     }
108 
109     protected void setParent(Object parent, Object child) {
110     }
111 
112     protected void nodeCompleted(Object parent, Object node) {
113         if (node instanceof Task) {
114             Task task = (Task) node;
115             task.perform();
116         }
117     }
118 
119     protected Object createNode(Object tagName) {
120         return createNode(tagName.toString(), Collections.EMPTY_MAP);
121     }
122 
123     protected Object createNode(Object name, Object value) {
124         Object task = createNode(name);
125         setText(task, value.toString());
126         return task;
127     }
128 
129     protected Object createNode(Object name, Map attributes, Object value) {
130         Object task = createNode(name, attributes);
131         setText(task, value.toString());
132         return task;
133     }
134     
135     protected Object createNode(Object name, Map attributes) {
136 
137         if (name.equals("fileScanner")) {
138             return new FileScanner(project);
139         }
140         
141         String tagName = name.toString();
142         Object answer = null;
143 
144         Object parentObject = getCurrent();
145         Object parentTask = getParentTask();
146 
147         // lets assume that Task instances are not nested inside other Task instances
148         // for example <manifest> inside a <jar> should be a nested object, where as 
149         // if the parent is not a Task the <manifest> should create a ManifestTask
150         //
151         // also its possible to have a root Ant tag which isn't a task, such as when
152         // defining <fileset id="...">...</fileset>
153 
154         Object nested = null;
155         if (parentObject != null && !(parentTask instanceof TaskContainer)) {
156             nested = createNestedObject(parentObject, tagName);
157         }
158 
159         Task task = null;
160         if (nested == null) {
161             task = createTask(tagName);
162             if (task != null) {
163                 if (log.isLoggable(Level.FINE)) {
164                     log.fine("Creating an ant Task for name: " + tagName);
165                 }
166 
167                 // the following algorithm follows the lifetime of a tag
168                 // http://jakarta.apache.org/ant/manual/develop.html#writingowntask
169                 // kindly recommended by Stefan Bodewig
170 
171                 // create and set its project reference
172                 if (task instanceof TaskAdapter) {
173                     answer = ((TaskAdapter) task).getProxy();
174                 }
175                 else {
176                     answer = task;
177                 }
178 
179                 // set the task ID if one is given
180                 Object id = attributes.remove("id");
181                 if (id != null) {
182                     project.addReference((String) id, task);
183                 }
184 
185                 // now lets initialize
186                 task.init();
187 
188                 // now lets set any attributes of this tag...
189                 setBeanProperties(task, attributes);
190             }
191         }
192 
193         if (task == null) {
194             if (nested == null) {
195                 if (log.isLoggable(Level.FINE)) {
196                     log.fine("Trying to create a data type for tag: " + tagName);
197                 }
198                 nested = createDataType(tagName);
199             }
200             else {
201                 if (log.isLoggable(Level.FINE)) {
202                     log.fine("Created nested property tag: " + tagName);
203                 }
204             }
205 
206             if (nested != null) {
207                 answer = nested;
208 
209                 // set the task ID if one is given
210                 Object id = attributes.remove("id");
211                 if (id != null) {
212                     project.addReference((String) id, nested);
213                 }
214 
215                 try {
216                     InvokerHelper.setProperty(nested, "name", tagName);
217                 }
218                 catch (Exception e) {
219                 }
220 
221                 // now lets set any attributes of this tag...
222                 setBeanProperties(nested, attributes);
223 
224                 // now lets add it to its parent
225                 if (parentObject != null) {
226                     IntrospectionHelper ih = IntrospectionHelper.getHelper(parentObject.getClass());
227                     try {
228                         if (log.isLoggable(Level.FINE)) {
229                             log.fine(
230                                 "About to set the: "
231                                     + tagName
232                                     + " property on: "
233                                     + parentObject
234                                     + " to value: "
235                                     + nested
236                                     + " with type: "
237                                     + nested.getClass());
238                         }
239 
240                         ih.storeElement(project, parentObject, nested, tagName);
241                     }
242                     catch (Exception e) {
243                         log.log(Level.WARNING, "Caught exception setting nested: " + tagName, e);
244                     }
245 
246                     // now try to set the property for good measure
247                     // as the storeElement() method does not
248                     // seem to call any setter methods of non-String types
249                     try {
250                         InvokerHelper.setProperty(parentObject, tagName, nested);
251                     }
252                     catch (Exception e) {
253                         log.fine("Caught exception trying to set property: " + tagName + " on: " + parentObject);
254                     }
255                 }
256             }
257             else {
258                 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?");
259             }
260         }
261 
262         return answer;
263     }
264 
265     protected void setText(Object task, String text) {
266         // now lets set the addText() of the body content, if its applicaable
267         Method method = getAccessibleMethod(task.getClass(), "addText", addTaskParamTypes);
268         if (method != null) {
269             Object[] args = { text };
270             try {
271                 method.invoke(task, args);
272             }
273             catch (Exception e) {
274                 log.log(Level.WARNING, "Cannot call addText on: " + task + ". Reason: " + e, e);
275             }
276         }
277     }
278 
279     protected Method getAccessibleMethod(Class theClass, String name, Class[] paramTypes) {
280         while (true) {
281             try {
282                 Method answer = theClass.getDeclaredMethod(name, paramTypes);
283                 if (answer != null) {
284                     return answer;
285                 }
286             }
287             catch (Exception e) {
288                 // ignore
289             }
290             theClass = theClass.getSuperclass();
291             if (theClass == null) {
292                 return null;
293             }
294         }
295     }
296 
297     public Project getAntProject() {
298         return project;
299     }
300 
301     // Implementation methods
302     //-------------------------------------------------------------------------
303     protected void setBeanProperties(Object object, Map map) {
304         for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
305             Map.Entry entry = (Map.Entry) iter.next();
306             String name = (String) entry.getKey();
307             Object value = entry.getValue();
308             setBeanProperty(object, name, ((value == null) ? null : value.toString()));
309         }
310     }
311 
312     protected void setBeanProperty(Object object, String name, Object value) {
313         if (log.isLoggable(Level.FINE)) {
314             log.fine("Setting bean property on: " + object + " name: " + name + " value: " + value);
315         }
316 
317         IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
318 
319         if (value instanceof String) {
320             try {
321                 ih.setAttribute(getAntProject(), object, name.toLowerCase(), (String) value);
322                 return;
323             }
324             catch (Exception e) {
325                 // ignore: not a valid property
326             }
327         }
328 
329         try {
330 
331             ih.storeElement(getAntProject(), object, value, name);
332         }
333         catch (Exception e) {
334 
335             InvokerHelper.setProperty(object, name, value);
336         }
337     }
338 
339     /***
340      * Creates a nested object of the given object with the specified name
341      */
342     protected Object createNestedObject(Object object, String name) {
343         Object dataType = null;
344         if (object != null) {
345             IntrospectionHelper ih = IntrospectionHelper.getHelper(object.getClass());
346 
347             if (ih != null) {
348                 try {
349                     dataType = ih.createElement(getAntProject(), object, name.toLowerCase());
350                 }
351                 catch (BuildException be) {
352                     log.log(Level.SEVERE, "Caught: " + be, be);
353                 }
354             }
355         }
356         if (dataType == null) {
357             dataType = createDataType(name);
358         }
359         return dataType;
360     }
361 
362     protected Object createDataType(String name) {
363         Object dataType = null;
364 
365         Class type = (Class) getAntProject().getDataTypeDefinitions().get(name);
366 
367         if (type != null) {
368 
369             Constructor ctor = null;
370             boolean noArg = false;
371 
372             // DataType can have a "no arg" constructor or take a single
373             // Project argument.
374             try {
375                 ctor = type.getConstructor(new Class[0]);
376                 noArg = true;
377             }
378             catch (NoSuchMethodException nse) {
379                 try {
380                     ctor = type.getConstructor(new Class[] { Project.class });
381                     noArg = false;
382                 }
383                 catch (NoSuchMethodException nsme) {
384                     log.log(Level.INFO, "datatype '" + name + "' didn't have a constructor with an Ant Project", nsme);
385                 }
386             }
387 
388             if (noArg) {
389                 dataType = createDataType(ctor, new Object[0], name, "no-arg constructor");
390             }
391             else {
392                 dataType = createDataType(ctor, new Object[] { getAntProject()}, name, "an Ant project");
393             }
394             if (dataType != null) {
395                 ((DataType) dataType).setProject(getAntProject());
396             }
397         }
398 
399         return dataType;
400     }
401 
402     /***
403      * @return an object create with the given constructor and args.
404      * @param ctor a constructor to use creating the object
405      * @param args the arguments to pass to the constructor
406      * @param name the name of the data type being created
407      * @param argDescription a human readable description of the args passed
408      */
409     protected Object createDataType(Constructor ctor, Object[] args, String name, String argDescription) {
410         try {
411             Object datatype = ctor.newInstance(args);
412             return datatype;
413         }
414         catch (InstantiationException ie) {
415             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ie);
416         }
417         catch (IllegalAccessException iae) {
418             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, iae);
419         }
420         catch (InvocationTargetException ite) {
421             log.log(Level.SEVERE, "datatype '" + name + "' couldn't be created with " + argDescription, ite);
422         }
423         return null;
424     }
425 
426     /***
427      * @param taskName the name of the task to create
428      * @return a newly created task
429      */
430     protected Task createTask(String taskName) {
431         return createTask(taskName, (Class) getAntProject().getTaskDefinitions().get(taskName));
432     }
433 
434     protected Task createTask(String taskName, Class taskType) {
435         if (taskType == null) {
436             return null;
437         }
438         try {
439             Object o = taskType.newInstance();
440             Task task = null;
441             if (o instanceof Task) {
442                 task = (Task) o;
443             }
444             else {
445                 TaskAdapter taskA = new TaskAdapter();
446                 taskA.setProxy(o);
447                 task = taskA;
448             }
449 
450             task.setProject(getAntProject());
451             task.setTaskName(taskName);
452 
453             return task;
454         }
455         catch (Exception e) {
456             log.log(Level.WARNING, "Could not create task: " + taskName + ". Reason: " + e, e);
457             return null;
458         }
459     }
460 
461     protected Task getParentTask() {
462         Object current = getCurrent();
463         if (current instanceof Task) {
464             return (Task) current;
465         }
466         return null;
467     }
468 }