source: release-kits/lirk3/ant-scripts/tasks/antelope/src/ise/antelope/tasks/XmlPropertyTask.java@ 14982

Last change on this file since 14982 was 14982, checked in by oranfry, 16 years ago

initial import of LiRK3

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