1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration;
18
19 import java.io.File;
20 import java.io.InputStream;
21 import java.io.Reader;
22 import java.io.Writer;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import javax.xml.parsers.ParserConfigurationException;
32 import javax.xml.transform.OutputKeys;
33 import javax.xml.transform.Result;
34 import javax.xml.transform.Source;
35 import javax.xml.transform.Transformer;
36 import javax.xml.transform.TransformerException;
37 import javax.xml.transform.TransformerFactory;
38 import javax.xml.transform.dom.DOMSource;
39 import javax.xml.transform.stream.StreamResult;
40
41 import org.w3c.dom.Attr;
42 import org.w3c.dom.CDATASection;
43 import org.w3c.dom.DOMException;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NamedNodeMap;
47 import org.w3c.dom.NodeList;
48 import org.w3c.dom.Text;
49 import org.xml.sax.InputSource;
50 import org.xml.sax.SAXException;
51 import org.xml.sax.SAXParseException;
52 import org.xml.sax.helpers.DefaultHandler;
53
54 /***
55 * <p>A specialized hierarchical configuration class that is able to parse XML
56 * documents.</p>
57 *
58 * <p>The parsed document will be stored keeping its structure. The class also
59 * tries to preserve as much information from the loaded XML document as
60 * possible, including comments and processing instructions. These will be
61 * contained in documents created by the <code>save()</code> methods, too.</p>
62 *
63 * <p>Like other file based configuration classes this class maintains the name
64 * and path to the loaded configuration file. These properties can be altered
65 * using several setter methods, but they are not modified by <code>save()</code>
66 * and <code>load()</code> methods. If XML documents contain relative paths to
67 * other documents (e.g. to a DTD), these references are resolved based on the
68 * path set for this configuration.</p>
69 *
70 * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
71 * provides some extended functionaly, e.g. interpolation of property values.
72 * Like in <code>{@link PropertiesConfiguration}</code> property values can
73 * contain delimiter characters (the comma ',' per default) and are then splitted
74 * into multiple values. This works for XML attributes and text content of
75 * elements as well. The delimiter can be escaped by a backslash. As an example
76 * consider the following XML fragment:</p>
77 *
78 * <p>
79 * <pre>
80 * <config>
81 * <array>10,20,30,40</array>
82 * <scalar>3\,1415</scalar>
83 * <cite text="To be or not to be\, this is the question!"/>
84 * </config>
85 * </pre>
86 * </p>
87 * <p>Here the content of the <code>array</code> element will be splitted at
88 * the commas, so the <code>array</code> key will be assigned 4 values. In the
89 * <code>scalar</code> property and the <code>text</code> attribute of the
90 * <code>cite</code> element the comma is escaped, so that no splitting is
91 * performed.</p>
92 *
93 * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
94 * interface and thus provides full support for loading XML documents from
95 * different sources like files, URLs, or streams. A full description of these
96 * features can be found in the documentation of
97 * <code>{@link AbstractFileConfiguration}</code>.</p>
98 *
99 * @since commons-configuration 1.0
100 *
101 * @author Jörg Schaible
102 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
103 * @version $Revision$, $Date: 2005-11-20 20:39:51 +0100 (Sun, 20 Nov 2005) $
104 */
105 public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
106 {
107 /*** Constant for the default root element name. */
108 private static final String DEFAULT_ROOT_NAME = "configuration";
109
110 /*** The document from this configuration's data source. */
111 private Document document;
112
113 /*** Stores the name of the root element. */
114 private String rootElementName;
115
116 /*** Stores the document builder that should be used for loading.*/
117 private DocumentBuilder documentBuilder;
118
119 /*** Stores a flag whether DTD validation should be performed.*/
120 private boolean validating;
121
122 /***
123 * Creates a new instance of <code>XMLConfiguration</code>.
124 */
125 public XMLConfiguration()
126 {
127 super();
128 }
129
130 /***
131 * Creates a new instance of <code>XMLConfiguration</code>.
132 * The configuration is loaded from the specified file
133 *
134 * @param fileName the name of the file to load
135 * @throws ConfigurationException if the file cannot be loaded
136 */
137 public XMLConfiguration(String fileName) throws ConfigurationException
138 {
139 this();
140 setFileName(fileName);
141 load();
142 }
143
144 /***
145 * Creates a new instance of <code>XMLConfiguration</code>.
146 * The configuration is loaded from the specified file.
147 *
148 * @param file the file
149 * @throws ConfigurationException if an error occurs while loading the file
150 */
151 public XMLConfiguration(File file) throws ConfigurationException
152 {
153 this();
154 setFile(file);
155 if (file.exists())
156 {
157 load();
158 }
159 }
160
161 /***
162 * Creates a new instance of <code>XMLConfiguration</code>.
163 * The configuration is loaded from the specified URL.
164 *
165 * @param url the URL
166 * @throws ConfigurationException if loading causes an error
167 */
168 public XMLConfiguration(URL url) throws ConfigurationException
169 {
170 this();
171 setURL(url);
172 load();
173 }
174
175 /***
176 * Returns the name of the root element. If this configuration was loaded
177 * from a XML document, the name of this document's root element is
178 * returned. Otherwise it is possible to set a name for the root element
179 * that will be used when this configuration is stored.
180 *
181 * @return the name of the root element
182 */
183 public String getRootElementName()
184 {
185 if (getDocument() == null)
186 {
187 return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
188 }
189 else
190 {
191 return getDocument().getDocumentElement().getNodeName();
192 }
193 }
194
195 /***
196 * Sets the name of the root element. This name is used when this
197 * configuration object is stored in an XML file. Note that setting the name
198 * of the root element works only if this configuration has been newly
199 * created. If the configuration was loaded from an XML file, the name
200 * cannot be changed and an <code>UnsupportedOperationException</code>
201 * exception is thrown. Whether this configuration has been loaded from an
202 * XML document or not can be found out using the <code>getDocument()</code>
203 * method.
204 *
205 * @param name the name of the root element
206 */
207 public void setRootElementName(String name)
208 {
209 if (getDocument() != null)
210 {
211 throw new UnsupportedOperationException("The name of the root element "
212 + "cannot be changed when loaded from an XML document!");
213 }
214 rootElementName = name;
215 }
216
217 /***
218 * Returns the <code>DocumentBuilder</code> object that is used for
219 * loading documents. If no specific builder has been set, this method
220 * returns <b>null</b>.
221 *
222 * @return the <code>DocumentBuilder</code> for loading new documents
223 * @since 1.2
224 */
225 public DocumentBuilder getDocumentBuilder()
226 {
227 return documentBuilder;
228 }
229
230 /***
231 * Sets the <code>DocumentBuilder</code> object to be used for loading
232 * documents. This method makes it possible to specify the exact document
233 * builder. So an application can create a builder, configure it for its
234 * special needs, and then pass it to this method.
235 *
236 * @param documentBuilder the document builder to be used; if undefined, a
237 * default builder will be used
238 * @since 1.2
239 */
240 public void setDocumentBuilder(DocumentBuilder documentBuilder)
241 {
242 this.documentBuilder = documentBuilder;
243 }
244
245 /***
246 * Returns the value of the validating flag.
247 *
248 * @return the validating flag
249 * @since 1.2
250 */
251 public boolean isValidating()
252 {
253 return validating;
254 }
255
256 /***
257 * Sets the value of the validating flag. This flag determines whether
258 * DTD validation should be performed when loading XML documents. This
259 * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
260 *
261 * @param validating the validating flag
262 * @since 1.2
263 */
264 public void setValidating(boolean validating)
265 {
266 this.validating = validating;
267 }
268
269 /***
270 * Returns the XML document this configuration was loaded from. The return
271 * value is <b>null</b> if this configuration was not loaded from a XML
272 * document.
273 *
274 * @return the XML document this configuration was loaded from
275 */
276 public Document getDocument()
277 {
278 return document;
279 }
280
281 /***
282 * Removes all properties from this configuration. If this configuration
283 * was loaded from a file, the associated DOM document is also cleared.
284 */
285 public void clear()
286 {
287 super.clear();
288 document = null;
289 }
290
291 /***
292 * Initializes this configuration from an XML document.
293 *
294 * @param document the document to be parsed
295 * @param elemRefs a flag whether references to the XML elements should be set
296 */
297 public void initProperties(Document document, boolean elemRefs)
298 {
299 constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);
300 }
301
302 /***
303 * Helper method for building the internal storage hierarchy. The XML
304 * elements are transformed into node objects.
305 *
306 * @param node the actual node
307 * @param element the actual XML element
308 * @param elemRefs a flag whether references to the XML elements should be set
309 */
310 private void constructHierarchy(Node node, Element element, boolean elemRefs)
311 {
312 processAttributes(node, element, elemRefs);
313 StringBuffer buffer = new StringBuffer();
314 NodeList list = element.getChildNodes();
315 for (int i = 0; i < list.getLength(); i++)
316 {
317 org.w3c.dom.Node w3cNode = list.item(i);
318 if (w3cNode instanceof Element)
319 {
320 Element child = (Element) w3cNode;
321 Node childNode = new XMLNode(child.getTagName(),
322 elemRefs ? child : null);
323 constructHierarchy(childNode, child, elemRefs);
324 node.addChild(childNode);
325 handleDelimiters(node, childNode);
326 }
327 else if (w3cNode instanceof Text)
328 {
329 Text data = (Text) w3cNode;
330 buffer.append(data.getData());
331 }
332 }
333 String text = buffer.toString().trim();
334 if (text.length() > 0 || !node.hasChildren())
335 {
336 node.setValue(text);
337 }
338 }
339
340 /***
341 * Helper method for constructing node objects for the attributes of the
342 * given XML element.
343 *
344 * @param node the actual node
345 * @param element the actual XML element
346 * @param elemRefs a flag whether references to the XML elements should be set
347 */
348 private void processAttributes(Node node, Element element, boolean elemRefs)
349 {
350 NamedNodeMap attributes = element.getAttributes();
351 for (int i = 0; i < attributes.getLength(); ++i)
352 {
353 org.w3c.dom.Node w3cNode = attributes.item(i);
354 if (w3cNode instanceof Attr)
355 {
356 Attr attr = (Attr) w3cNode;
357 for (Iterator it = PropertyConverter.split(attr.getValue(), getDelimiter()).iterator(); it.hasNext();)
358 {
359 Node child = new XMLNode(ConfigurationKey.constructAttributeKey(attr.getName()),
360 elemRefs ? element : null);
361 child.setValue(it.next());
362 node.addChild(child);
363 }
364 }
365 }
366 }
367
368 /***
369 * Deals with elements whose value is a list. In this case multiple child
370 * elements must be added.
371 *
372 * @param parent the parent element
373 * @param child the child element
374 */
375 private void handleDelimiters(Node parent, Node child)
376 {
377 if (child.getValue() != null)
378 {
379 List values = PropertyConverter.split(child.getValue().toString(),
380 getDelimiter());
381 if (values.size() > 1)
382 {
383
384 parent.remove(child);
385
386 for (Iterator it = values.iterator(); it.hasNext();)
387 {
388 Node c = new XMLNode(child.getName(), null);
389 c.setValue(it.next());
390 parent.addChild(c);
391 }
392 }
393 else if (values.size() == 1)
394 {
395
396
397 child.setValue(values.get(0));
398 }
399 }
400 }
401
402 /***
403 * Creates the <code>DocumentBuilder</code> to be used for loading files.
404 * This implementation checks whether a specific
405 * <code>DocumentBuilder</code> has been set. If this is the case, this
406 * one is used. Otherwise a default builder is created. Depending on the
407 * value of the validating flag this builder will be a validating or a non
408 * validating <code>DocumentBuilder</code>.
409 *
410 * @return the <code>DocumentBuilder</code> for loading configuration
411 * files
412 * @throws ParserConfigurationException if an error occurs
413 * @since 1.2
414 */
415 protected DocumentBuilder createDocumentBuilder()
416 throws ParserConfigurationException
417 {
418 if (getDocumentBuilder() != null)
419 {
420 return getDocumentBuilder();
421 }
422 else
423 {
424 DocumentBuilderFactory factory = DocumentBuilderFactory
425 .newInstance();
426 factory.setValidating(isValidating());
427 DocumentBuilder result = factory.newDocumentBuilder();
428
429 if (isValidating())
430 {
431
432 result.setErrorHandler(new DefaultHandler()
433 {
434 public void error(SAXParseException ex) throws SAXException
435 {
436 throw ex;
437 }
438 });
439 }
440 return result;
441 }
442 }
443
444 /***
445 * Creates a DOM document from the internal tree of configuration nodes.
446 *
447 * @return the new document
448 * @throws ConfigurationException if an error occurs
449 */
450 protected Document createDocument() throws ConfigurationException
451 {
452 try
453 {
454 if (document == null)
455 {
456 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
457 Document newDocument = builder.newDocument();
458 Element rootElem = newDocument.createElement(getRootElementName());
459 newDocument.appendChild(rootElem);
460 document = newDocument;
461 }
462
463 XMLBuilderVisitor builder = new XMLBuilderVisitor(document);
464 builder.processDocument(getRoot());
465 return document;
466 }
467 catch (DOMException domEx)
468 {
469 throw new ConfigurationException(domEx);
470 }
471 catch (ParserConfigurationException pex)
472 {
473 throw new ConfigurationException(pex);
474 }
475 }
476
477 /***
478 * Creates a new node object. This implementation returns an instance of the
479 * <code>XMLNode</code> class.
480 *
481 * @param name the node's name
482 * @return the new node
483 */
484 protected Node createNode(String name)
485 {
486 return new XMLNode(name, null);
487 }
488
489 /***
490 * Loads the configuration from the given input stream.
491 *
492 * @param in the input stream
493 * @throws ConfigurationException if an error occurs
494 */
495 public void load(InputStream in) throws ConfigurationException
496 {
497 load(new InputSource(in));
498 }
499
500 /***
501 * Load the configuration from the given reader.
502 * Note that the <code>clear()</code> method is not called, so
503 * the properties contained in the loaded file will be added to the
504 * actual set of properties.
505 *
506 * @param in An InputStream.
507 *
508 * @throws ConfigurationException if an error occurs
509 */
510 public void load(Reader in) throws ConfigurationException
511 {
512 load(new InputSource(in));
513 }
514
515 /***
516 * Loads a configuration file from the specified input source.
517 * @param source the input source
518 * @throws ConfigurationException if an error occurs
519 */
520 private void load(InputSource source) throws ConfigurationException
521 {
522 try
523 {
524 URL sourceURL = getDelegate().getURL();
525 if (sourceURL != null)
526 {
527 source.setSystemId(sourceURL.toString());
528 }
529
530 DocumentBuilder builder = createDocumentBuilder();
531 Document newDocument = builder.parse(source);
532 Document oldDocument = document;
533 document = null;
534 initProperties(newDocument, oldDocument == null);
535 document = (oldDocument == null) ? newDocument : oldDocument;
536 }
537 catch (Exception e)
538 {
539 throw new ConfigurationException(e.getMessage(), e);
540 }
541 }
542
543 /***
544 * Saves the configuration to the specified writer.
545 *
546 * @param writer the writer used to save the configuration
547 * @throws ConfigurationException if an error occurs
548 */
549 public void save(Writer writer) throws ConfigurationException
550 {
551 try
552 {
553 Transformer transformer = TransformerFactory.newInstance().newTransformer();
554 Source source = new DOMSource(createDocument());
555 Result result = new StreamResult(writer);
556
557 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
558 if (getEncoding() != null)
559 {
560 transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
561 }
562 transformer.transform(source, result);
563 }
564 catch (TransformerException e)
565 {
566 throw new ConfigurationException(e.getMessage(), e);
567 }
568 }
569
570 /***
571 * Creates a copy of this object. The new configuration object will contain
572 * the same properties as the original, but it will lose any connection to a
573 * source document (if one exists). This is to avoid race conditions if both
574 * the original and the copy are modified and then saved.
575 *
576 * @return the copy
577 */
578 public Object clone()
579 {
580 XMLConfiguration copy = (XMLConfiguration) super.clone();
581
582
583 copy.document = null;
584 copy.setDelegate(createDelegate());
585
586 copy.getRoot().visit(new NodeVisitor()
587 {
588 public void visitBeforeChildren(Node node, ConfigurationKey key)
589 {
590 node.setReference(null);
591 }
592 }, null);
593
594 return copy;
595 }
596
597 /***
598 * Creates the file configuration delegate for this object. This implementation
599 * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
600 * that deals with some specialities of <code>XMLConfiguration</code>.
601 * @return the delegate for this object
602 */
603 protected FileConfigurationDelegate createDelegate()
604 {
605 return new XMLFileConfigurationDelegate();
606 }
607
608 /***
609 * A specialized <code>Node</code> class that is connected with an XML
610 * element. Changes on a node are also performed on the associated element.
611 */
612 class XMLNode extends Node
613 {
614 /***
615 * Creates a new instance of <code>XMLNode</code> and initializes it
616 * with a name and the corresponding XML element.
617 *
618 * @param name the node's name
619 * @param elem the XML element
620 */
621 public XMLNode(String name, Element elem)
622 {
623 super(name);
624 setReference(elem);
625 }
626
627 /***
628 * Sets the value of this node. If this node is associated with an XML
629 * element, this element will be updated, too.
630 *
631 * @param value the node's new value
632 */
633 public void setValue(Object value)
634 {
635 super.setValue(value);
636
637 if (getReference() != null && document != null)
638 {
639 if (ConfigurationKey.isAttributeKey(getName()))
640 {
641 updateAttribute();
642 }
643 else
644 {
645 updateElement(value);
646 }
647 }
648 }
649
650 /***
651 * Updates the associated XML elements when a node is removed.
652 */
653 protected void removeReference()
654 {
655 if (getReference() != null)
656 {
657 Element element = (Element) getReference();
658 if (ConfigurationKey.isAttributeKey(getName()))
659 {
660 updateAttribute();
661 }
662 else
663 {
664 org.w3c.dom.Node parentElem = element.getParentNode();
665 if (parentElem != null)
666 {
667 parentElem.removeChild(element);
668 }
669 }
670 }
671 }
672
673 /***
674 * Updates the node's value if it represents an element node.
675 *
676 * @param value the new value
677 */
678 private void updateElement(Object value)
679 {
680 Text txtNode = findTextNodeForUpdate();
681 if (value == null)
682 {
683
684 if (txtNode != null)
685 {
686 ((Element) getReference()).removeChild(txtNode);
687 }
688 }
689 else
690 {
691 if (txtNode == null)
692 {
693 txtNode = document
694 .createTextNode(PropertyConverter.escapeDelimiters(
695 value.toString(), getDelimiter()));
696 if (((Element) getReference()).getFirstChild() != null)
697 {
698 ((Element) getReference()).insertBefore(txtNode,
699 ((Element) getReference()).getFirstChild());
700 }
701 else
702 {
703 ((Element) getReference()).appendChild(txtNode);
704 }
705 }
706 else
707 {
708 txtNode.setNodeValue(PropertyConverter.escapeDelimiters(
709 value.toString(), getDelimiter()));
710 }
711 }
712 }
713
714 /***
715 * Updates the node's value if it represents an attribute.
716 *
717 */
718 private void updateAttribute()
719 {
720 XMLBuilderVisitor.updateAttribute(getParent(), getName());
721 }
722
723 /***
724 * Returns the only text node of this element for update. This method is
725 * called when the element's text changes. Then all text nodes except
726 * for the first are removed. A reference to the first is returned or
727 * <b>null </b> if there is no text node at all.
728 *
729 * @return the first and only text node
730 */
731 private Text findTextNodeForUpdate()
732 {
733 Text result = null;
734 Element elem = (Element) getReference();
735
736 NodeList children = elem.getChildNodes();
737 Collection textNodes = new ArrayList();
738 for (int i = 0; i < children.getLength(); i++)
739 {
740 org.w3c.dom.Node nd = children.item(i);
741 if (nd instanceof Text)
742 {
743 if (result == null)
744 {
745 result = (Text) nd;
746 }
747 else
748 {
749 textNodes.add(nd);
750 }
751 }
752 }
753
754
755 if (result instanceof CDATASection)
756 {
757 textNodes.add(result);
758 result = null;
759 }
760
761
762 for (Iterator it = textNodes.iterator(); it.hasNext();)
763 {
764 elem.removeChild((org.w3c.dom.Node) it.next());
765 }
766 return result;
767 }
768 }
769
770 /***
771 * A concrete <code>BuilderVisitor</code> that can construct XML
772 * documents.
773 */
774 static class XMLBuilderVisitor extends BuilderVisitor
775 {
776 /*** Stores the document to be constructed. */
777 private Document document;
778
779 /***
780 * Creates a new instance of <code>XMLBuilderVisitor</code>
781 *
782 * @param doc the document to be created
783 */
784 public XMLBuilderVisitor(Document doc)
785 {
786 document = doc;
787 }
788
789 /***
790 * Processes the node hierarchy and adds new nodes to the document.
791 *
792 * @param rootNode the root node
793 */
794 public void processDocument(Node rootNode)
795 {
796 rootNode.visit(this, null);
797 }
798
799 /***
800 * Inserts a new node. This implementation ensures that the correct
801 * XML element is created and inserted between the given siblings.
802 *
803 * @param newNode the node to insert
804 * @param parent the parent node
805 * @param sibling1 the first sibling
806 * @param sibling2 the second sibling
807 * @return the new node
808 */
809 protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
810 {
811 if (ConfigurationKey.isAttributeKey(newNode.getName()))
812 {
813 updateAttribute(parent, getElement(parent), newNode.getName());
814 return null;
815 }
816
817 else
818 {
819 Element elem = document.createElement(newNode.getName());
820 if (newNode.getValue() != null)
821 {
822 elem.appendChild(document.createTextNode(
823 PropertyConverter.escapeDelimiters(newNode.getValue().toString(), getDelimiter())));
824 }
825 if (sibling2 == null)
826 {
827 getElement(parent).appendChild(elem);
828 }
829 else if (sibling1 != null)
830 {
831 getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
832 }
833 else
834 {
835 getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
836 }
837 return elem;
838 }
839 }
840
841 /***
842 * Helper method for updating the value of the specified node's
843 * attribute with the given name.
844 *
845 * @param node the affected node
846 * @param elem the element that is associated with this node
847 * @param name the name of the affected attribute
848 */
849 private static void updateAttribute(Node node, Element elem, String name)
850 {
851 if (node != null && elem != null)
852 {
853 List attrs = node.getChildren(name);
854 StringBuffer buf = new StringBuffer();
855 for (Iterator it = attrs.iterator(); it.hasNext();)
856 {
857 Node attr = (Node) it.next();
858 if (attr.getValue() != null)
859 {
860 if (buf.length() > 0)
861 {
862 buf.append(getDelimiter());
863 }
864 buf.append(PropertyConverter.escapeDelimiters(attr
865 .getValue().toString(), getDelimiter()));
866 }
867 attr.setReference(elem);
868 }
869
870 if (buf.length() < 1)
871 {
872 elem.removeAttribute(ConfigurationKey
873 .removeAttributeMarkers(name));
874 }
875 else
876 {
877 elem.setAttribute(ConfigurationKey
878 .removeAttributeMarkers(name), buf.toString());
879 }
880 }
881 }
882
883 /***
884 * Updates the value of the specified attribute of the given node.
885 * Because there can be multiple child nodes representing this attribute
886 * the new value is determined by iterating over all those child nodes.
887 *
888 * @param node the affected node
889 * @param name the name of the attribute
890 */
891 static void updateAttribute(Node node, String name)
892 {
893 if (node != null)
894 {
895 updateAttribute(node, (Element) node.getReference(), name);
896 }
897 }
898
899 /***
900 * Helper method for accessing the element of the specified node.
901 *
902 * @param node the node
903 * @return the element of this node
904 */
905 private Element getElement(Node node)
906 {
907
908 return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement();
909 }
910 }
911
912 /***
913 * A special implementation of the <code>FileConfiguration</code> interface that is
914 * used internally to implement the <code>FileConfiguration</code> methods
915 * for <code>XMLConfiguration</code>, too.
916 */
917 private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
918 {
919 public void load(InputStream in) throws ConfigurationException
920 {
921 XMLConfiguration.this.load(in);
922 }
923 }
924 }