source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/XmlProperty.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 24.6 KB
Line 
1/*
2 * Copyright 2002-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant.taskdefs;
19
20import java.io.File;
21import java.io.IOException;
22import java.util.Hashtable;
23import javax.xml.parsers.DocumentBuilderFactory;
24import javax.xml.parsers.DocumentBuilder;
25import javax.xml.parsers.ParserConfigurationException;
26import org.apache.tools.ant.BuildException;
27import org.apache.tools.ant.Project;
28import org.apache.tools.ant.types.Path;
29import org.apache.tools.ant.types.XMLCatalog;
30import org.apache.tools.ant.util.FileUtils;
31import org.w3c.dom.Document;
32import org.w3c.dom.Element;
33import org.w3c.dom.NamedNodeMap;
34import org.w3c.dom.Node;
35import org.w3c.dom.NodeList;
36import org.xml.sax.SAXException;
37import org.xml.sax.EntityResolver;
38
39/**
40 * Loads property values from a valid XML file, generating the
41 * property names from the file's element and attribute names.
42 *
43 * <p>Example:</p>
44 * <pre>
45 * &lt;root-tag myattr="true"&gt;
46 * &lt;inner-tag someattr="val"&gt;Text&lt;/inner-tag&gt;
47 * &lt;a2&gt;&lt;a3&gt;&lt;a4&gt;false&lt;/a4&gt;&lt;/a3&gt;&lt;/a2&gt;
48 * &lt;x&gt;x1&lt;/x&gt;
49 * &lt;x&gt;x2&lt;/x&gt;
50 * &lt;/root-tag&gt;
51 *</pre>
52 *
53 * <p>this generates the following properties:</p>
54 *
55 * <pre>
56 * root-tag(myattr)=true
57 * root-tag.inner-tag=Text
58 * root-tag.inner-tag(someattr)=val
59 * root-tag.a2.a3.a4=false
60 * root-tag.x=x1,x2
61 * </pre>
62 *
63 * <p>The <i>collapseAttributes</i> property of this task can be set
64 * to true (the default is false) which will instead result in the
65 * following properties (note the difference in names of properties
66 * corresponding to XML attributes):</p>
67 *
68 * <pre>
69 * root-tag.myattr=true
70 * root-tag.inner-tag=Text
71 * root-tag.inner-tag.someattr=val
72 * root-tag.a2.a3.a4=false
73 * root-tag.x=x1,x2
74 * </pre>
75 *
76 * <p>Optionally, to more closely mirror the abilities of the Property
77 * task, a selected set of attributes can be treated specially. To
78 * enable this behavior, the "semanticAttributes" property of this task
79 * must be set to true (it defaults to false). If this attribute is
80 * specified, the following attributes take on special meaning
81 * (setting this to true implicitly sets collapseAttributes to true as
82 * well):</p>
83 *
84 * <ul>
85 * <li><b>value</b>: Identifies a text value for a property.</li>
86 * <li><b>location</b>: Identifies a file location for a property.</li>
87 * <li><b>id</b>: Sets an id for a property</li>
88 * <li><b>refid</b>: Sets a property to the value of another property
89 * based upon the provided id</li>
90 * <li><b>pathid</b>: Defines a path rather than a property with
91 * the given id.</li>
92 * </ul>
93 *
94 * <p>For example, with keepRoot = false, the following properties file:</p>
95 *
96 * <pre>
97 * &lt;root-tag&gt;
98 * &lt;build&gt;
99 * &lt;build folder="build"&gt;
100 * &lt;classes id="build.classes" location="${build.folder}/classes"/&gt;
101 * &lt;reference refid="build.classes"/&gt;
102 * &lt;/build&gt;
103 * &lt;compile&gt;
104 * &lt;classpath pathid="compile.classpath"&gt;
105 * &lt;pathelement location="${build.classes}"/&gt;
106 * &lt;/classpath&gt;
107 * &lt;/compile&gt;
108 * &lt;run-time&gt;
109 * &lt;jars&gt;*.jar&lt;/jars&gt;
110 * &lt;classpath pathid="run-time.classpath"&gt;
111 * &lt;path refid="compile.classpath"/&gt;
112 * &lt;pathelement path="${run-time.jars}"/&gt;
113 * &lt;/classpath&gt;
114 * &lt;/run-time&gt;
115 * &lt;/root-tag&gt;
116 * </pre>
117 *
118 * <p>is equivalent to the following entries in a build file:</p>
119 *
120 * <pre>
121 * &lt;property name="build" location="build"/&gt;
122 * &lt;property name="build.classes" location="${build.location}/classes"/&gt;
123 * &lt;property name="build.reference" refid="build.classes"/&gt;
124 *
125 * &lt;property name="run-time.jars" value="*.jar/&gt;
126 *
127 * &lt;classpath id="compile.classpath"&gt;
128 * &lt;pathelement location="${build.classes}"/&gt;
129 * &lt;/classpath&gt;
130 *
131 * &lt;classpath id="run-time.classpath"&gt;
132 * &lt;path refid="compile.classpath"/&gt;
133 * &lt;pathelement path="${run-time.jars}"/&gt;
134 * &lt;/classpath&gt;
135 * </pre>
136 *
137 * <p> This task <i>requires</i> the following attributes:</p>
138 *
139 * <ul>
140 * <li><b>file</b>: The name of the file to load.</li>
141 * </ul>
142 *
143 * <p>This task supports the following attributes:</p>
144 *
145 * <ul>
146 * <li><b>prefix</b>: Optionally specify a prefix applied to
147 * all properties loaded. Defaults to an empty string.</li>
148 * <li><b>keepRoot</b>: Indicate whether the root xml element
149 * is kept as part of property name. Defaults to true.</li>
150 * <li><b>validate</b>: Indicate whether the xml file is validated.
151 * Defaults to false.</li>
152 * <li><b>collapseAttributes</b>: Indicate whether attributes are
153 * stored in property names with parens or with period
154 * delimiters. Defaults to false, meaning properties
155 * are stored with parens (i.e., foo(attr)).</li>
156 * <li><b>semanticAttributes</b>: Indicate whether attributes
157 * named "location", "value", "refid" and "path"
158 * are interpreted as ant properties. Defaults
159 * to false.</li>
160 * <li><b>rootDirectory</b>: Indicate the directory to use
161 * as the root directory for resolving location
162 * properties. Defaults to the directory
163 * of the project using the task.</li>
164 * <li><b>includeSemanticAttribute</b>: Indicate whether to include
165 * the semantic attribute ("location" or "value") as
166 * part of the property name. Defaults to false.</li>
167 * </ul>
168 *
169 * @ant.task name="xmlproperty" category="xml"
170 */
171
172public class XmlProperty extends org.apache.tools.ant.Task {
173
174 private File src;
175 private String prefix = "";
176 private boolean keepRoot = true;
177 private boolean validate = false;
178 private boolean collapseAttributes = false;
179 private boolean semanticAttributes = false;
180 private boolean includeSemanticAttribute = false;
181 private File rootDirectory = null;
182 private FileUtils fileUtils = FileUtils.newFileUtils();
183 private Hashtable addedAttributes = new Hashtable();
184 private XMLCatalog xmlCatalog = new XMLCatalog();
185
186 private static final String ID = "id";
187 private static final String REF_ID = "refid";
188 private static final String LOCATION = "location";
189 private static final String VALUE = "value";
190 private static final String PATH = "path";
191 private static final String PATHID = "pathid";
192 private static final String[] ATTRIBUTES = new String[] {
193 ID, REF_ID, LOCATION, VALUE, PATH, PATHID
194 };
195
196 /**
197 * Constructor.
198 */
199 public XmlProperty() {
200 super();
201 }
202
203 /**
204 * Initializes the task.
205 */
206
207 public void init() {
208 super.init();
209 xmlCatalog.setProject(getProject());
210 }
211
212
213 /**
214 * @return the xmlCatalog as the entityresolver.
215 */
216 protected EntityResolver getEntityResolver() {
217 return xmlCatalog;
218 }
219
220 /**
221 * Run the task.
222 * @throws BuildException The exception raised during task execution.
223 * @todo validate the source file is valid before opening, print a better error message
224 * @todo add a verbose level log message listing the name of the file being loaded
225 */
226 public void execute()
227 throws BuildException {
228
229 if (getFile() == null) {
230 String msg = "XmlProperty task requires a file attribute";
231 throw new BuildException(msg);
232 }
233
234 try {
235 log("Loading " + src.getAbsolutePath(), Project.MSG_VERBOSE);
236
237 if (src.exists()) {
238
239 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
240 factory.setValidating(validate);
241 factory.setNamespaceAware(false);
242 DocumentBuilder builder = factory.newDocumentBuilder();
243 builder.setEntityResolver(getEntityResolver());
244 Document document = builder.parse(src);
245 Element topElement = document.getDocumentElement();
246
247 // Keep a hashtable of attributes added by this task.
248 // This task is allow to override its own properties
249 // but not other properties. So we need to keep track
250 // of which properties we've added.
251 addedAttributes = new Hashtable();
252
253 if (keepRoot) {
254 addNodeRecursively(topElement, prefix, null);
255 } else {
256 NodeList topChildren = topElement.getChildNodes();
257 int numChildren = topChildren.getLength();
258 for (int i = 0; i < numChildren; i++) {
259 addNodeRecursively(topChildren.item(i), prefix, null);
260 }
261 }
262
263 } else {
264 log("Unable to find property file: " + src.getAbsolutePath(),
265 Project.MSG_VERBOSE);
266 }
267
268 } catch (SAXException sxe) {
269 // Error generated during parsing
270 Exception x = sxe;
271 if (sxe.getException() != null) {
272 x = sxe.getException();
273 }
274 throw new BuildException(x);
275
276 } catch (ParserConfigurationException pce) {
277 // Parser with specified options can't be built
278 throw new BuildException(pce);
279 } catch (IOException ioe) {
280 // I/O error
281 throw new BuildException(ioe);
282 }
283 }
284
285 /** Iterate through all nodes in the tree. */
286 private void addNodeRecursively(Node node, String prefix,
287 Object container) {
288
289 // Set the prefix for this node to include its tag name.
290 String nodePrefix = prefix;
291 if (node.getNodeType() != Node.TEXT_NODE) {
292 if (prefix.trim().length() > 0) {
293 nodePrefix += ".";
294 }
295 nodePrefix += node.getNodeName();
296 }
297
298 // Pass the container to the processing of this node,
299 Object nodeObject = processNode(node, nodePrefix, container);
300
301 // now, iterate through children.
302 if (node.hasChildNodes()) {
303
304 NodeList nodeChildren = node.getChildNodes();
305 int numChildren = nodeChildren.getLength();
306
307 for (int i = 0; i < numChildren; i++) {
308 // For each child, pass the object added by
309 // processNode to its children -- in other word, each
310 // object can pass information along to its children.
311 addNodeRecursively(nodeChildren.item(i), nodePrefix,
312 nodeObject);
313 }
314 }
315 }
316
317 void addNodeRecursively(org.w3c.dom.Node node, String prefix) {
318 addNodeRecursively(node, prefix, null);
319 }
320
321 /**
322 * Process the given node, adding any required attributes from
323 * this child node alone -- but <em>not</em> processing any
324 * children.
325 *
326 * @param node the XML Node to parse
327 * @param prefix A string to prepend to any properties that get
328 * added by this node.
329 * @param container Optionally, an object that a parent node
330 * generated that this node might belong to. For example, this
331 * node could be within a node that generated a Path.
332 * @return the Object created by this node. Generally, this is
333 * either a String if this node resulted in setting an attribute,
334 * or a Path.
335 */
336 public Object processNode (Node node, String prefix, Object container) {
337
338 // Parse the attribute(s) and text of this node, adding
339 // properties for each.
340 // if the "path" attribute is specified, then return the created path
341 // which will be passed to the children of this node.
342 Object addedPath = null;
343
344 // The value of an id attribute of this node.
345 String id = null;
346
347 if (node.hasAttributes()) {
348
349 NamedNodeMap nodeAttributes = node.getAttributes();
350
351 // Is there an id attribute?
352 Node idNode = nodeAttributes.getNamedItem(ID);
353 id = (semanticAttributes && idNode != null
354 ? idNode.getNodeValue() : null);
355
356 // Now, iterate through the attributes adding them.
357 for (int i = 0; i < nodeAttributes.getLength(); i++) {
358
359 Node attributeNode = nodeAttributes.item(i);
360
361 if (!semanticAttributes) {
362 String attributeName = getAttributeName(attributeNode);
363 String attributeValue = getAttributeValue(attributeNode);
364 addProperty(prefix + attributeName, attributeValue, null);
365 } else {
366
367 String nodeName = attributeNode.getNodeName();
368 String attributeValue = getAttributeValue(attributeNode);
369
370 Path containingPath = (container != null
371 && container instanceof Path ? (Path) container : null);
372
373 /*
374 * The main conditional logic -- if the attribute
375 * is somehow "special" (i.e., it has known
376 * semantic meaning) then deal with it
377 * appropriately.
378 */
379 if (nodeName.equals(ID)) {
380 // ID has already been found above.
381 continue;
382 } else if (containingPath != null
383 && nodeName.equals(PATH)) {
384 // A "path" attribute for a node within a Path object.
385 containingPath.setPath(attributeValue);
386 } else if (container instanceof Path
387 && nodeName.equals(REF_ID)) {
388 // A "refid" attribute for a node within a Path object.
389 containingPath.setPath(attributeValue);
390 } else if (container instanceof Path
391 && nodeName.equals(LOCATION)) {
392 // A "location" attribute for a node within a
393 // Path object.
394 containingPath.setLocation(resolveFile(attributeValue));
395 } else if (nodeName.equals(PATHID)) {
396 // A node identifying a new path
397 if (container != null) {
398 throw new BuildException("XmlProperty does not "
399 + "support nested paths");
400 }
401
402 addedPath = new Path(getProject());
403 getProject().addReference(attributeValue, addedPath);
404 } else {
405 // An arbitrary attribute.
406 String attributeName = getAttributeName(attributeNode);
407 addProperty(prefix + attributeName, attributeValue, id);
408 }
409 }
410 }
411 }
412
413 String nodeText = null;
414 if (node.getNodeType() == Node.TEXT_NODE) {
415 // For the text node, add a property.
416 nodeText = getAttributeValue(node);
417 } else if ((node.getNodeType() == Node.ELEMENT_NODE)
418 && (node.getChildNodes().getLength() == 1)
419 && (node.getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE)) {
420
421 nodeText = node.getFirstChild().getNodeValue();
422 }
423
424 if (nodeText != null) {
425 // If the containing object was a String, then use it as the ID.
426 if (semanticAttributes && id == null
427 && container instanceof String) {
428 id = (String) container;
429 }
430
431 if (nodeText.trim().length() != 0) {
432 addProperty(prefix, nodeText, id);
433 }
434 }
435
436 // Return the Path we added or the ID of this node for
437 // children to reference if needed. Path objects are
438 // definitely used by child path elements, and ID may be used
439 // for a child text node.
440 return (addedPath != null ? addedPath : id);
441 }
442
443 /**
444 * Actually add the given property/value to the project
445 * after writing a log message.
446 */
447 private void addProperty (String name, String value, String id) {
448 String msg = name + ":" + value;
449 if (id != null) {
450 msg += ("(id=" + id + ")");
451 }
452 log(msg, Project.MSG_DEBUG);
453
454 if (addedAttributes.containsKey(name)) {
455 // If this attribute was added by this task, then
456 // we append this value to the existing value.
457 // We use the setProperty method which will
458 // forcibly override the property if it already exists.
459 // We need to put these properties into the project
460 // when we read them, though (instead of keeping them
461 // outside of the project and batch adding them at the end)
462 // to allow other properties to reference them.
463 value = (String) addedAttributes.get(name) + "," + value;
464 getProject().setProperty(name, value);
465 } else {
466 getProject().setNewProperty(name, value);
467 }
468 addedAttributes.put(name, value);
469 if (id != null) {
470 getProject().addReference(id, value);
471 }
472 }
473
474 /**
475 * Return a reasonable attribute name for the given node.
476 * If we are using semantic attributes or collapsing
477 * attributes, the returned name is ".nodename".
478 * Otherwise, we return "(nodename)". This is long-standing
479 * (and default) &lt;xmlproperty&gt; behavior.
480 */
481 private String getAttributeName (Node attributeNode) {
482 String attributeName = attributeNode.getNodeName();
483
484 if (semanticAttributes) {
485 // Never include the "refid" attribute as part of the
486 // attribute name.
487 if (attributeName.equals(REF_ID)) {
488 return "";
489 // Otherwise, return it appended unless property to hide it is set.
490 } else if (!isSemanticAttribute(attributeName)
491 || includeSemanticAttribute) {
492 return "." + attributeName;
493 } else {
494 return "";
495 }
496 } else if (collapseAttributes) {
497 return "." + attributeName;
498 } else {
499 return "(" + attributeName + ")";
500 }
501 }
502
503 /**
504 * Return whether the provided attribute name is recognized or not.
505 */
506 private static boolean isSemanticAttribute (String attributeName) {
507 for (int i = 0; i < ATTRIBUTES.length; i++) {
508 if (attributeName.equals(ATTRIBUTES[i])) {
509 return true;
510 }
511 }
512 return false;
513 }
514
515 /**
516 * Return the value for the given attribute.
517 * If we are not using semantic attributes, its just the
518 * literal string value of the attribute.
519 *
520 * <p>If we <em>are</em> using semantic attributes, then first
521 * dependent properties are resolved (i.e., ${foo} is resolved
522 * based on the foo property value), and then an appropriate data
523 * type is used. In particular, location-based properties are
524 * resolved to absolute file names. Also for refid values, look
525 * up the referenced object from the project.</p>
526 */
527 private String getAttributeValue (Node attributeNode) {
528 String nodeValue = attributeNode.getNodeValue().trim();
529 if (semanticAttributes) {
530 String attributeName = attributeNode.getNodeName();
531 nodeValue = getProject().replaceProperties(nodeValue);
532 if (attributeName.equals(LOCATION)) {
533 File f = resolveFile(nodeValue);
534 return f.getPath();
535 } else if (attributeName.equals(REF_ID)) {
536 Object ref = getProject().getReference(nodeValue);
537 if (ref != null) {
538 return ref.toString();
539 }
540 }
541 }
542 return nodeValue;
543 }
544
545 /**
546 * The XML file to parse; required.
547 * @param src the file to parse
548 */
549 public void setFile(File src) {
550 this.src = src;
551 }
552
553 /**
554 * the prefix to prepend to each property
555 * @param prefix the prefix to prepend to each property
556 */
557 public void setPrefix(String prefix) {
558 this.prefix = prefix.trim();
559 }
560
561 /**
562 * flag to include the xml root tag as a
563 * first value in the property name; optional,
564 * default is true
565 * @param keepRoot if true (default), include the xml root tag
566 */
567 public void setKeeproot(boolean keepRoot) {
568 this.keepRoot = keepRoot;
569 }
570
571 /**
572 * flag to validate the XML file; optional, default false
573 * @param validate if true validate the XML file, default false
574 */
575 public void setValidate(boolean validate) {
576 this.validate = validate;
577 }
578
579 /**
580 * flag to treat attributes as nested elements;
581 * optional, default false
582 * @param collapseAttributes if true treat attributes as nested elements
583 */
584 public void setCollapseAttributes(boolean collapseAttributes) {
585 this.collapseAttributes = collapseAttributes;
586 }
587
588 /**
589 * Attribute to enable special handling of attributes - see ant manual.
590 * @param semanticAttributes if true enable the special handling.
591 */
592 public void setSemanticAttributes(boolean semanticAttributes) {
593 this.semanticAttributes = semanticAttributes;
594 }
595
596 /**
597 * The directory to use for resolving file references.
598 * Ignored if semanticAttributes is not set to true.
599 * @param rootDirectory the directory.
600 */
601 public void setRootDirectory(File rootDirectory) {
602 this.rootDirectory = rootDirectory;
603 }
604
605 /**
606 * Include the semantic attribute name as part of the property name.
607 * Ignored if semanticAttributes is not set to true.
608 * @param includeSemanticAttribute if true include the sematic attribute
609 * name.
610 */
611 public void setIncludeSemanticAttribute(boolean includeSemanticAttribute) {
612 this.includeSemanticAttribute = includeSemanticAttribute;
613 }
614
615 /**
616 * add an XMLCatalog as a nested element; optional.
617 * @param catalog the XMLCatalog to use
618 */
619 public void addConfiguredXMLCatalog(XMLCatalog catalog) {
620 xmlCatalog.addConfiguredXMLCatalog(catalog);
621 }
622
623 /* Expose members for extensibility */
624
625 /**
626 * @return the file attribute.
627 */
628 protected File getFile () {
629 return this.src;
630 }
631
632 /**
633 * @return the prefix attribute.
634 */
635 protected String getPrefix () {
636 return this.prefix;
637 }
638
639 /**
640 * @return the keeproot attribute.
641 */
642 protected boolean getKeeproot () {
643 return this.keepRoot;
644 }
645
646 /**
647 * @return the validate attribute.
648 */
649 protected boolean getValidate () {
650 return this.validate;
651 }
652
653 /**
654 * @return the collapse attributes attribute.
655 */
656 protected boolean getCollapseAttributes () {
657 return this.collapseAttributes;
658 }
659
660 /**
661 * @return the semantic attributes attribute.
662 */
663 protected boolean getSemanticAttributes () {
664 return this.semanticAttributes;
665 }
666
667 /**
668 * @return the root directory attribute.
669 */
670 protected File getRootDirectory () {
671 return this.rootDirectory;
672 }
673
674 /**
675 * @return the include semantic attribute.
676 */
677 protected boolean getIncludeSementicAttribute () {
678 return this.includeSemanticAttribute;
679 }
680
681 /**
682 * Let project resolve the file - or do it ourselves if
683 * rootDirectory has been set.
684 */
685 private File resolveFile(String fileName) {
686 if (rootDirectory == null) {
687 return getProject().resolveFile(fileName);
688 }
689 return fileUtils.resolveFile(rootDirectory, fileName);
690 }
691
692}
Note: See TracBrowser for help on using the repository browser.