Browse Source

Enhancements for <xmlproperty>: you can now expand ${properties},

define ids or paths and use Ant's location magic for filename
resolutions in the XML file.

PR: 11321, 12045
Submitted by:	Paul Christmann <paul at priorartisans.com>


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@273484 13f79535-47bb-0310-9956-ffa450edef68
master
Stefan Bodewig 22 years ago
parent
commit
524a7831a7
23 changed files with 828 additions and 44 deletions
  1. +4
    -0
      WHATSNEW
  2. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-input1.properties
  3. +2
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-override.properties
  4. +8
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-nocollapse-input1.properties
  5. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-include.properties
  6. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-input1.properties
  7. +2
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-override.properties
  8. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-collapse-input1.properties
  9. +7
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-input1.properties
  10. +1
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-multi.properties
  11. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-include-input1.properties
  12. +6
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-input1.properties
  13. +1
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-locations.properties
  14. +1
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-paths.properties
  15. +5
    -0
      src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-references.properties
  16. +10
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/input1.xml
  17. +3
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/locations.xml
  18. +8
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/multi.xml
  19. +5
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/override.xml
  20. +5
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/paths.xml
  21. +7
    -0
      src/etc/testcases/taskdefs/xmlproperty/inputs/references.xml
  22. +429
    -38
      src/main/org/apache/tools/ant/taskdefs/XmlProperty.java
  23. +294
    -6
      src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java

+ 4
- 0
WHATSNEW View File

@@ -63,6 +63,10 @@ Other changes:
* <arg> has a new attribute pathref that can be used to reference
previously defined paths.

* <xmlproperty> 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
====================================



+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-input1.properties View File

@@ -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

+ 2
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-collapse-override.properties View File

@@ -0,0 +1,2 @@
# Match value hardwired in code, NOT in the input...
override.property.test=foo

+ 8
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-nocollapse-input1.properties View File

@@ -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


+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-include.properties View File

@@ -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

+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-input1.properties View File

@@ -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

+ 2
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/keeproot-semantic-override.properties View File

@@ -0,0 +1,2 @@
# Match value hardwired in code, NOT in the input...
override.property.test=foo

+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-collapse-input1.properties View File

@@ -0,0 +1,6 @@
root=foo,bar
a.b.c=d
a.b=e
foo.bar=quux,quux1
foo.quux=bar
tag.value=foo

+ 7
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-input1.properties View File

@@ -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

+ 1
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-nocollapse-multi.properties View File

@@ -0,0 +1 @@
foo.bar=1,2,3,4

+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-include-input1.properties View File

@@ -0,0 +1,6 @@
root=foo,bar
a.b.c=d
a.b=e
foo.bar=quux,quux1
foo.quux=bar
tag.value=foo

+ 6
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-input1.properties View File

@@ -0,0 +1,6 @@
root=foo,bar
a.b.c=d
a.b=e
foo.bar=quux,quux1
foo.quux=bar
tag=foo

+ 1
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-locations.properties View File

@@ -0,0 +1 @@
file=FILE.foo

+ 1
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-paths.properties View File

@@ -0,0 +1 @@
foo=ID.path

+ 5
- 0
src/etc/testcases/taskdefs/xmlproperty/goldfiles/nokeeproot-semantic-references.properties View File

@@ -0,0 +1,5 @@
property=foo
foo.bar=foo
foo.quux=foo
foo.thunk=foo
foo.property=ID.foo

+ 10
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/input1.xml View File

@@ -0,0 +1,10 @@
<properties>
<root>foo</root>
<root>bar</root>
<a><b c="d">e</b></a>
<foo bar="quux">
<bar>quux1</bar>
<quux>bar</quux>
</foo>
<tag value="foo"/>
</properties>

+ 3
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/locations.xml View File

@@ -0,0 +1,3 @@
<locations>
<file location="foo"/>
</locations>

+ 8
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/multi.xml View File

@@ -0,0 +1,8 @@
<properties>
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
<bar>4</bar>
</foo>
</properties>

+ 5
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/override.xml View File

@@ -0,0 +1,5 @@
<root>
<override>
<property test="bar"/>
</override>
</root>

+ 5
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/paths.xml View File

@@ -0,0 +1,5 @@
<paths>
<classpath pathid="foo">
<path value="bar"/>
</classpath>
</paths>

+ 7
- 0
src/etc/testcases/taskdefs/xmlproperty/inputs/references.xml View File

@@ -0,0 +1,7 @@
<references>
<property value="foo" id="foo.property"/>
<foo bar="${property}">
<quux refid="foo.property"/>
<thunk>${property}</thunk>
</foo>
</references>

+ 429
- 38
src/main/org/apache/tools/ant/taskdefs/XmlProperty.java View File

