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