This has a lot of consequences, most notably those listed in WHATSNEW. This also affects the behavior of several other tasks like <available> who will now do their job at runtime instead of parser time as well. I've changed the version number in build.xml to reflect this bigger change. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@267988 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -3,6 +3,16 @@ Changes from Ant 1.1 to the current sources | |||||
| Changes that could break older environments: | Changes that could break older environments: | ||||
| -------------------------------------------- | -------------------------------------------- | ||||
| * Semantics of <property> has changed again in the hope to be more | |||||
| intuitive. ${} expansion now happens at runtime and <property> tags | |||||
| living inside of targets only take effect if they are visited at | |||||
| runtime. | |||||
| As a side effect of this change, task's attributes get set at runtime | |||||
| not at parser time as well, which might change the results of | |||||
| <script>s or other custom tasks that reference other tasks by their id | |||||
| attribute. | |||||
| * copying of support files in <javac> has been removed - as well as | * copying of support files in <javac> has been removed - as well as | ||||
| the filtering attribute. | the filtering attribute. | ||||
| @@ -12,7 +12,7 @@ | |||||
| <property name="Name" value="Ant"/> | <property name="Name" value="Ant"/> | ||||
| <property name="name" value="ant"/> | <property name="name" value="ant"/> | ||||
| <property name="version" value="1.2alpha"/> | |||||
| <property name="version" value="1.2alpha2"/> | |||||
| <property name="ant.home" value="."/> | <property name="ant.home" value="."/> | ||||
| <property name="src.bin.dir" value="src/bin"/> | <property name="src.bin.dir" value="src/bin"/> | ||||
| @@ -25,7 +25,7 @@ | |||||
| <li>Dave Walend (<a href="mailto:dwalend@cs.tufts.edu">dwalend@cs.tufts.edu</a>)</li> | <li>Dave Walend (<a href="mailto:dwalend@cs.tufts.edu">dwalend@cs.tufts.edu</a>)</li> | ||||
| </ul> | </ul> | ||||
| <p>Version 1.2 - 2000/09/07</p> | |||||
| <p>Version 1.2 - 2000/09/14</p> | |||||
| <hr> | <hr> | ||||
| <h2>Table of Contents</h2> | <h2>Table of Contents</h2> | ||||
| @@ -4778,8 +4778,44 @@ output. | |||||
| throws a <code>BuildException</code>. This method implements the task | throws a <code>BuildException</code>. This method implements the task | ||||
| itself.</li> | itself.</li> | ||||
| </ol> | </ol> | ||||
| <p>It is important to know that Ant first calls the setters for the attributes | |||||
| it encounters for a specific task in the buildfile, before it executes is.</p> | |||||
| <h3>The life cycle of a task</h3> | |||||
| <ol> | |||||
| <li>The task gets instantiated using a no-arg constructor at parser | |||||
| time. This means even tasks that are never executed get | |||||
| instantiated.</li> | |||||
| <li>The tasks gets references to its project and location inside the | |||||
| build file via their inherited <code>project</code> and | |||||
| <code>location</code> variables.</li> | |||||
| <li>If the user specified an id attribute to this task, the project | |||||
| registers a reference to this newly created task - at parser | |||||
| time.</li> | |||||
| <li><code>init()</code> is called at parser time.</li> | |||||
| <li>The task gets a reference to the target it belongs to via its | |||||
| inherited <code>target</code> variable.</li> | |||||
| <li>All child elements of the XML element corresponding to this task | |||||
| are created via this task's <code>createXXX()</code> methods or | |||||
| instantiated and added to this task via its <code>addXXX()</code> | |||||
| methods - at parser time.</li> | |||||
| <li>All attributes of this task get set via their corresponding | |||||
| <code>setXXX</code> methods - at runtime.</li> | |||||
| <li>The content character data sections inside the XML element | |||||
| corresponding to this task is added to the task via its | |||||
| <code>addText</code> method - at runtime.</li> | |||||
| <li>All attributes of all child elements get set via their corresponding | |||||
| <code>setXXX</code> methods - at runtime.</li> | |||||
| <li><code>execute()</code> is called at runtime.</li> | |||||
| </ol> | |||||
| <h3>Example</h3> | |||||
| <p>Let's write our own task, that prints a message on the System.out stream. The | <p>Let's write our own task, that prints a message on the System.out stream. The | ||||
| task has one attribute called "message".</p> | task has one attribute called "message".</p> | ||||
| <blockquote> | <blockquote> | ||||
| @@ -354,6 +354,7 @@ public class ProjectHelper { | |||||
| private class TaskHandler extends AbstractHandler { | private class TaskHandler extends AbstractHandler { | ||||
| private Target target; | private Target target; | ||||
| private Task task; | private Task task; | ||||
| private RuntimeConfigurable wrapper = null; | |||||
| public TaskHandler(DocumentHandler parentHandler, Target target) { | public TaskHandler(DocumentHandler parentHandler, Target target) { | ||||
| super(parentHandler); | super(parentHandler); | ||||
| @@ -363,35 +364,37 @@ public class ProjectHelper { | |||||
| public void init(String tag, AttributeList attrs) throws SAXParseException { | public void init(String tag, AttributeList attrs) throws SAXParseException { | ||||
| task = project.createTask(tag); | task = project.createTask(tag); | ||||
| configure(task, attrs); | |||||
| task.setLocation(new Location(buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber())); | task.setLocation(new Location(buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber())); | ||||
| configureId(task, attrs); | |||||
| task.init(); | task.init(); | ||||
| // Top level tasks don't have associated targets | // Top level tasks don't have associated targets | ||||
| if (target != null) { | if (target != null) { | ||||
| task.setOwningTarget(target); | task.setOwningTarget(target); | ||||
| target.addTask(task); | target.addTask(task); | ||||
| wrapper = task.getRuntimeConfigurableWrapper(); | |||||
| wrapper.setAttributes(attrs); | |||||
| } else { | } else { | ||||
| configure(task, attrs, project); | |||||
| task.execute(); | task.execute(); | ||||
| } | } | ||||
| } | } | ||||
| public void characters(char[] buf, int start, int end) throws SAXParseException { | public void characters(char[] buf, int start, int end) throws SAXParseException { | ||||
| String text = new String(buf, start, end).trim(); | |||||
| if (text.length() == 0) return; | |||||
| IntrospectionHelper ih = | |||||
| IntrospectionHelper.getHelper(task.getClass()); | |||||
| try { | |||||
| ih.addText(task, text); | |||||
| } catch (BuildException exc) { | |||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | |||||
| if (wrapper == null) { | |||||
| try { | |||||
| addText(task, buf, start, end); | |||||
| } catch (BuildException exc) { | |||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | |||||
| } | |||||
| } else { | |||||
| wrapper.addText(buf, start, end); | |||||
| } | } | ||||
| } | } | ||||
| public void startElement(String name, AttributeList attrs) throws SAXParseException { | public void startElement(String name, AttributeList attrs) throws SAXParseException { | ||||
| new NestedElementHandler(this, task).init(name, attrs); | |||||
| new NestedElementHandler(this, task, wrapper).init(name, attrs); | |||||
| } | } | ||||
| } | } | ||||
| @@ -401,11 +404,16 @@ public class ProjectHelper { | |||||
| private class NestedElementHandler extends AbstractHandler { | private class NestedElementHandler extends AbstractHandler { | ||||
| private Object target; | private Object target; | ||||
| private Object child; | private Object child; | ||||
| private RuntimeConfigurable parentWrapper; | |||||
| private RuntimeConfigurable childWrapper = null; | |||||
| public NestedElementHandler(DocumentHandler parentHandler, Object target) { | |||||
| public NestedElementHandler(DocumentHandler parentHandler, | |||||
| Object target, | |||||
| RuntimeConfigurable parentWrapper) { | |||||
| super(parentHandler); | super(parentHandler); | ||||
| this.target = target; | this.target = target; | ||||
| this.parentWrapper = parentWrapper; | |||||
| } | } | ||||
| public void init(String propType, AttributeList attrs) throws SAXParseException { | public void init(String propType, AttributeList attrs) throws SAXParseException { | ||||
| @@ -415,28 +423,34 @@ public class ProjectHelper { | |||||
| try { | try { | ||||
| child = ih.createElement(target, propType.toLowerCase()); | child = ih.createElement(target, propType.toLowerCase()); | ||||
| configure(child, attrs); | |||||
| configureId(child, attrs); | |||||
| if (parentWrapper != null) { | |||||
| childWrapper = new RuntimeConfigurable(child); | |||||
| childWrapper.setAttributes(attrs); | |||||
| parentWrapper.addChild(childWrapper); | |||||
| } else { | |||||
| configure(child, attrs, project); | |||||
| } | |||||
| } catch (BuildException exc) { | } catch (BuildException exc) { | ||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | throw new SAXParseException(exc.getMessage(), locator, exc); | ||||
| } | } | ||||
| } | } | ||||
| public void characters(char[] buf, int start, int end) throws SAXParseException { | public void characters(char[] buf, int start, int end) throws SAXParseException { | ||||
| String text = new String(buf, start, end).trim(); | |||||
| if (text.length() == 0) return; | |||||
| IntrospectionHelper ih = | |||||
| IntrospectionHelper.getHelper(child.getClass()); | |||||
| try { | |||||
| ih.addText(child, text); | |||||
| } catch (BuildException exc) { | |||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | |||||
| if (parentWrapper == null) { | |||||
| try { | |||||
| addText(child, buf, start, end); | |||||
| } catch (BuildException exc) { | |||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | |||||
| } | |||||
| } else { | |||||
| childWrapper.addText(buf, start, end); | |||||
| } | } | ||||
| } | } | ||||
| public void startElement(String name, AttributeList attrs) throws SAXParseException { | public void startElement(String name, AttributeList attrs) throws SAXParseException { | ||||
| new NestedElementHandler(this, child).init(name, attrs); | |||||
| new NestedElementHandler(this, child, childWrapper).init(name, attrs); | |||||
| } | } | ||||
| } | } | ||||
| @@ -457,32 +471,28 @@ public class ProjectHelper { | |||||
| throw new BuildException("Unknown data type "+propType); | throw new BuildException("Unknown data type "+propType); | ||||
| } | } | ||||
| configure(element, attrs); | |||||
| configureId(element, attrs); | |||||
| configure(element, attrs, project); | |||||
| } catch (BuildException exc) { | } catch (BuildException exc) { | ||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | throw new SAXParseException(exc.getMessage(), locator, exc); | ||||
| } | } | ||||
| } | } | ||||
| public void characters(char[] buf, int start, int end) throws SAXParseException { | public void characters(char[] buf, int start, int end) throws SAXParseException { | ||||
| String text = new String(buf, start, end).trim(); | |||||
| if (text.length() == 0) return; | |||||
| IntrospectionHelper ih = | |||||
| IntrospectionHelper.getHelper(element.getClass()); | |||||
| try { | try { | ||||
| ih.addText(element, text); | |||||
| addText(element, buf, start, end); | |||||
| } catch (BuildException exc) { | } catch (BuildException exc) { | ||||
| throw new SAXParseException(exc.getMessage(), locator, exc); | throw new SAXParseException(exc.getMessage(), locator, exc); | ||||
| } | } | ||||
| } | } | ||||
| public void startElement(String name, AttributeList attrs) throws SAXParseException { | public void startElement(String name, AttributeList attrs) throws SAXParseException { | ||||
| new NestedElementHandler(this, element).init(name, attrs); | |||||
| new NestedElementHandler(this, element, null).init(name, attrs); | |||||
| } | } | ||||
| } | } | ||||
| private void configure(Object target, AttributeList attrs) throws BuildException { | |||||
| public static void configure(Object target, AttributeList attrs, | |||||
| Project project) throws BuildException { | |||||
| if( target instanceof TaskAdapter ) | if( target instanceof TaskAdapter ) | ||||
| target=((TaskAdapter)target).getProxy(); | target=((TaskAdapter)target).getProxy(); | ||||
| @@ -498,18 +508,38 @@ public class ProjectHelper { | |||||
| attrs.getName(i).toLowerCase(), value); | attrs.getName(i).toLowerCase(), value); | ||||
| } catch (BuildException be) { | } catch (BuildException be) { | ||||
| if (attrs.getName(i).equals("id")) { | |||||
| project.addReference(attrs.getValue(i), target); | |||||
| } else { | |||||
| be.setLocation(new Location(buildFile.toString(), | |||||
| locator.getLineNumber(), | |||||
| locator.getColumnNumber())); | |||||
| // id attribute must be set externally | |||||
| if (!attrs.getName(i).equals("id")) { | |||||
| throw be; | throw be; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Adds the content of #PCDATA sections to an element. | |||||
| */ | |||||
| public static void addText(Object target, char[] buf, int start, int end) | |||||
| throws BuildException { | |||||
| addText(target, new String(buf, start, end).trim()); | |||||
| } | |||||
| /** | |||||
| * Adds the content of #PCDATA sections to an element. | |||||
| */ | |||||
| public static void addText(Object target, String text) | |||||
| throws BuildException { | |||||
| if (text == null || text.length() == 0) { | |||||
| return; | |||||
| } | |||||
| if(target instanceof TaskAdapter) | |||||
| target = ((TaskAdapter) target).getProxy(); | |||||
| IntrospectionHelper.getHelper(target.getClass()).addText(target, text); | |||||
| } | |||||
| /** Replace ${NAME} with the property value | /** Replace ${NAME} with the property value | ||||
| */ | */ | ||||
| @@ -562,4 +592,19 @@ public class ProjectHelper { | |||||
| return parserFactory; | return parserFactory; | ||||
| } | } | ||||
| /** | |||||
| * Scan AttributeList for the id attribute and maybe add a | |||||
| * reference to project. | |||||
| * | |||||
| * <p>Moved out of {@link #configure configure} to make it happen | |||||
| * at parser time.</p> | |||||
| */ | |||||
| private void configureId(Object target, AttributeList attr) { | |||||
| String id = attr.getValue("id"); | |||||
| if (id != null) { | |||||
| project.addReference(id, target); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,130 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 2000 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| package org.apache.tools.ant; | |||||
| import java.util.Enumeration; | |||||
| import java.util.Vector; | |||||
| import org.xml.sax.AttributeList; | |||||
| import org.xml.sax.helpers.AttributeListImpl; | |||||
| /** | |||||
| * Wrapper class that holds the attributes of a Task (or elements | |||||
| * nested below that level) and takes care of configuring that element | |||||
| * at runtime. | |||||
| * | |||||
| * @author <a href="stefan.bodewig@megabit.net">Stefan Bodewig</a> | |||||
| */ | |||||
| public class RuntimeConfigurable { | |||||
| private Vector children = new Vector(); | |||||
| private Object wrappedObject = null; | |||||
| private AttributeList attributes; | |||||
| private StringBuffer characters = new StringBuffer(); | |||||
| /** | |||||
| * @param proxy The element to wrap. | |||||
| */ | |||||
| public RuntimeConfigurable(Object proxy) { | |||||
| wrappedObject = proxy; | |||||
| } | |||||
| /** | |||||
| * Set's the attributes for the wrapped element. | |||||
| */ | |||||
| public void setAttributes(AttributeList attributes) { | |||||
| this.attributes = new AttributeListImpl(attributes); | |||||
| } | |||||
| /** | |||||
| * Adds child elements to the wrapped element. | |||||
| */ | |||||
| public void addChild(RuntimeConfigurable child) { | |||||
| children.addElement(child); | |||||
| } | |||||
| /** | |||||
| * Add characters from #PCDATA areas to the wrapped element. | |||||
| */ | |||||
| public void addText(String data) { | |||||
| characters.append(data); | |||||
| } | |||||
| /** | |||||
| * Add characters from #PCDATA areas to the wrapped element. | |||||
| */ | |||||
| public void addText(char[] buf, int start, int end) { | |||||
| addText(new String(buf, start, end).trim()); | |||||
| } | |||||
| /** | |||||
| * Configure the wrapped element and all children. | |||||
| */ | |||||
| public void maybeConfigure(Project p) throws BuildException { | |||||
| if (attributes != null) { | |||||
| ProjectHelper.configure(wrappedObject, attributes, p); | |||||
| attributes = null; | |||||
| } | |||||
| if (characters.length() != 0) { | |||||
| ProjectHelper.addText(wrappedObject, characters.toString()); | |||||
| characters.setLength(0); | |||||
| } | |||||
| Enumeration enum = children.elements(); | |||||
| while (enum.hasMoreElements()) { | |||||
| RuntimeConfigurable child = (RuntimeConfigurable) enum.nextElement(); | |||||
| child.maybeConfigure(p); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -138,6 +138,7 @@ public class Target { | |||||
| try { | try { | ||||
| project.fireTaskStarted(task); | project.fireTaskStarted(task); | ||||
| task.maybeConfigure(); | |||||
| task.execute(); | task.execute(); | ||||
| project.fireTaskFinished(task, null); | project.fireTaskFinished(task, null); | ||||
| } | } | ||||
| @@ -68,6 +68,7 @@ public abstract class Task { | |||||
| protected Location location = Location.UNKNOWN_LOCATION; | protected Location location = Location.UNKNOWN_LOCATION; | ||||
| protected String taskName = null; | protected String taskName = null; | ||||
| protected String taskType = null; | protected String taskType = null; | ||||
| protected RuntimeConfigurable wrapper; | |||||
| /** | /** | ||||
| * Sets the project object of this task. This method is used by | * Sets the project object of this task. This method is used by | ||||
| @@ -193,5 +194,24 @@ public abstract class Task { | |||||
| public void setLocation(Location location) { | public void setLocation(Location location) { | ||||
| this.location = location; | this.location = location; | ||||
| } | } | ||||
| /** | |||||
| * Returns the wrapper class for runtime configuration. | |||||
| */ | |||||
| public RuntimeConfigurable getRuntimeConfigurableWrapper() { | |||||
| if (wrapper == null) { | |||||
| wrapper = new RuntimeConfigurable(this); | |||||
| } | |||||
| return wrapper; | |||||
| } | |||||
| /** | |||||
| * Configure this task - if it hasn't been done already. | |||||
| */ | |||||
| public void maybeConfigure() throws BuildException { | |||||
| if (wrapper != null) { | |||||
| wrapper.maybeConfigure(project); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -94,7 +94,7 @@ public class Available extends Task { | |||||
| this.resource = resource; | this.resource = resource; | ||||
| } | } | ||||
| public void init() throws BuildException { | |||||
| public void execute() throws BuildException { | |||||
| if ((classname != null) && !checkClass(classname)) return; | if ((classname != null) && !checkClass(classname)) return; | ||||
| if ((file != null) && !checkFile(file)) return; | if ((file != null) && !checkFile(file)) return; | ||||
| if ((resource != null) && !checkResource(resource)) return; | if ((resource != null) && !checkResource(resource)) return; | ||||
| @@ -75,7 +75,7 @@ public class Filter extends Task { | |||||
| this.value = value; | this.value = value; | ||||
| } | } | ||||
| public void init() throws BuildException { | |||||
| public void execute() throws BuildException { | |||||
| project.addFilter(token, value); | project.addFilter(token, value); | ||||
| } | } | ||||
| } | } | ||||
| @@ -107,7 +107,7 @@ public class Property extends Task { | |||||
| return resource; | return resource; | ||||
| } | } | ||||
| public void init() throws BuildException { | |||||
| public void execute() throws BuildException { | |||||
| try { | try { | ||||
| if ((name != null) && (value != null)) { | if ((name != null) && (value != null)) { | ||||
| addProperty(name, value); | addProperty(name, value); | ||||
| @@ -67,7 +67,7 @@ import java.text.*; | |||||
| */ | */ | ||||
| public class Tstamp extends Task { | public class Tstamp extends Task { | ||||
| public void init() throws BuildException { | |||||
| public void execute() throws BuildException { | |||||
| try { | try { | ||||
| Date d = new Date(); | Date d = new Date(); | ||||