diff --git a/WHATSNEW b/WHATSNEW
index 96e2f2b94..fc5c90ed2 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -63,6 +63,10 @@ Other changes:
* has a new attribute pathref that can be used to reference
previously defined paths.
+* has been improved, you can now expand ${properties},
+ define ids or paths and use Ant's location magic for filename resolutions
+ in the XML file.
+
Changes from Ant 1.5.1Beta1 to 1.5.1
====================================
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-input1.properties
new file mode 100644
index 000000000..bed8609b8
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-input1.properties
@@ -0,0 +1,6 @@
+properties.root=foo,bar
+properties.a.b.c=d
+properties.a.b=e
+properties.foo.bar=quux,quux1
+properties.foo.quux=bar
+properties.tag.value=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-override.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-override.properties
new file mode 100644
index 000000000..5d4d70643
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-override.properties
@@ -0,0 +1,2 @@
+# Match value hardwired in code, NOT in the input...
+override.property.test=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-nocollapse-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-nocollapse-input1.properties
new file mode 100644
index 000000000..153ac765d
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-nocollapse-input1.properties
@@ -0,0 +1,8 @@
+properties.root=foo,bar
+properties.a.b(c)=d
+properties.a.b=e
+properties.foo(bar)=quux
+properties.foo.bar=quux1
+properties.foo.quux=bar
+properties.tag(value)=foo
+
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-include.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-include.properties
new file mode 100644
index 000000000..bed8609b8
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-include.properties
@@ -0,0 +1,6 @@
+properties.root=foo,bar
+properties.a.b.c=d
+properties.a.b=e
+properties.foo.bar=quux,quux1
+properties.foo.quux=bar
+properties.tag.value=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-input1.properties
new file mode 100644
index 000000000..50cc98184
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-input1.properties
@@ -0,0 +1,6 @@
+properties.root=foo,bar
+properties.a.b.c=d
+properties.a.b=e
+properties.foo.bar=quux,quux1
+properties.foo.quux=bar
+properties.tag=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-override.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-override.properties
new file mode 100644
index 000000000..5d4d70643
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-override.properties
@@ -0,0 +1,2 @@
+# Match value hardwired in code, NOT in the input...
+override.property.test=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-collapse-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-collapse-input1.properties
new file mode 100644
index 000000000..d6640f40f
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-collapse-input1.properties
@@ -0,0 +1,6 @@
+root=foo,bar
+a.b.c=d
+a.b=e
+foo.bar=quux,quux1
+foo.quux=bar
+tag.value=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-input1.properties
new file mode 100644
index 000000000..3eca36838
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-input1.properties
@@ -0,0 +1,7 @@
+root=foo,bar
+a.b(c)=d
+a.b=e
+foo(bar)=quux
+foo.bar=quux1
+foo.quux=bar
+tag(value)=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-multi.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-multi.properties
new file mode 100644
index 000000000..9dd7a54ff
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-multi.properties
@@ -0,0 +1 @@
+foo.bar=1,2,3,4
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-include-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-include-input1.properties
new file mode 100644
index 000000000..d6640f40f
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-include-input1.properties
@@ -0,0 +1,6 @@
+root=foo,bar
+a.b.c=d
+a.b=e
+foo.bar=quux,quux1
+foo.quux=bar
+tag.value=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-input1.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-input1.properties
new file mode 100644
index 000000000..f041c804d
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-input1.properties
@@ -0,0 +1,6 @@
+root=foo,bar
+a.b.c=d
+a.b=e
+foo.bar=quux,quux1
+foo.quux=bar
+tag=foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-locations.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-locations.properties
new file mode 100644
index 000000000..fdf835aeb
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-locations.properties
@@ -0,0 +1 @@
+file=FILE.foo
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-paths.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-paths.properties
new file mode 100644
index 000000000..b856cd77b
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-paths.properties
@@ -0,0 +1 @@
+foo=ID.path
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-references.properties b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-references.properties
new file mode 100644
index 000000000..e03249b45
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-references.properties
@@ -0,0 +1,5 @@
+property=foo
+foo.bar=foo
+foo.quux=foo
+foo.thunk=foo
+foo.property=ID.foo
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/input1.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/input1.xml
new file mode 100644
index 000000000..5c977cbbf
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/input1.xml
@@ -0,0 +1,10 @@
+
+ foo
+ bar
+ e
+
+ quux1
+ bar
+
+
+
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/locations.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/locations.xml
new file mode 100644
index 000000000..60f855aae
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/locations.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/multi.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/multi.xml
new file mode 100644
index 000000000..9d2c9a12f
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/multi.xml
@@ -0,0 +1,8 @@
+
+
+ 1
+ 2
+ 3
+ 4
+
+
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/override.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/override.xml
new file mode 100644
index 000000000..9ae88991f
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/override.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/paths.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/paths.xml
new file mode 100644
index 000000000..e08270d9b
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/etc/testcases/taskdefs/xmlproperty/inputs/references.xml b/src/etc/testcases/taskdefs/xmlproperty/inputs/references.xml
new file mode 100644
index 000000000..a7274bc25
--- /dev/null
+++ b/src/etc/testcases/taskdefs/xmlproperty/inputs/references.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ ${property}
+
+
\ No newline at end of file
diff --git a/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java b/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java
index aa2af432c..90ec7f48c 100644
--- a/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java
+++ b/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java
@@ -58,36 +58,155 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Enumeration;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.util.FileUtils;
import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
- * Loads property values from a valid XML file,
- * generating the property names from the file's element and attribute names.
+ * Loads property values from a valid XML file, generating the
+ * property names from the file's element and attribute names.
*
- * Example:
+ * Example:
*
* <root-tag myattr="true">
* <inner-tag someattr="val">Text</inner-tag>
* <a2><a3><a4>false</a4></a3></a2>
+ * <x>x1</x>
+ * <x>x2</x>
* </root-tag>
*
- * this generates
+ *
+ * this generates the following properties:
+ *
*
* root-tag(myattr)=true
* root-tag.inner-tag=Text
* root-tag.inner-tag(someattr)=val
* root-tag.a2.a3.a4=false
+ * root-tag.x=x1,x2
+ *
+ *
+ * The collapseAttributes property of this task can be set
+ * to true (the default is false) which will instead result in the
+ * following properties (note the difference in names of properties
+ * corresponding to XML attributes):
+ *
+ *
+ * root-tag.myattr=true
+ * root-tag.inner-tag=Text
+ * root-tag.inner-tag.someattr=val
+ * root-tag.a2.a3.a4=false
+ * root-tag.x=x1,x2
+ *
+ *
+ * Optionally, to more closely mirror the abilities of the Property
+ * task, a selected set of attributes can be treated specially. To
+ * enable this behavior, the "semanticAttribute" property of this task
+ * must be set to true (it defaults to false). If this attribute is
+ * specified, the following attributes take on special meaning
+ * (setting this to true implicitly sets collapseAttributes to true as
+ * well):
+ *
+ *
+ * - value: Identifies a text value for a property.
+ * - location: Identifies a file location for a property.
+ * - id: Sets an id for a property
+ * - refid: Sets a property to the value of another property
+ * based upon the provided id
+ * - pathid: Defines a path rather than a property with
+ * the given id.
+ *
+ *
+ * For example, with keepRoot = false, the following properties file:
+ *
+ *
+ * <root-tag>
+ * <build location="build">
+ * <classes id="build.classes" location="${build.location}/classes"/>
+ * <reference refid="build.location"/>
+ * </build>
+ * <compile>
+ * <classpath pathid="compile.classpath">
+ * <pathelement location="${build.classes}"/>
+ * </classpath>
+ * </compile>
+ * <run-time>
+ * <jars>*.jar</jars>
+ * <classpath pathid="run-time.classpath">
+ * <path refid="compile.classpath"/>
+ * <pathelement path="${run-time.jars}"/>
+ * </classpath>
+ * </run-time>
+ * </root-tag>
*
+ *
+ * is equivalent to the following entries in a build file:
+ *
+ *
+ * <property name="build.location" location="build"/>
+ * <property name="build.classes.location" location="${build.location}/classes"/>
+ * <property name="build.reference" refid="build.location"/>
+ *
+ * <property name="run-time.jars" value="*.jar/>
+ *
+ * <classpath id="compile.classpath">
+ * <pathelement location="${build.classes}"/>
+ * </classpath>
+ *
+ * <classpath id="run-time.classpath">
+ * <path refid="compile.classpath"/>
+ * <pathelement path="${run-time.jars}"/>
+ * </classpath>
+ *
+ *
+ * This task requires the following attributes:
+ *
+ *
+ * - file: The name of the file to load.
+ *
+ *
+ * This task supports the following attributes:
+ *
+ *
+ * - prefix: Optionally specify a prefix applied to
+ * all properties loaded. Defaults to an empty string.
+ * - keepRoot: Indicate whether the root xml element
+ * is kept as part of property name. Defaults to true.
+ * - validate: Indicate whether the xml file is validated.
+ * Defaults to false.
+ * - collapseAttributes: Indicate whether attributes are
+ * stored in property names with parens or with period
+ * delimiters. Defaults to false, meaning properties
+ * are stored with parens (i.e., foo(attr)).
+ * - semanticAttributes: Indicate whether attributes
+ * named "location", "value", "refid" and "path"
+ * are interpreted as ant properties. Defaults
+ * to true.
+ * - rootDirectory: Indicate the directory to use
+ * as the root directory for resolving location
+ * properties. Defaults to the directory
+ * of the project using the task.
+ * - includeSemanticAttribute: Indicate whether to include
+ * the semanticAttribute ("location" or "value") as
+ * part of the property name. Defaults to false.
+ *
+ *
* @author Nicola Ken Barozzi
* @author Erik Hatcher
+ * @author Paul Christmann
+ *
* @ant.task name="xmlproperty" category="xml"
*/
@@ -98,7 +217,21 @@ public class XmlProperty extends org.apache.tools.ant.Task {
private boolean keepRoot = true;
private boolean validate = false;
private boolean collapseAttributes = false;
- private org.w3c.dom.Document document;
+ private boolean semanticAttributes = false;
+ private boolean includeSemanticAttribute = false;
+ private File rootDirectory = null;
+ private FileUtils fileUtils = FileUtils.newFileUtils();
+ private Hashtable addedAttributes = new Hashtable();
+
+ private static final String ID = "id";
+ private static final String REF_ID = "refid";
+ private static final String LOCATION = "location";
+ private static final String VALUE = "value";
+ private static final String PATH = "path";
+ private static final String PATHID = "pathid";
+ private static final String[] ATTRIBUTES = new String[] {
+ ID, REF_ID, LOCATION, VALUE, PATH, PATHID
+ };
/**
* Constructor.
@@ -124,6 +257,11 @@ public class XmlProperty extends org.apache.tools.ant.Task {
public void execute()
throws BuildException {
+ if (getFile() == null) {
+ String msg = "XmlProperty task requires a file attribute";
+ throw new BuildException(msg);
+ }
+
BufferedInputStream configurationStream = null;
try {
@@ -135,21 +273,17 @@ public class XmlProperty extends org.apache.tools.ant.Task {
factory.setValidating(validate);
factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- document = builder.parse(configurationStream);
-
- Element topElement = document.getDocumentElement();
- NodeList topChildren = topElement.getChildNodes();
- int numChildren = topChildren.getLength();
+ Element topElement = factory.newDocumentBuilder().parse(configurationStream).getDocumentElement();
- log("Using prefix: \"" + prefix + "\"", Project.MSG_DEBUG);
+ addedAttributes = new Hashtable();
if (keepRoot) {
- addNodeRecursively(topElement, prefix);
- }
- else {
+ addNodeRecursively(topElement, prefix, null);
+ } else {
+ NodeList topChildren = topElement.getChildNodes();
+ int numChildren = topChildren.getLength();
for (int i = 0; i < numChildren; i++) {
- addNodeRecursively(topChildren.item(i), prefix);
+ addNodeRecursively(topChildren.item(i), prefix, null);
}
}
@@ -176,51 +310,251 @@ public class XmlProperty extends org.apache.tools.ant.Task {
}
}
+ /** Iterate through all nodes in the tree. */
+ private void addNodeRecursively(Node node, String prefix,
+ Object container) {
+
+ // Set the prefix for this node to include its tag name.
+ String nodePrefix = prefix;
+ if (node.getNodeType() != Node.TEXT_NODE) {
+ if (prefix.trim().length() > 0) {
+ nodePrefix += ".";
+ }
+ nodePrefix += node.getNodeName();
+ }
+
+ // Pass the container to the processing of this node,
+ Object nodeObject = processNode(node, nodePrefix, container);
+
+ // now, iterate through children.
+ if (node.hasChildNodes()) {
+
+ NodeList nodeChildren = node.getChildNodes();
+ int numChildren = nodeChildren.getLength();
+
+ for (int i = 0; i < numChildren; i++) {
+ // For each child, pass the object added by
+ // processNode to its children -- in other word, each
+ // object can pass information along to its children.
+ addNodeRecursively(nodeChildren.item(i), nodePrefix,
+ nodeObject);
+ }
+ }
+ }
+
+ void addNodeRecursively(org.w3c.dom.Node node, String prefix) {
+ addNodeRecursively(node, prefix, null);
+ }
+
/**
- * add all attributes of a node, and its inner text, and then recursively add all nested elements
+ * Process the given node, adding any required attributes from
+ * this child node alone -- but not processing any
+ * children.
+ *
+ * @param node the XML Node to parse
+ * @param prefix A string to prepend to any properties that get
+ * added by this node.
+ * @param container Optionally, an object that a parent node
+ * generated that this node might belong to. For example, this
+ * node could be within a node that generated a Path.
+ * @return the Object created by this node. Generally, this is
+ * either a String if this node resulted in setting an attribute,
+ * or a Path.
*/
+ public Object processNode (Node node, String prefix, Object container) {
- void addNodeRecursively(org.w3c.dom.Node node, String prefix) {
+ // Parse the attribute(s) and text of this node, adding
+ // properties for each.
+ // if the "path" attribute is specified, then return the created path
+ // which will be passed to the children of this node.
+ Object addedPath = null;
+
+ // The value of an id attribute of this node.
+ String id = null;
if (node.hasAttributes()) {
- org.w3c.dom.NamedNodeMap nodeAttributes = node.getAttributes();
+
+ NamedNodeMap nodeAttributes = node.getAttributes();
+
+ // Is there an id attribute?
+ Node idNode = nodeAttributes.getNamedItem(ID);
+ id = (semanticAttributes && idNode != null
+ ? idNode.getNodeValue() : null);
+
+ // Now, iterate through the attributes adding them.
for (int i = 0; i < nodeAttributes.getLength(); i++) {
+
Node attributeNode = nodeAttributes.item(i);
- String attributeName;
- if(collapseAttributes){
- attributeName = prefix + (prefix.trim().equals("")?"":".") + node.getNodeName() + "." + attributeNode.getNodeName();
- }
- else{
- attributeName = prefix + (prefix.trim().equals("")?"":".") + node.getNodeName() + "(" + attributeNode.getNodeName() + ")";
+ if (!semanticAttributes) {
+ String attributeName = getAttributeName(attributeNode);
+ String attributeValue = getAttributeValue(attributeNode);
+ addProperty(prefix + attributeName, attributeValue, null);
+ } else {
+
+ String nodeName = attributeNode.getNodeName();
+ String attributeValue = getAttributeValue(attributeNode);
+
+ Path containingPath =
+ (container != null && container instanceof Path
+ ? (Path) container : null );
+
+ /*
+ * The main conditional logic -- if the attribute
+ * is somehow "special" (i.e., it has known
+ * semantic meaning) then deal with it
+ * appropriately.
+ */
+ if (nodeName.equals(ID)) {
+ // ID has already been found above.
+ continue;
+ } else if (containingPath != null
+ && nodeName.equals(PATH)) {
+ // A "path" attribute for a node within a Path object.
+ containingPath.setPath(attributeValue);
+ } else if (container instanceof Path
+ && nodeName.equals(REF_ID)) {
+ // A "refid" attribute for a node within a Path object.
+ containingPath.setPath(attributeValue);
+ } else if (container instanceof Path
+ && nodeName.equals(LOCATION)) {
+ // A "location" attribute for a node within a
+ // Path object.
+ containingPath.setLocation(resolveFile(attributeValue));
+ } else if (nodeName.equals(PATHID)) {
+ // A node identifying a new path
+ if (container != null) {
+ throw new BuildException("XmlProperty does not "
+ + "support nested paths");
+ }
+
+ addedPath = new Path(getProject());
+ getProject().addReference(attributeValue, addedPath);
+ } else {
+ // An arbitrary attribute.
+ String attributeName = getAttributeName(attributeNode);
+ addProperty(prefix + attributeName, attributeValue, id);
+ }
}
-
- String attributeValue = attributeNode.getNodeValue();
- log(attributeName + ":" + attributeValue, Project.MSG_DEBUG);
- getProject().setNewProperty(attributeName, attributeValue);
}
}
if (node.getNodeType() == Node.TEXT_NODE) {
- String nodeText = node.getNodeValue();
+ // If the containing object was a String, then use it as the ID.
+ if (semanticAttributes && id == null
+ && container instanceof String) {
+ id = (String) container;
+ }
+ // For the text node, add a property.
+ String nodeText = getAttributeValue(node);
if (nodeText.trim().length() != 0) {
- log(prefix + ":" + nodeText, Project.MSG_DEBUG);
- getProject().setNewProperty(prefix, nodeText);
+ addProperty(prefix, nodeText, id);
}
}
- if (node.hasChildNodes()) {
- prefix += ((prefix.trim().equals("")?"":".") + node.getNodeName());
+ // Return the Path we added or the ID of this node for
+ // children to reference if needed. Path objects are
+ // definitely used by child path elements, and ID may be used
+ // for a child text node.
+ return (addedPath != null ? addedPath : id);
+ }
- org.w3c.dom.NodeList nodeChildren = node.getChildNodes();
- int numChildren = nodeChildren.getLength();
+ /**
+ * Actually add the given property/value to the project
+ * after writing a log message.
+ */
+ private void addProperty (String name, String value, String id) {
+ String msg = name + ":" + value;
+ if (id != null) {
+ msg += ("(id=" + id + ")");
+ }
+ log(msg, Project.MSG_DEBUG);
+
+ if (addedAttributes.containsKey(name)) {
+ // If this attribute was added by this task, then
+ // we append this value to the existing value.
+ value = (String)addedAttributes.get(name) + "," + value;
+ getProject().setProperty(name, value);
+ } else {
+ getProject().setNewProperty(name, value);
+ }
+ addedAttributes.put(name, value);
+ if (id != null) {
+ getProject().addReference(id, value);
+ }
+ }
- for (int i = 0; i < numChildren; i++) {
- addNodeRecursively(nodeChildren.item(i), prefix);
+ /**
+ * Return a reasonable attribute name for the given node.
+ * If we are using semantic attributes or collapsing
+ * attributes, the returned name is ".nodename".
+ * Otherwise, we return "(nodename)". This is long-standing
+ * (and default) <xmlproperty> behavior.
+ */
+ private String getAttributeName (Node attributeNode) {
+ String attributeName = attributeNode.getNodeName();
+
+ if (semanticAttributes) {
+ // Never include the "refid" attribute as part of the
+ // attribute name.
+ if (attributeName.equals(REF_ID)) {
+ return "";
+ // Otherwise, return it appended unless property to hide it is set.
+ } else if (!isSemanticAttribute(attributeName)
+ || includeSemanticAttribute) {
+ return "." + attributeName;
+ } else {
+ return "";
}
+ } else if (collapseAttributes) {
+ return "." + attributeName;
+ } else {
+ return "(" + attributeName + ")";
}
}
+ /**
+ * Return whether the provided attribute name is recognized or not.
+ */
+ private static boolean isSemanticAttribute (String attributeName) {
+ for (int i=0;iIf we are using semantic attributes, then first
+ * dependent properties are resolved (i.e., ${foo} is resolved
+ * based on the foo property value), and then an appropriate data
+ * type is used. In particular, location-based properties are
+ * resolved to absolute file names. Also for refid values, look
+ * up the referenced object from the project.
+ */
+ private String getAttributeValue (Node attributeNode) {
+ String nodeValue = attributeNode.getNodeValue().trim();
+ if (semanticAttributes) {
+ String attributeName = attributeNode.getNodeName();
+ nodeValue = getProject().replaceProperties(nodeValue);
+ if (attributeName.equals(LOCATION)) {
+ File f = resolveFile(nodeValue);
+ return f.getPath();
+ } else if (attributeName.equals(REF_ID)) {
+ Object ref = getProject().getReference(nodeValue);
+ if (ref != null) {
+ return ref.toString();
+ }
+ }
+ }
+ return nodeValue;
+ }
+
/**
* The XML file to parse; required.
*/
@@ -259,4 +593,61 @@ public class XmlProperty extends org.apache.tools.ant.Task {
this.collapseAttributes = collapseAttributes;
}
+ public void setSemanticAttributes (boolean semanticAttributes) {
+ this.semanticAttributes = semanticAttributes;
+ }
+
+ public void setRootDirectory (File rootDirectory) {
+ this.rootDirectory = rootDirectory;
+ }
+
+ public void setIncludeSemanticAttribute (boolean includeSemanticAttribute) {
+ this.includeSemanticAttribute = includeSemanticAttribute;
+ }
+
+ /* Expose members for extensibility */
+
+ protected File getFile () {
+ return this.src;
+ }
+
+ protected String getPrefix () {
+ return this.prefix;
+ }
+
+ protected boolean getKeeproot () {
+ return this.keepRoot;
+ }
+
+ protected boolean getValidate () {
+ return this.validate;
+ }
+
+ protected boolean getCollapseAttributes () {
+ return this.collapseAttributes;
+ }
+
+ protected boolean getSemanticAttributes () {
+ return this.semanticAttributes;
+ }
+
+ protected File getRootDirectory () {
+ return this.rootDirectory;
+ }
+
+ protected boolean getIncludeSementicAttribute () {
+ return this.includeSemanticAttribute;
+ }
+
+ /**
+ * Let project resolve the file - or do it ourselves if
+ * rootDirectory has been set.
+ */
+ private File resolveFile(String fileName) {
+ if (rootDirectory == null) {
+ return getProject().resolveFile(fileName);
+ }
+ return fileUtils.resolveFile(rootDirectory, fileName);
+ }
+
}
diff --git a/src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java b/src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java
index 98d35d840..f424d1f32 100644
--- a/src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java
+++ b/src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java
@@ -54,12 +54,26 @@
package org.apache.tools.ant.taskdefs;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
import org.apache.tools.ant.BuildFileTest;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.util.FileUtils;
/**
* @author Erik Hatcher
+ * @author Paul Christmann
*/
public class XmlPropertyTest extends BuildFileTest {
+ private static FileUtils fileUtils = FileUtils.newFileUtils();
public XmlPropertyTest(String name) {
super(name);
@@ -69,14 +83,288 @@ public class XmlPropertyTest extends BuildFileTest {
configureProject("src/etc/testcases/taskdefs/xmlproperty.xml");
}
-
public void testProperties() {
executeTarget("test");
-// System.out.println(this.getFullLog());
- assertEquals("true", project.getProperty("root-tag(myattr)"));
- assertEquals("Text", project.getProperty("root-tag.inner-tag"));
- assertEquals("val", project.getProperty("root-tag.inner-tag(someattr)"));
- assertEquals("false", project.getProperty("root-tag.a2.a3.a4"));
+ assertEquals("true", getProject().getProperty("root-tag(myattr)"));
+ assertEquals("Text", getProject().getProperty("root-tag.inner-tag"));
+ assertEquals("val",
+ getProject().getProperty("root-tag.inner-tag(someattr)"));
+ assertEquals("false", getProject().getProperty("root-tag.a2.a3.a4"));
+ }
+
+ public void testNone () {
+ doTest("testNone", false, false, false, false, false);
+ }
+
+ public void testKeeproot() {
+ doTest("testKeeproot", true, false, false, false, false);
+ }
+
+ public void testCollapse () {
+ doTest("testCollapse", false, true, false, false, false);
+ }
+
+ public void testSemantic () {
+ doTest("testSemantic", false, false, true, false, false);
+ }
+
+ public void testKeeprootCollapse () {
+ doTest("testKeeprootCollapse", true, true, false, false, false);
+ }
+
+ public void testKeeprootSemantic () {
+ doTest("testKeeprootSemantic", true, false, true, false, false);
+ }
+
+ public void testCollapseSemantic () {
+ doTest("testCollapseSemantic", false, true, true, false, false);
+ }
+
+ public void testKeeprootCollapseSemantic () {
+ doTest("testKeeprootCollapseSemantic", true, true, true, false, false);
+ }
+
+ public void testInclude () {
+ doTest("testInclude", false, false, false, true, false);
+ }
+
+ public void testSemanticInclude () {
+ doTest("testSemanticInclude", false, false, true, true, false);
+ }
+
+ public void testSemanticLocal () {
+ doTest("testSemanticInclude", false, false, true, false, true);
+ }
+
+ /**
+ * Actually run a test, finding all input files (and corresponding
+ * goldfile)
+ */
+ private void doTest(String msg, boolean keepRoot, boolean collapse,
+ boolean semantic, boolean include, boolean localRoot) {
+ Enumeration iter =
+ getFiles(new File("src/etc/testcases/taskdefs/xmlproperty/inputs"));
+ while (iter.hasMoreElements()) {
+ File inputFile = (File) iter.nextElement();
+ // What's the working directory? If local, then its the
+ // folder of the input file. Otherwise, its the "current" dir..
+ File workingDir;
+ if ( localRoot ) {
+ workingDir = fileUtils.getParentFile(inputFile);
+ } else {
+ workingDir = fileUtils.resolveFile(new File("."), ".");
+ }
+
+ try {
+
+ File propertyFile = getGoldfile(inputFile, keepRoot, collapse,
+ semantic, include, localRoot);
+ if (!propertyFile.exists()) {
+// System.out.println("Skipping as "
+// + propertyFile.getAbsolutePath()
+// + ") doesn't exist.");
+ continue;
+ }
+
+ // System.out.println(msg + " (" + propertyFile.getName() + ") in (" + workingDir + ")");
+
+ Project project = new Project();
+
+ XmlProperty xmlproperty = new XmlProperty();
+ xmlproperty.setProject(project);
+ xmlproperty.setFile(inputFile);
+
+ xmlproperty.setKeeproot(keepRoot);
+ xmlproperty.setCollapseAttributes(collapse);
+ xmlproperty.setSemanticAttributes(semantic);
+ xmlproperty.setIncludeSemanticAttribute(include);
+ xmlproperty.setRootDirectory(workingDir);
+
+ project.setNewProperty("override.property.test", "foo");
+ xmlproperty.execute();
+
+ Properties props = new Properties();
+ props.load(new FileInputStream(propertyFile));
+
+ //printProperties(project.getProperties());
+ ensureProperties(msg, inputFile, workingDir, project, props);
+ ensureReferences(msg, inputFile, project.getReferences());
+
+ } catch (IOException ex) {
+ fail(ex.toString());
+ }
+ }
+ }
+
+ /**
+ * Make sure every property loaded from the goldfile was also
+ * read from the XmlProperty. We could try and test the other way,
+ * but some other properties may get set in the XmlProperty due
+ * to generic Project/Task configuration.
+ */
+ private static void ensureProperties (String msg, File inputFile,
+ File workingDir, Project project,
+ Properties properties) {
+ Hashtable xmlproperties = project.getProperties();
+ // Every key identified by the Properties must have been loaded.
+ Enumeration propertyKeyEnum = properties.propertyNames();
+ while(propertyKeyEnum.hasMoreElements()){
+ String currentKey = propertyKeyEnum.nextElement().toString();
+ String assertMsg = msg + "-" + inputFile.getName()
+ + " Key=" + currentKey;
+
+ String propertyValue = properties.getProperty(currentKey);
+
+ String xmlValue = (String)xmlproperties.get(currentKey);
+
+ if ( propertyValue.indexOf("ID.") == 0 ) {
+ // The property is an id's thing -- either a property
+ // or a path. We need to make sure
+ // that the object was created with the given id.
+ // We don't have an adequate way of testing the actual
+ // *value* of the Path object, though...
+ String id = currentKey;
+ Object obj = project.getReferences().get(id);
+
+ if ( obj == null ) {
+ fail(assertMsg + " Object ID does not exist.");
+ }
+
+ // What is the property supposed to be?
+ propertyValue =
+ propertyValue.substring(3, propertyValue.length());
+ if (propertyValue.equals("path")) {
+ if (!(obj instanceof Path)) {
+ fail(assertMsg + " Path ID is a "
+ + obj.getClass().getName());
+ }
+ } else {
+ assertEquals(assertMsg, propertyValue, obj.toString());
+ }
+
+ } else {
+
+ if (propertyValue.indexOf("FILE.") == 0) {
+ // The property is the name of a file. We are testing
+ // a location attribute, so we need to resolve the given
+ // file name in the provided folder.
+ String fileName =
+ propertyValue.substring(5, propertyValue.length());
+ File f = new File(workingDir, fileName);
+ propertyValue = f.getAbsolutePath();
+ }
+
+ assertEquals(assertMsg, propertyValue, xmlValue);
+ }
+
+ }
}
+ /**
+ * Debugging method to print the properties in the given hashtable
+ */
+ private static void printProperties(Hashtable xmlproperties) {
+ Enumeration keyEnum = xmlproperties.keys();
+ while (keyEnum.hasMoreElements()) {
+ String currentKey = keyEnum.nextElement().toString();
+ System.out.println(currentKey + " = "
+ + xmlproperties.get(currentKey));
+ }
+ }
+
+ /**
+ * Ensure all references loaded by the project are valid.
+ */
+ private static void ensureReferences (String msg, File inputFile,
+ Hashtable references) {
+ Enumeration referenceKeyEnum = references.keys();
+ while(referenceKeyEnum.hasMoreElements()){
+ String currentKey = referenceKeyEnum.nextElement().toString();
+ Object currentValue = references.get(currentKey);
+
+ if (currentValue instanceof Path) {
+ } else if (currentValue instanceof String) {
+ } else {
+ fail(msg + "-" + inputFile.getName() + " Key="
+ + currentKey + " is not a recognized type.");
+ }
+ }
+ }
+
+ /**
+ * Munge the name of the input file to find an appropriate goldfile,
+ * based on hardwired naming conventions.
+ */
+ private static File getGoldfile (File input, boolean keepRoot,
+ boolean collapse, boolean semantic,
+ boolean include, boolean localRoot) {
+ // Substitute .xml with .properties
+ String baseName = input.getName().toLowerCase();
+ if (baseName.endsWith(".xml")) {
+ baseName = baseName.substring(0, baseName.length() - 4)
+ + ".properties";
+ }
+
+ File dir = fileUtils.getParentFile(fileUtils.getParentFile(input));
+
+ String goldFileFolder = "goldfiles/";
+
+ if (keepRoot) {
+ goldFileFolder += "keeproot-";
+ } else {
+ goldFileFolder += "nokeeproot-";
+ }
+
+ if (semantic) {
+ goldFileFolder += "semantic-";
+ if (include) {
+ goldFileFolder += "include-";
+ }
+ } else {
+ if (collapse) {
+ goldFileFolder += "collapse-";
+ } else {
+ goldFileFolder += "nocollapse-";
+ }
+ }
+
+ return new File(dir, goldFileFolder + baseName);
+ }
+
+ /**
+ * Retrieve a list of xml files in the specified folder
+ * and below.
+ */
+ private static Enumeration getFiles (final File startingDir) {
+ Vector result = new Vector();
+ getFiles(startingDir, result);
+ return result.elements();
+ }
+
+ /**
+ * Collect a list of xml files in the specified folder
+ * and below.
+ */
+ private static void getFiles (final File startingDir, Vector collect) {
+ FileFilter filter = new FileFilter() {
+ public boolean accept (File file) {
+ if (file.isDirectory()) {
+ return true;
+ } else {
+ return (file.getPath().indexOf("taskdefs") > 0 &&
+ file.getPath().toLowerCase().endsWith(".xml") );
+ }
+ }
+ };
+
+ File[] files = startingDir.listFiles(filter);
+ for (int i=0;i