@@ -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:
* <p>Example:</p>
* <pre>
* &lt;root-tag myattr="true"&gt;
* &lt;inner-tag someattr="val"&gt;Text&lt;/inner-tag&gt;
* &lt;a2&gt;&lt;a3&gt;&lt;a4&gt;false&lt;/a4&gt;&lt;/a3&gt;&lt;/a2&gt;
* &lt;x&gt;x1&lt;/x&gt;
* &lt;x&gt;x2&lt;/x&gt;
* &lt;/root-tag&gt;
*</pre>
* this generates
*
* <p>this generates the following properties:</p>
*
* <pre>
* 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
* </pre>
*
* <p>The <i>collapseAttributes</i> 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):</p>
*
* <pre>
* 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
* </pre>
*
* <p>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):</p>
*
* <ul>
* <li><b>value</b>: Identifies a text value for a property.</li>
* <li><b>location</b>: Identifies a file location for a property.</li>
* <li><b>id</b>: Sets an id for a property</li>
* <li><b>refid</b>: Sets a property to the value of another property
* based upon the provided id</li>
* <li><b>pathid</b>: Defines a path rather than a property with
* the given id.</li>
* </ul>
*
* <p>For example, with keepRoot = false, the following properties file:</p>
*
* <pre>
* &lt;root-tag&gt;
* &lt;build location="build"&gt;
* &lt;classes id="build.classes" location="${build.location}/classes"/&gt;
* &lt;reference refid="build.location"/&gt;
* &lt;/build&gt;
* &lt;compile&gt;
* &lt;classpath pathid="compile.classpath"&gt;
* &lt;pathelement location="${build.classes}"/&gt;
* &lt;/classpath&gt;
* &lt;/compile&gt;
* &lt;run-time&gt;
* &lt;jars&gt;*.jar&lt;/jars&gt;
* &lt;classpath pathid="run-time.classpath"&gt;
* &lt;path refid="compile.classpath"/&gt;
* &lt;pathelement path="${run-time.jars}"/&gt;
* &lt;/classpath&gt;
* &lt;/run-time&gt;
* &lt;/root-tag&gt;
* </pre>
*
* <p>is equivalent to the following entries in a build file:</p>
*
* <pre>
* &lt;property name="build.location" location="build"/&gt;
* &lt;property name="build.classes.location" location="${build.location}/classes"/&gt;
* &lt;property name="build.reference" refid="build.location"/&gt;
*
* &lt;property name="run-time.jars" value="*.jar/&gt;
*
* &lt;classpath id="compile.classpath"&gt;
* &lt;pathelement location="${build.classes}"/&gt;
* &lt;/classpath&gt;
*
* &lt;classpath id="run-time.classpath"&gt;
* &lt;path refid="compile.classpath"/&gt;
* &lt;pathelement path="${run-time.jars}"/&gt;
* &lt;/classpath&gt;
* </pre>
*
* <p> This task <i>requires</i> the following attributes:</p>
*
* <ul>
* <li><b>file</b>: The name of the file to load.</li>
* </ul>
*
* <p>This task supports the following attributes:</p>
*
* <ul>
* <li><b>prefix</b>: Optionally specify a prefix applied to
* all properties loaded. Defaults to an empty string.</li>
* <li><b>keepRoot</b>: Indicate whether the root xml element
* is kept as part of property name. Defaults to true.</li>
* <li><b>validate</b>: Indicate whether the xml file is validated.
* Defaults to false.</li>
* <li><b>collapseAttributes</b>: 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)).</li>
* <li><b>semanticAttributes</b>: Indicate whether attributes
* named "location", "value", "refid" and "path"
* are interpreted as ant properties. Defaults
* to true.</li>
* <li><b>rootDirectory</b>: Indicate the directory to use
* as the root directory for resolving location
* properties. Defaults to the directory
* of the project using the task.</li>
* <li><b>includeSemanticAttribute</b>: Indicate whether to include
* the semanticAttribute ("location" or "value") as
* part of the property name. Defaults to false.</li>
* </ul>
*
* @author <a href="mailto:nicolaken@apache.org">Nicola Ken Barozzi</a>
* @author Erik Hatcher
* @author <a href="mailto:paul@priorartisans.com">Paul Christmann</a>
*
* @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 <em>not</em> 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) &lt;xmlproperty&gt; 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;i<ATTRIBUTES.length;i++) {
if (attributeName.equals(ATTRIBUTES[i])) {
return true;
}
}
return false;
}

/**
* Return the value for the given attribute.
* If we are not using semantic attributes, its just the
* literal string value of the attribute.
*
* <p>If we <em>are</em> 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.</p>
*/
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);
}

}

+ 294
- 6
src/testcases/org/apache/tools/ant/taskdefs/XmlPropertyTest.java View File

@@ -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 <a href="mailto:paul@priorartisans.com">Paul Christmann</a>
*/
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<files.length;i++) {
File f = files[i];
if (!f.isDirectory()) {
collect.addElement(f);
} else {
getFiles(f, collect);
}
}
}
}

Loading…
Cancel
Save