001 /*
002 $Id: AntBuilder.java,v 1.9 2004/12/13 23:48:21 glaforge Exp $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046 package groovy.util;
047
048
049 import java.lang.reflect.Constructor;
050 import java.lang.reflect.InvocationTargetException;
051 import java.lang.reflect.Method;
052 import java.util.Collections;
053 import java.util.Iterator;
054 import java.util.Map;
055 import java.util.logging.Level;
056 import java.util.logging.Logger;
057
058 import org.apache.tools.ant.*;
059 import org.apache.tools.ant.types.DataType;
060 import org.codehaus.groovy.ant.FileScanner;
061 import org.codehaus.groovy.runtime.InvokerHelper;
062
063 /**
064 * Allows Ant tasks to be used with GroovyMarkup
065 *
066 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>, changes by Dierk Koenig (dk)
067 * @version $Revision: 1.9 $
068 */
069 public class AntBuilder extends BuilderSupport {
070
071 private static final Class[] addTaskParamTypes = { String.class };
072
073 private Logger log = Logger.getLogger(getClass().getName());
074 private Project project;
075
076 public AntBuilder() {
077 this.project = createProject();
078 }
079
080 public AntBuilder(Project project) {
081 this.project = project;
082 }
083
084 // dk: introduced for convenience in subclasses
085 protected Project getProject() {
086 return project;
087 }
088
089 /**
090 * @return Factory method to create new Project instances
091 */
092 protected Project createProject() {
093 Project project = new Project();
094 BuildLogger logger = new NoBannerLogger();
095
096 logger.setMessageOutputLevel(org.apache.tools.ant.Project.MSG_INFO);
097 logger.setOutputPrintStream(System.out);
098 logger.setErrorPrintStream(System.err);
099
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 }