FileDocCategorySizeDatePackage
XmlProperty.javaAPI DocApache Ant 1.7028607Wed Dec 13 06:16:18 GMT 2006org.apache.tools.ant.taskdefs

XmlProperty

public class XmlProperty extends org.apache.tools.ant.Task
Loads property values from a valid XML file, generating the property names from the file's element and attribute names.

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 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 "semanticAttributes" 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>
<build folder="build">
<classes id="build.classes" location="${build.folder}/classes"/>
<reference refid="build.classes"/>
</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="build"/>
<property name="build.classes" location="${build.location}/classes"/>
<property name="build.reference" refid="build.classes"/>

<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 false.
  • 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 semantic attribute ("location" or "value") as part of the property name. Defaults to false.
ant.task
name="xmlproperty" category="xml"

Fields Summary
private org.apache.tools.ant.types.Resource
src
private String
prefix
private boolean
keepRoot
private boolean
validate
private boolean
collapseAttributes
private boolean
semanticAttributes
private boolean
includeSemanticAttribute
private File
rootDirectory
private Hashtable
addedAttributes
private org.apache.tools.ant.types.XMLCatalog
xmlCatalog
private static final String
ID
private static final String
REF_ID
private static final String
LOCATION
private static final String
VALUE
private static final String
PATH
private static final String
PATHID
private static final String[]
ATTRIBUTES
private static final org.apache.tools.ant.util.FileUtils
FILE_UTILS
Constructors Summary
public XmlProperty()
Constructor.


          
      
        super();
    
Methods Summary
public voidaddConfigured(org.apache.tools.ant.types.ResourceCollection a)
Set the source resource.

param
a the resource to pack as a single element Resource collection.

        if (a.size() != 1) {
            throw new BuildException("only single argument resource collections"
                                     + " are supported as archives");
        }
        setSrcResource((Resource) a.iterator().next());
    
public voidaddConfiguredXMLCatalog(org.apache.tools.ant.types.XMLCatalog catalog)
add an XMLCatalog as a nested element; optional.

param
catalog the XMLCatalog to use

        xmlCatalog.addConfiguredXMLCatalog(catalog);
    
private voidaddNodeRecursively(org.w3c.dom.Node node, java.lang.String prefix, java.lang.Object container)
Iterate through all nodes in the tree.


        // 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);
            }
        }
    
voidaddNodeRecursively(org.w3c.dom.Node node, java.lang.String prefix)

        addNodeRecursively(node, prefix, null);
    
private voidaddProperty(java.lang.String name, java.lang.String value, java.lang.String id)
Actually add the given property/value to the project after writing a log message.

        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.
            // We use the setProperty method which will
            // forcibly override the property if it already exists.
            // We need to put these properties into the project
            // when we read them, though (instead of keeping them
            // outside of the project and batch adding them at the end)
            // to allow other properties to reference them.
            value = (String) addedAttributes.get(name) + "," + value;
            getProject().setProperty(name, value);
            addedAttributes.put(name, value);
        } else if (getProject().getProperty(name) == null) {
            getProject().setNewProperty(name, value);
            addedAttributes.put(name, value);
        } else {
            log("Override ignored for property " + name, Project.MSG_VERBOSE);
        }
        if (id != null) {
            getProject().addReference(id, value);
        }
    
public voidexecute()
Run the task.

