|
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
- package org.apache.tools.ant.taskdefs;
-
- import java.io.File;
- import java.io.IOException;
- import java.util.Hashtable;
- import javax.xml.parsers.DocumentBuilderFactory;
- import javax.xml.parsers.DocumentBuilder;
- 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.Resource;
- import org.apache.tools.ant.types.ResourceCollection;
- import org.apache.tools.ant.types.XMLCatalog;
- import org.apache.tools.ant.types.resources.FileResource;
- import org.apache.tools.ant.util.FileUtils;
- import org.w3c.dom.Document;
- 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;
- import org.xml.sax.EntityResolver;
-
- /**
- * Loads property values from a valid XML file, generating the
- * property names from the file's element and attribute names.
- *
- * <p>Example:</p>
- * <pre>
- * <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>
- *</pre>
- *
- * <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 "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):</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>
- * <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>
- * </pre>
- *
- * <p>is equivalent to the following entries in a build file:</p>
- *
- * <pre>
- * <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>
- * </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 false.</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 semantic attribute ("location" or "value") as
- * part of the property name. Defaults to false.</li>
- * </ul>
- *
- * @ant.task name="xmlproperty" category="xml"
- */
-
- public class XmlProperty extends org.apache.tools.ant.Task {
-
- private Resource src;
- private String prefix = "";
- private boolean keepRoot = true;
- private boolean validate = false;
- private boolean collapseAttributes = false;
- private boolean semanticAttributes = false;
- private boolean includeSemanticAttribute = false;
- private File rootDirectory = null;
- private Hashtable addedAttributes = new Hashtable();
- private XMLCatalog xmlCatalog = new XMLCatalog();
-
- 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
- };
- private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
-
- /**
- * Constructor.
- */
- public XmlProperty() {
- super();
- }
-
- /**
- * Initializes the task.
- */
-
- public void init() {
- super.init();
- xmlCatalog.setProject(getProject());
- }
-
-
- /**
- * @return the xmlCatalog as the entityresolver.
- */
- protected EntityResolver getEntityResolver() {
- return xmlCatalog;
- }
-
- /**
- * 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
- */
- public void execute()
- throws BuildException {
-
- 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);
- }
- }
-
- /** 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);
- }
-
- /**
- * 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) {
-
- // 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);
- }
-
- /**
- * 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.
- // 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);
- } else {
- getProject().setNewProperty(name, value);
- }
- addedAttributes.put(name, value);
- if (id != null) {
- getProject().addReference(id, value);
- }
- }
-
- /**
- * 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; 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.
- * @param src the file to parse
- */
- public void setFile(File src) {
- setSrcResource(new FileResource(src));
- }
-
- /**
- * The resource to pack; required.
- * @param src resource to expand
- */
- public void setSrcResource(Resource src) {
- 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;
- }
-
- /**
- * Set the source resource.
- * @param a the resource to pack as a single element Resource collection.
- */
- public void addConfigured(ResourceCollection a) {
- if (a.size() != 1) {
- throw new BuildException("only single argument resource collections"
- + " are supported as archives");
- }
- setSrcResource((Resource) a.iterator().next());
- }
-
- /**
- * the prefix to prepend to each property
- * @param prefix the prefix to prepend to each property
- */
- public void setPrefix(String prefix) {
- this.prefix = prefix.trim();
- }
-
- /**
- * 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
- */
- public void setKeeproot(boolean keepRoot) {
- this.keepRoot = keepRoot;
- }
-
- /**
- * flag to validate the XML file; optional, default false
- * @param validate if true validate the XML file, default false
- */
- public void setValidate(boolean validate) {
- this.validate = validate;
- }
-
- /**
- * flag to treat attributes as nested elements;
- * optional, default false
- * @param collapseAttributes if true treat attributes as nested elements
- */
- public void setCollapseAttributes(boolean collapseAttributes) {
- this.collapseAttributes = collapseAttributes;
- }
-
- /**
- * Attribute to enable special handling of attributes - see ant manual.
- * @param semanticAttributes if true enable the special handling.
- */
- public void setSemanticAttributes(boolean semanticAttributes) {
- this.semanticAttributes = semanticAttributes;
- }
-
- /**
- * The directory to use for resolving file references.
- * Ignored if semanticAttributes is not set to true.
- * @param rootDirectory the directory.
- */
- public void setRootDirectory(File rootDirectory) {
- this.rootDirectory = rootDirectory;
- }
-
- /**
- * 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.
- */
- public void setIncludeSemanticAttribute(boolean includeSemanticAttribute) {
- this.includeSemanticAttribute = includeSemanticAttribute;
- }
-
- /**
- * add an XMLCatalog as a nested element; optional.
- * @param catalog the XMLCatalog to use
- */
- public void addConfiguredXMLCatalog(XMLCatalog catalog) {
- xmlCatalog.addConfiguredXMLCatalog(catalog);
- }
-
- /* Expose members for extensibility */
-
- /**
- * @return the file attribute.
- */
- protected File getFile () {
- if (src instanceof FileResource) {
- return ((FileResource) src).getFile();
- } else {
- return null;
- }
- }
-
- /**
- * @return the resource.
- */
- protected Resource getResource() {
- // delegate this way around to support subclasses that
- // overwrite getFile
- File f = getFile();
- if (f != null) {
- return new FileResource(f);
- } else {
- return src;
- }
- }
-
- /**
- * @return the prefix attribute.
- */
- protected String getPrefix () {
- return this.prefix;
- }
-
- /**
- * @return the keeproot attribute.
- */
- protected boolean getKeeproot () {
- return this.keepRoot;
- }
-
- /**
- * @return the validate attribute.
- */
- protected boolean getValidate () {
- return this.validate;
- }
-
- /**
- * @return the collapse attributes attribute.
- */
- protected boolean getCollapseAttributes () {
- return this.collapseAttributes;
- }
-
- /**
- * @return the semantic attributes attribute.
- */
- protected boolean getSemanticAttributes () {
- return this.semanticAttributes;
- }
-
- /**
- * @return the root directory attribute.
- */
- protected File getRootDirectory () {
- return this.rootDirectory;
- }
-
- /**
- * @return the include semantic attribute.
- */
- 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 FILE_UTILS.resolveFile(getProject().getBaseDir(),fileName);
- }
- return FILE_UTILS.resolveFile(rootDirectory, fileName);
- }
-
- /**
- * Whether this task can deal with non-file resources.
- *
- * <p>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.</p>
- *
- * @since Ant 1.7
- */
- protected boolean supportsNonFileResources() {
- return getClass().equals(XmlProperty.class);
- }
- }
|