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 }