throws
BuildException The exception raised during task execution.
todo
validate the source file is valid before opening, print a better error message
todo
add a verbose level log message listing the name of the file being loaded


        Resource r = getResource();

        if (r == null) {
            String msg = "XmlProperty task requires a source resource";
            throw new BuildException(msg);
        }

        try {
            log("Loading " + src, Project.MSG_VERBOSE);

            if (r.isExists()) {

              DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
              factory.setValidating(validate);
              factory.setNamespaceAware(false);
              DocumentBuilder builder = factory.newDocumentBuilder();
              builder.setEntityResolver(getEntityResolver());
              Document document = null;
              if (src instanceof FileResource) {
                  document = builder.parse(((FileResource) src).getFile());
              } else {
                  document = builder.parse(src.getInputStream());
              }
              Element topElement = document.getDocumentElement();

              // Keep a hashtable of attributes added by this task.
              // This task is allow to override its own properties
              // but not other properties.  So we need to keep track
              // of which properties we've added.
              addedAttributes = new Hashtable();

              if (keepRoot) {
                  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, null);
                  }
              }

            } else {
                log("Unable to find property resource: " + r,
                    Project.MSG_VERBOSE);
            }

        } catch (SAXException sxe) {
            // Error generated during parsing
            Exception x = sxe;
            if (sxe.getException() != null) {
                x = sxe.getException();
            }
            throw new BuildException("Failed to load " + src, x);

        } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            throw new BuildException(pce);
        } catch (IOException ioe) {
            // I/O error
            throw new BuildException("Failed to load " + src, ioe);
        }
    
private java.lang.StringgetAttributeName(org.w3c.dom.Node attributeNode)
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.

        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 + ")";
        }
    
private java.lang.StringgetAttributeValue(org.w3c.dom.Node attributeNode)
Return the value for the given attribute. If we are not using semantic attributes, its just the literal string value of the attribute.

If 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.

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

return
the collapse attributes attribute.

        return this.collapseAttributes;
    
protected org.xml.sax.EntityResolvergetEntityResolver()

return
the xmlCatalog as the entityresolver.

        return xmlCatalog;
    
protected java.io.FilegetFile()

return
the file attribute.

        if (src instanceof FileResource) {
            return ((FileResource) src).getFile();
        } else {
            return null;
        }
    
protected booleangetIncludeSementicAttribute()

return
the include semantic attribute.

        return this.includeSemanticAttribute;
    
protected booleangetKeeproot()

return
the keeproot attribute.

        return this.keepRoot;
    
protected java.lang.StringgetPrefix()

return
the prefix attribute.

        return this.prefix;
    
protected org.apache.tools.ant.types.ResourcegetResource()

return
the resource.

        // delegate this way around to support subclasses that
        // overwrite getFile
        File f = getFile();
        if (f != null) {
            return new FileResource(f);
        } else {
            return src;
        }
    
protected java.io.FilegetRootDirectory()

return
the root directory attribute.

        return this.rootDirectory;
    
protected booleangetSemanticAttributes()

return
the semantic attributes attribute.

        return this.semanticAttributes;
    
protected booleangetValidate()

return
the validate attribute.

        return this.validate;
    
public voidinit()
Initializes the task.

        super.init();
        xmlCatalog.setProject(getProject());
    
private static booleanisSemanticAttribute(java.lang.String attributeName)
Return whether the provided attribute name is recognized or not.

        for (int i = 0; i < ATTRIBUTES.length; i++) {
            if (attributeName.equals(ATTRIBUTES[i])) {
                return true;
            }
        }
        return false;
    
