diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java | 780 |
1 files changed, 780 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java new file mode 100644 index 00000000..2830bdf9 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java @@ -0,0 +1,780 @@ +/* + * 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.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.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.XMLCatalog; +import org.apache.tools.ant.types.resources.FileProvider; +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.EntityResolver; +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. + * + * <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 String delimiter = ","; + + 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) { + throw new BuildException("XmlProperty task requires a source resource"); + } + 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; + FileProvider fp = src.as(FileProvider.class); + if (fp != null) { + document = builder.parse(fp.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; + } + 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) + getDelimiter() + 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); + } + } + + /** + * 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. + if (!isSemanticAttribute(attributeName) || includeSemanticAttribute) { + return "." + attributeName; + } + return ""; + } + return collapseAttributes ? "." + attributeName : "(" + 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(); + } + 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.as(FileProvider.class) != null || supportsNonFileResources()) { + this.src = src; + } else { + throw new BuildException("Only FileSystem resources are supported."); + } + } + + /** + * 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(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 semantic 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 () { + FileProvider fp = src.as(FileProvider.class); + return fp != null ? fp.getFile() : null; + } + + /** + * @return the resource. + */ + protected Resource getResource() { + // delegate this way around to support subclasses that + // overwrite getFile + File f = getFile(); + FileProvider fp = src.as(FileProvider.class); + return f == null ? src : fp != null + && fp.getFile().equals(f) ? src : new FileResource(f); + } + + /** + * @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) { + return FILE_UTILS.resolveFile(rootDirectory == null ? getProject().getBaseDir() + : 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> + * @return true for this task. + * @since Ant 1.7 + */ + protected boolean supportsNonFileResources() { + return getClass().equals(XmlProperty.class); + } + + /** + * Get the current delimiter. + * @return delimiter + */ + public String getDelimiter() { + return delimiter; + } + + /** + * Sets a new delimiter. + * @param delimiter new value + * @since Ant 1.7.1 + */ + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } +} |