oheger 2004/12/23 10:40:21 Modified: configuration/src/java/org/apache/commons/configuration XMLConfiguration.java Log: Merged XMLConfiguration with HierarchicalXMLConfiguration Revision Changes Path 1.20 +599 -362 jakarta-commons/configuration/src/java/org/apache/commons/configuration/XMLConfiguration.java Index: XMLConfiguration.java =================================================================== RCS file: /home/cvs/jakarta-commons/configuration/src/java/org/apache/commons/configuration/XMLConfiguration.java,v retrieving revision 1.19 retrieving revision 1.20 diff -u -r1.19 -r1.20 --- XMLConfiguration.java 19 Oct 2004 11:44:31 -0000 1.19 +++ XMLConfiguration.java 23 Dec 2004 18:40:21 -0000 1.20 @@ -17,16 +17,20 @@ package org.apache.commons.configuration; import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; import java.io.Reader; -import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; + import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -35,540 +39,773 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import org.apache.commons.lang.StringUtils; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; -import org.w3c.dom.CharacterData; +import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.InputSource; +import org.apache.commons.configuration.reloading.ReloadingStrategy; /** - * Reads a XML configuration file. - * - * To retrieve the value of an attribute of an element, use - * <code>[EMAIL PROTECTED]</code>. The '@' symbol was chosen for consistency - * with XPath. - * + * A specialized hierarchical configuration class that is able to parse XML + * documents. + * + * <p>The parsed document will be stored keeping its structure. The class also + * tries to preserve as much information from the loaded XML document as + * possible, including comments and processing instructions. These will be + * contained in documents created by the <code>save()</code> methods, too. + * * @since commons-configuration 1.0 - * - * @author Jörg Schaible - * @author <a href="mailto:[EMAIL PROTECTED]">Kelvin Tan</a> - * @author <a href="mailto:[EMAIL PROTECTED]">Daniel Rall</a> - * @author <a href="mailto:[EMAIL PROTECTED]">Emmanuel Bourg</a> + * + * @author Jörg Schaible + * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Heger </a> * @version $Revision$, $Date$ */ -public class XMLConfiguration extends AbstractFileConfiguration +public class XMLConfiguration extends HierarchicalConfiguration implements FileConfiguration { - // For conformance with xpath - private static final String ATTRIBUTE_START = "[@"; + /** Constant for the default root element name. */ + private static final String DEFAULT_ROOT_NAME = "configuration"; - private static final String ATTRIBUTE_END = "]"; + /** Delimiter character for attributes. */ + private static char ATTR_DELIMITER = ','; - /** - * For consistency with properties files. Access nodes via an "A.B.C" - * notation. - */ - private static final String NODE_DELIMITER = "."; + private FileConfigurationDelegate delegate = new FileConfigurationDelegate(); - /** - * The XML document from our data source. - */ + /** The document from this configuration's data source. */ private Document document; + /** Stores the name of the root element. */ + private String rootElementName; + /** - * Creates an empty XML configuration. + * Creates a new instance of <code>XMLConfiguration</code>. */ public XMLConfiguration() { - // build an empty document. - DocumentBuilder builder = null; - try - { - builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } - catch (ParserConfigurationException e) - { - throw new ConfigurationRuntimeException(e.getMessage(), e); - } - - document = builder.newDocument(); - document.appendChild(document.createElement("configuration")); + super(); } /** - * Creates and loads the XML configuration from the specified file. - * - * @param fileName The name of the file to load. - * - * @throws ConfigurationException Error while loading the XML file + * Creates a new instance of <code>XMLConfiguration</code>. + * The configuration is loaded from the specified file + * + * @param fileName the name of the file to load + * @throws ConfigurationException if the file cannot be loaded */ public XMLConfiguration(String fileName) throws ConfigurationException { - super(fileName); + this(); + setFileName(fileName); + load(); } /** - * Creates and loads the XML configuration from the specified file. - * - * @param file The XML file to load. - * @throws ConfigurationException Error while loading the XML file + * Creates a new instance of <code>XMLConfiguration</code>. + * The configuration is loaded from the specified file. + * + * @param file the file + * @throws ConfigurationException if an error occurs while loading the file */ public XMLConfiguration(File file) throws ConfigurationException { - super(file); + this(); + setFile(file); + if (file.exists()) + { + load(); + } } /** - * Creates and loads the XML configuration from the specified URL. - * - * @param url The location of the XML file to load. - * @throws ConfigurationException Error while loading the XML file + * Creates a new instance of <code>XMLConfiguration</code>. + * The configuration is loaded from the specified URL. + * + * @param url the URL + * @throws ConfigurationException if loading causes an error */ public XMLConfiguration(URL url) throws ConfigurationException { - super(url); + this(); + setURL(url); + load(); } /** - * [EMAIL PROTECTED] + * Returns the name of the root element. If this configuration was loaded + * from a XML document, the name of this document's root element is + * returned. Otherwise it is possible to set a name for the root element + * that will be used when this configuration is stored. + * + * @return the name of the root element */ - public void load(Reader in) throws ConfigurationException + public String getRootElementName() { - try + if (getDocument() == null) { - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - document = builder.parse(new InputSource(in)); + return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName; } - catch (Exception e) + else { - throw new ConfigurationException(e.getMessage(), e); + return getDocument().getDocumentElement().getNodeName(); + } + } + + /** + * Sets the name of the root element. This name is used when this + * configuration object is stored in an XML file. Note that setting the name + * of the root element works only if this configuration has been newly + * created. If the configuration was loaded from an XML file, the name + * cannot be changed and an <code>UnsupportedOperationException</code> + * exception is thrown. Whether this configuration has been loaded from an + * XML document or not can be found out using the <code>getDocument()</code> + * method. + * + * @param name the name of the root element + */ + public void setRootElementName(String name) + { + if (getDocument() != null) + { + throw new UnsupportedOperationException("The name of the root element " + + "cannot be changed when loaded from an XML document!"); } + rootElementName = name; + } + + /** + * Returns the XML document this configuration was loaded from. The return + * value is <b>null</b> if this configuration was not loaded from a XML + * document. + * + * @return the XML document this configuration was loaded from + */ + public Document getDocument() + { + return document; + } - initProperties(document.getDocumentElement(), new StringBuffer()); + /** + * @inheritDoc + */ + protected void addPropertyDirect(String key, Object obj) + { + super.addPropertyDirect(key, obj); + delegate.possiblySave(); } /** - * Loads and initializes from the XML file. - * - * @param element The element to start processing from. Callers should supply the root element of the document. - * @param hierarchy + * @inheritDoc */ - private void initProperties(Element element, StringBuffer hierarchy) + public void clearProperty(String key) { + super.clearProperty(key); + delegate.possiblySave(); + } + + /** + * @inheritDoc + */ + public void setProperty(String key, Object value) + { + super.setProperty(key, value); + delegate.possiblySave(); + } + + /** + * Initializes this configuration from an XML document. + * + * @param document the document to be parsed + */ + public void initProperties(Document document) + { + constructHierarchy(getRoot(), document.getDocumentElement()); + } + + /** + * Helper method for building the internal storage hierarchy. The XML + * elements are transformed into node objects. + * + * @param node the actual node + * @param element the actual XML element + */ + private void constructHierarchy(Node node, Element element) + { + processAttributes(node, element); StringBuffer buffer = new StringBuffer(); NodeList list = element.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { - Node node = list.item(i); - if (node instanceof Element) + org.w3c.dom.Node w3cNode = list.item(i); + if (w3cNode instanceof Element) { - Element child = (Element) node; - - StringBuffer subhierarchy = new StringBuffer(hierarchy.toString()); - subhierarchy.append(child.getTagName()); - processAttributes(subhierarchy.toString(), child); - initProperties(child, subhierarchy.append(NODE_DELIMITER)); + Element child = (Element) w3cNode; + Node childNode = new XMLNode(child.getTagName(), child); + constructHierarchy(childNode, child); + node.addChild(childNode); } - else if (node instanceof CDATASection || node instanceof Text) + else if (w3cNode instanceof Text) { - CharacterData data = (CharacterData) node; + Text data = (Text) w3cNode; buffer.append(data.getData()); } } - String text = buffer.toString().trim(); - if (text.length() > 0 && hierarchy.length() > 0) + if (text.length() > 0) { - super.addProperty(hierarchy.substring(0, hierarchy.length() - 1), text); + node.setValue(text); } } /** - * Helper method for constructing properties for the attributes of the given - * XML element. - * - * @param hierarchy the actual hierarchy - * @param element the actual XML element + * Helper method for constructing node objects for the attributes of the + * given XML element. + * + * @param node the actual node + * @param element the actual XML element */ - private void processAttributes(String hierarchy, Element element) + private void processAttributes(Node node, Element element) { - // Add attributes as x.y{ATTRIBUTE_START}att{ATTRIBUTE_END} NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); ++i) { - Attr attr = (Attr) attributes.item(i); - String attrName = hierarchy + ATTRIBUTE_START + attr.getName() + ATTRIBUTE_END; - super.addProperty(attrName, attr.getValue()); + org.w3c.dom.Node w3cNode = attributes.item(i); + if (w3cNode instanceof Attr) + { + Attr attr = (Attr) w3cNode; + for (Iterator it = PropertyConverter.split(attr.getValue(), ATTR_DELIMITER).iterator(); it.hasNext();) + { + Node child = new XMLNode(ConfigurationKey.constructAttributeKey(attr.getName()), element); + child.setValue(it.next()); + node.addChild(child); + } + } } } /** - * Calls super method, and also ensures the underlying [EMAIL PROTECTED] Document} is - * modified so changes are persisted when saved. - * - * @param name - * @param value + * Creates a DOM document from the internal tree of configuration nodes. + * + * @return the new document + * @throws ConfigurationException if an error occurs */ - public void addProperty(String name, Object value) - { - addXmlProperty(name, value); - super.addProperty(name, value); - } - - Object getXmlProperty(String name) + protected Document createDocument() throws ConfigurationException { - // parse the key - String[] nodes = parseElementNames(name); - String attName = parseAttributeName(name); - - // get all the matching elements - List children = findElementsForPropertyNodes(nodes); - - List properties = new ArrayList(); - if (attName == null) + try { - // return text contents of elements - Iterator cIter = children.iterator(); - while (cIter.hasNext()) + if (document == null) { - Element child = (Element) cIter.next(); - // add non-empty strings - String text = getChildText(child); - if (StringUtils.isNotEmpty(text)) - { - properties.add(text); - } + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document newDocument = builder.newDocument(); + Element rootElem = newDocument.createElement(getRootElementName()); + newDocument.appendChild(rootElem); + document = newDocument; } - } - else + + XMLBuilderVisitor builder = new XMLBuilderVisitor(document); + builder.processDocument(getRoot()); + return document; + } /* try */ + catch (DOMException domEx) { - // return text contents of attributes - Iterator cIter = children.iterator(); - while (cIter.hasNext()) - { - Element child = (Element) cIter.next(); - if (child.hasAttribute(attName)) - { - properties.add(child.getAttribute(attName)); - } - } + throw new ConfigurationException(domEx); } - - switch (properties.size()) + catch (ParserConfigurationException pex) { - case 0: - return null; - case 1: - return properties.get(0); - default: - return properties; + throw new ConfigurationException(pex); } } /** - * TODO Add comment. - * - * @param nodes - * @return + * Creates a new node object. This implementation returns an instance of the + * <code>XMLNode</code> class. + * + * @param name the node's name + * @return the new node */ - private List findElementsForPropertyNodes(String[] nodes) + protected Node createNode(String name) { - List children = new ArrayList(); - List elements = new ArrayList(); + return new XMLNode(name, null); + } - children.add(document.getDocumentElement()); - for (int i = 0; i < nodes.length; i++) - { - elements.clear(); - elements.addAll(children); - children.clear(); + public void load() throws ConfigurationException + { + delegate.load(); + } - String eName = nodes[i]; - Iterator eIter = elements.iterator(); - while (eIter.hasNext()) - { - Element element = (Element) eIter.next(); - NodeList list = element.getChildNodes(); - for (int j = 0; j < list.getLength(); j++) - { - Node node = list.item(j); - if (node instanceof Element) - { - Element child = (Element) node; - if (eName.equals(child.getTagName())) - { - children.add(child); - } - } - } - } - } + public void load(String fileName) throws ConfigurationException + { + delegate.load(fileName); + } + + public void load(File file) throws ConfigurationException + { + delegate.load(file); + } + + public void load(URL url) throws ConfigurationException + { + delegate.load(url); + } - return children; + public void load(InputStream in) throws ConfigurationException + { + delegate.load(in); + } + + public void load(InputStream in, String encoding) throws ConfigurationException + { + delegate.load(in, encoding); } - private static String getChildText(Node node) + public void load(Reader in) throws ConfigurationException { - // is there anything to do? - if (node == null) + try { - return null; + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document newDocument = builder.parse(new InputSource(in)); + document = null; + initProperties(newDocument); + document = newDocument; } - - // concatenate children text - StringBuffer str = new StringBuffer(); - Node child = node.getFirstChild(); - while (child != null) + catch (Exception e) { - short type = child.getNodeType(); - if (type == Node.TEXT_NODE) - { - str.append(child.getNodeValue()); - } - else if (type == Node.CDATA_SECTION_NODE) - { - str.append(child.getNodeValue()); - } - child = child.getNextSibling(); + throw new ConfigurationException(e.getMessage(), e); } + } - // return text value - return StringUtils.trimToNull(str.toString()); + public void save() throws ConfigurationException + { + delegate.save(); + } + public void save(String fileName) throws ConfigurationException + { + delegate.save(fileName); } - private Element getChildElementWithName(String eName, Element element) + public void save(File file) throws ConfigurationException { - Element child = null; + delegate.save(file); + } - NodeList list = element.getChildNodes(); - for (int j = 0; j < list.getLength(); j++) - { - Node node = list.item(j); - if (node instanceof Element) - { - child = (Element) node; - if (eName.equals(child.getTagName())) - { - break; - } - child = null; - } - } - return child; + public void save(URL url) throws ConfigurationException + { + delegate.save(url); } - /** - * Adds the property value in our document tree. - * - * @param name The name of the element to set a value for. - * @param value The value to set. - */ - private void addXmlProperty(String name, Object value) + public void save(OutputStream out) throws ConfigurationException { - // parse the key - String[] nodes = parseElementNames(name); - String attName = parseAttributeName(name); + delegate.save(out); + } - Element element = document.getDocumentElement(); - Element parent = element; + public void save(OutputStream out, String encoding) throws ConfigurationException + { + delegate.save(out, encoding); + } - for (int i = 0; i < nodes.length; i++) + /** + * Saves the configuration to the specified writer. + * + * @param writer the writer used to save the configuration + * @throws ConfigurationException if an error occurs + */ + public void save(Writer writer) throws ConfigurationException + { + try { - if (element == null) - { - break; - } - parent = element; - String eName = nodes[i]; - Element child = getChildElementWithName(eName, element); - - element = child; - } + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + Source source = new DOMSource(createDocument()); + Result result = new StreamResult(writer); - Element child = document.createElement(nodes[nodes.length - 1]); - parent.appendChild(child); - if (attName == null) - { - CharacterData data = document.createTextNode(String.valueOf(value)); - child.appendChild(data); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(source, result); } - else + catch (TransformerException e) { - child.setAttribute(attName, String.valueOf(value)); + throw new ConfigurationException(e.getMessage(), e); } } - /** - * Calls super method, and also ensures the underlying [EMAIL PROTECTED] Document}is - * modified so changes are persisted when saved. - * - * @param name The name of the property to clear. - */ - public void clearProperty(String name) + public String getFileName() + { + return delegate.getFileName(); + } + + public void setFileName(String fileName) + { + delegate.setFileName(fileName); + } + + public String getBasePath() + { + return delegate.getBasePath(); + } + + public void setBasePath(String basePath) + { + delegate.setBasePath(basePath); + } + + public File getFile() + { + return delegate.getFile(); + } + + public void setFile(File file) + { + delegate.setFile(file); + } + + public URL getURL() + { + return delegate.getURL(); + } + + public void setURL(URL url) + { + delegate.setURL(url); + } + + public void setAutoSave(boolean autoSave) + { + delegate.setAutoSave(autoSave); + } + + public boolean isAutoSave() { - clearXmlProperty(name); - super.clearProperty(name); + return delegate.isAutoSave(); } - private void clearXmlProperty(String name) + public ReloadingStrategy getReloadingStrategy() { - // parse the key - String[] nodes = parseElementNames(name); - String attName = parseAttributeName(name); + return delegate.getReloadingStrategy(); + } - // get all the matching elements - List children = findElementsForPropertyNodes(nodes); + public void setReloadingStrategy(ReloadingStrategy strategy) + { + delegate.setReloadingStrategy(strategy); + } - if (attName == null) + public void reload() + { + delegate.reload(); + } + + /** + * A specialized <code>Node</code> class that is connected with an XML + * element. Changes on a node are also performed on the associated element. + */ + class XMLNode extends Node + { + /** + * Creates a new instance of <code>XMLNode</code> and initializes it + * with the corresponding XML element. + * + * @param elem the XML element + */ + public XMLNode(Element elem) + { + super(); + setReference(elem); + } + + /** + * Creates a new instance of <code>XMLNode</code> and initializes it + * with a name and the corresponding XML element. + * + * @param name the node's name + * @param elem the XML element + */ + public XMLNode(String name, Element elem) { - // remove children with no subelements - Iterator cIter = children.iterator(); - while (cIter.hasNext()) + super(name); + setReference(elem); + } + + /** + * Sets the value of this node. If this node is associated with an XML + * element, this element will be updated, too. + * + * @param value the node's new value + */ + public void setValue(Object value) + { + super.setValue(value); + + if (getReference() != null && document != null) { - Element child = (Element) cIter.next(); + if (ConfigurationKey.isAttributeKey(getName())) + { + updateAttribute(); + } + else + { + updateElement(value); + } + } + } - // determine if child has subelments - boolean hasSubelements = false; - Node subchild = child.getFirstChild(); - while (subchild != null) + /** + * Updates the associated XML elements when a node is removed. + */ + protected void removeReference() + { + if (getReference() != null) + { + Element element = (Element) getReference(); + if (ConfigurationKey.isAttributeKey(getName())) { - if (subchild.getNodeType() == Node.ELEMENT_NODE) + updateAttribute(); + } + else + { + org.w3c.dom.Node parentElem = element.getParentNode(); + if (parentElem != null) { - hasSubelements = true; - break; + parentElem.removeChild(element); } - subchild = subchild.getNextSibling(); } + } + } - if (!hasSubelements) + /** + * Updates the node's value if it represents an element node. + * + * @param value the new value + */ + private void updateElement(Object value) + { + Text txtNode = findTextNodeForUpdate(); + if (value == null) + { + // remove text + if (txtNode != null) + { + ((Element) getReference()).removeChild(txtNode); + } + } + else + { + if (txtNode == null) { - // safe to remove - if (!child.hasAttributes()) + txtNode = document.createTextNode(value.toString()); + if (((Element) getReference()).getFirstChild() != null) { - // remove entire node - Node parent = child.getParentNode(); - parent.removeChild(child); + ((Element) getReference()).insertBefore(txtNode, ((Element) getReference()).getFirstChild()); } else { - // only remove node contents - subchild = child.getLastChild(); - while (subchild != null) - { - child.removeChild(subchild); - subchild = child.getLastChild(); - } + ((Element) getReference()).appendChild(txtNode); } } + else + { + txtNode.setNodeValue(value.toString()); + } } } - else - { - // remove attributes from children - Iterator cIter = children.iterator(); - while (cIter.hasNext()) + + /** + * Updates the node's value if it represents an attribute. + * + */ + private void updateAttribute() + { + XMLBuilderVisitor.updateAttribute(getParent(), getName()); + } + + /** + * Returns the only text node of this element for update. This method is + * called when the element's text changes. Then all text nodes except + * for the first are removed. A reference to the first is returned or + * <b>null </b> if there is no text node at all. + * + * @return the first and only text node + */ + private Text findTextNodeForUpdate() + { + Text result = null; + Element elem = (Element) getReference(); + // Find all Text nodes + NodeList children = elem.getChildNodes(); + Collection textNodes = new ArrayList(); + for (int i = 0; i < children.getLength(); i++) { - Element child = (Element) cIter.next(); - child.removeAttribute(attName); + org.w3c.dom.Node nd = children.item(i); + if (nd instanceof Text) + { + if (result == null) + { + result = (Text) nd; + } + else + { + textNodes.add(nd); + } + } + } + + // We don't want CDATAs + if (result instanceof CDATASection) + { + textNodes.add(result); + result = null; } + + // Remove all but the first Text node + for (Iterator it = textNodes.iterator(); it.hasNext();) + { + elem.removeChild((org.w3c.dom.Node) it.next()); + } + return result; } } /** - * [EMAIL PROTECTED] + * A concrete <code>BuilderVisitor</code> that can construct XML + * documents. */ - public void save(Writer writer) throws ConfigurationException + static class XMLBuilderVisitor extends BuilderVisitor { - try - { - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - Source source = new DOMSource(document); - Result result = new StreamResult(writer); + /** Stores the document to be constructed. */ + private Document document; - transformer.setOutputProperty("indent", "yes"); - transformer.transform(source, result); - } - catch (TransformerException e) + /** + * Creates a new instance of <code>XMLBuilderVisitor</code> + * + * @param doc the document to be created + */ + public XMLBuilderVisitor(Document doc) { - throw new ConfigurationException(e.getMessage(), e); + document = doc; } - } - public String toString() - { - StringWriter writer = new StringWriter(); - try + /** + * Processes the node hierarchy and adds new nodes to the document. + * + * @param rootNode the root node + */ + public void processDocument(Node rootNode) { - save(writer); + rootNode.visit(this, null); } - catch (ConfigurationException e) + + /** + * @inheritDoc + */ + protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2) { - e.printStackTrace(); + if (ConfigurationKey.isAttributeKey(newNode.getName())) + { + updateAttribute(parent, getElement(parent), newNode.getName()); + return null; + } + + else + { + Element elem = document.createElement(newNode.getName()); + if (newNode.getValue() != null) + { + elem.appendChild(document.createTextNode(newNode.getValue().toString())); + } + if (sibling2 == null) + { + getElement(parent).appendChild(elem); + } + else if (sibling1 != null) + { + getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling()); + } + else + { + getElement(parent).insertBefore(elem, getElement(parent).getFirstChild()); + } + return elem; + } } - return writer.toString(); - } - /** - * Parse a property key and return an array of the element hierarchy it - * specifies. For example the key "[EMAIL PROTECTED]" will result in [x, y, z]. - * - * @param key the key to parse - * - * @return the elements in the key - */ - protected static String[] parseElementNames(String key) - { - if (key == null) + /** + * Helper method for updating the value of the specified node's + * attribute with the given name. + * + * @param node the affected node + * @param elem the element that is associated with this node + * @param name the name of the affected attribute + */ + private static void updateAttribute(Node node, Element elem, String name) { - return new String[]{}; + if (node != null && elem != null) + { + List attrs = node.getChildren(name); + StringBuffer buf = new StringBuffer(); + for (Iterator it = attrs.iterator(); it.hasNext();) + { + Node attr = (Node) it.next(); + if (attr.getValue() != null) + { + if (buf.length() > 0) + { + buf.append(ATTR_DELIMITER); + } + buf.append(attr.getValue()); + } + attr.setReference(elem); + } + + if (buf.length() < 1) + { + elem.removeAttribute(ConfigurationKey.removeAttributeMarkers(name)); + } + else + { + elem.setAttribute(ConfigurationKey.removeAttributeMarkers(name), buf.toString()); + } + } } - else - { - // find the beginning of the attribute name - int attStart = key.indexOf(ATTRIBUTE_START); - if (attStart > -1) + /** + * Updates the value of the specified attribute of the given node. + * Because there can be multiple child nodes representing this attribute + * the new value is determined by iterating over all those child nodes. + * + * @param node the affected node + * @param name the name of the attribute + */ + static void updateAttribute(Node node, String name) + { + if (node != null) { - // remove the attribute part of the key - key = key.substring(0, attStart); + updateAttribute(node, (Element) node.getReference(), name); } + } - return StringUtils.split(key, NODE_DELIMITER); + /** + * Helper method for accessing the element of the specified node. + * + * @param node the node + * @return the element of this node + */ + private Element getElement(Node node) + { + // special treatement for root node of the hierarchy + return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement(); } } - /** - * Parse a property key and return the attribute name if it existst. - * - * @param key the key to parse - * - * @return the attribute name, or null if the key doesn't contain one - */ - protected static String parseAttributeName(String key) + private class FileConfigurationDelegate extends AbstractFileConfiguration { - String name = null; - - if (key != null) + public void load(Reader in) throws ConfigurationException { - // find the beginning of the attribute name - int attStart = key.indexOf(ATTRIBUTE_START); - - if (attStart > -1) - { - // find the end of the attribute name - int attEnd = key.indexOf(ATTRIBUTE_END); - attEnd = attEnd > -1 ? attEnd : key.length(); - - name = key.substring(attStart + ATTRIBUTE_START.length(), attEnd); - } + XMLConfiguration.this.load(in); } - return name; + public void save(Writer out) throws ConfigurationException + { + XMLConfiguration.this.save(out); + } } -} +} \ No newline at end of file
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]