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(); | ||||