You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by oh...@apache.org on 2004/12/23 19:40:22 UTC

cvs commit: jakarta-commons/configuration/src/java/org/apache/commons/configuration XMLConfiguration.java

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>X.Y.Z[@attribute]</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:kelvint@apache.org">Kelvin Tan</a>
  - * @author <a href="mailto:dlr@apache.org">Daniel Rall</a>
  - * @author <a href="mailto:ebourg@apache.org">Emmanuel Bourg</a>
  + * 
  + * @author J&ouml;rg Schaible
  + * @author <a href="mailto:oliver.heger@t-online.de">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();
       }
   
       /**
  -     * {@inheritDoc}
  +     * 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 {@link 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 {@link 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;
           }
       }
   
       /**
  -     * {@inheritDoc}
  +     * 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 "x.y.z[@abc]" 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: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org