public java.lang.ObjectprocessNode(org.w3c.dom.Node node, java.lang.String prefix, java.lang.Object container)
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.


        // 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()) {

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

                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 nodeText = null;
        boolean emptyNode = false;
        boolean semanticEmptyOverride = false;
        if (node.getNodeType() == Node.ELEMENT_NODE
            && semanticAttributes
            && node.hasAttributes()
            && (node.getAttributes().getNamedItem(VALUE) != null
                || node.getAttributes().getNamedItem(LOCATION) != null
                || node.getAttributes().getNamedItem(REF_ID) != null
                || node.getAttributes().getNamedItem(PATH) != null
                || node.getAttributes().getNamedItem(PATHID) != null)) {
            semanticEmptyOverride = true;
        }
        if (node.getNodeType() == Node.TEXT_NODE) {
            // For the text node, add a property.
            nodeText = getAttributeValue(node);
        } else if ((node.getNodeType() == Node.ELEMENT_NODE)
            && (node.getChildNodes().getLength() == 1)
            && (node.getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE)) {

            nodeText = node.getFirstChild().getNodeValue();
            if ("".equals(nodeText) && !semanticEmptyOverride) {
                emptyNode = true;
            }
        } else if ((node.getNodeType() == Node.ELEMENT_NODE)
                   && (node.getChildNodes().getLength() == 0)
                   && !semanticEmptyOverride) {
            nodeText = "";
            emptyNode = true;
        } else if ((node.getNodeType() == Node.ELEMENT_NODE)
                   && (node.getChildNodes().getLength() == 1)
                   && (node.getFirstChild().getNodeType() == Node.TEXT_NODE)
                   && ("".equals(node.getFirstChild().getNodeValue()))
                   && !semanticEmptyOverride) {
            nodeText = "";
            emptyNode = true;
        }

        if (nodeText != null) {
            // If the containing object was a String, then use it as the ID.
            if (semanticAttributes && id == null
                && container instanceof String) {
                id = (String) container;
            }
            if (nodeText.trim().length() != 0 || emptyNode) {
                addProperty(prefix, nodeText, id);
            }
        }

        // 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);
    
private java.io.FileresolveFile(java.lang.String fileName)
Let project resolve the file - or do it ourselves if rootDirectory has been set.

        if (rootDirectory == null) {
            return FILE_UTILS.resolveFile(getProject().getBaseDir(), fileName);
        }
        return FILE_UTILS.resolveFile(rootDirectory, fileName);
    
public voidsetCollapseAttributes(boolean collapseAttributes)
flag to treat attributes as nested elements; optional, default false

param
collapseAttributes if true treat attributes as nested elements

        this.collapseAttributes = collapseAttributes;
    
public voidsetFile(java.io.File src)
The XML file to parse; required.

param
src the file to parse

        setSrcResource(new FileResource(src));
    
public voidsetIncludeSemanticAttribute(boolean includeSemanticAttribute)
Include the semantic attribute name as part of the property name. Ignored if semanticAttributes is not set to true.

param
includeSemanticAttribute if true include the sematic attribute name.

        this.includeSemanticAttribute = includeSemanticAttribute;
    
public voidsetKeeproot(boolean keepRoot)
flag to include the xml root tag as a first value in the property name; optional, default is true

param
keepRoot if true (default), include the xml root tag

        this.keepRoot = keepRoot;
    
public voidsetPrefix(java.lang.String prefix)
the prefix to prepend to each property

param
prefix the prefix to prepend to each property

        this.prefix = prefix.trim();
    
public voidsetRootDirectory(java.io.File rootDirectory)
The directory to use for resolving file references. Ignored if semanticAttributes is not set to true.

param
rootDirectory the directory.

        this.rootDirectory = rootDirectory;
    
public voidsetSemanticAttributes(boolean semanticAttributes)
Attribute to enable special handling of attributes - see ant manual.

param
semanticAttributes if true enable the special handling.

        this.semanticAttributes = semanticAttributes;
    
public voidsetSrcResource(org.apache.tools.ant.types.Resource src)
The resource to pack; required.

param
src resource to expand

        if (src.isDirectory()) {
            throw new BuildException("the source can't be a directory");
        }
        if (src instanceof FileResource && !supportsNonFileResources()) {
            throw new BuildException("Only FileSystem resources are"
                                     + " supported.");
        }
        this.src = src;
    
public voidsetValidate(boolean validate)
flag to validate the XML file; optional, default false

param
validate if true validate the XML file, default false

        this.validate = validate;
    
protected booleansupportsNonFileResources()
Whether this task can deal with non-file resources.

This implementation returns true only if this task is <xmlproperty>. Any subclass of this class that also wants to support non-file resources needs to override this method. We need to do so for backwards compatibility reasons since we can't expect subclasses to support resources.

return
true for this task.
since
Ant 1.7

        return getClass().equals(XmlProperty.class);