001 /*
002 $Id: AntBuilder.java,v 1.10 2005/09/13 11:36:29 dierk 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.10 $
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 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 }