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 }