Uploaded image for project: 'Commons Configuration'
  1. Commons Configuration
  2. CONFIGURATION-152

[configuration][PATCH]HierarchicalConfiguration and XMLReader

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Resolved
    • Minor
    • Resolution: Duplicate
    • None
    • None
    • None
    • None
    • Operating System: other
      Platform: Other

    • 24262

    Description

      Index: conf/testHierarchicalDOM4JConfiguration.xml
      ===================================================================
      RCS file: conf/testHierarchicalDOM4JConfiguration.xml
      diff -N conf/testHierarchicalDOM4JConfiguration.xml
      — /dev/null 1 Jan 1970 00:00:00 -0000
      +++ conf/testHierarchicalDOM4JConfiguration.xml 30 Oct 2003 17:27:04 -0000
      @@ -0,0 +1,56 @@
      +<?xml version="1.0" encoding="ISO-8859-1" ?>
      +
      +<database>
      + <tables>
      + <table tableType="system">
      + <name>users</name>
      + <fields>
      + <field>
      + <name>uid</name>
      + <type>long</type>
      + </field>
      + <field>
      + <name>uname</name>
      + <type>java.lang.String</type>
      + </field>
      + <field>
      + <name>firstName</name>
      + <type>java.lang.String</type>
      + </field>
      + <field>
      + <name>lastName</name>
      + <type>java.lang.String</type>
      + </field>
      + <field>
      + <name>email</name>
      + <type>java.lang.String</type>
      + </field>
      + </fields>
      + </table>
      + <table tableType="application">
      + <name>documents</name>
      + <fields>
      + <field>
      + <name>docid</name>
      + <type>long</type>
      + </field>
      + <field>
      + <name>name</name>
      + <type>java.lang.String</type>
      + </field>
      + <field>
      + <name>creationDate</name>
      + <type>java.util.Date</type>
      + </field>
      + <field>
      + <name>authorID</name>
      + <type>long</type>
      + </field>
      + <field>
      + <name>version</name>
      + <type>int</type>
      + </field>
      + </fields>
      + </table>
      + </tables>
      +</database>
      Index: src/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java
      ===================================================================
      RCS file: src/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java
      diff -N src/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java
      — /dev/null 1 Jan 1970 00:00:00 -0000
      +++ src/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java 30
      Oct 2003 17:27:05 -0000
      @@ -0,0 +1,168 @@
      +package org.apache.commons.configuration;
      +
      +/* ====================================================================
      + * The Apache Software License, Version 1.1
      + *
      + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
      + * reserved.
      + *
      + * Redistribution and use in source and binary forms, with or without
      + * modification, are permitted provided that the following conditions
      + * are met:
      + *
      + * 1. Redistributions of source code must retain the above copyright
      + * notice, this list of conditions and the following disclaimer.
      + *
      + * 2. Redistributions in binary form must reproduce the above copyright
      + * notice, this list of conditions and the following disclaimer in
      + * the documentation and/or other materials provided with the
      + * distribution.
      + *
      + * 3. The end-user documentation included with the redistribution, if
      + * any, must include the following acknowledgement:
      + * "This product includes software developed by the
      + * Apache Software Foundation (http://www.apache.org/)."
      + * Alternately, this acknowledgement may appear in the software itself,
      + * if and wherever such third-party acknowledgements normally appear.
      + *
      + * 4. The names "The Jakarta Project", "Commons", and "Apache Software
      + * Foundation" must not be used to endorse or promote products derived
      + * from this software without prior written permission. For written
      + * permission, please contact apache@apache.org.
      + *
      + * 5. Products derived from this software may not be called "Apache"
      + * nor may "Apache" appear in their names without prior written
      + * permission of the Apache Software Foundation.
      + *
      + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
      + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
      + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
      + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
      + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
      + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      + * SUCH DAMAGE.
      + * ====================================================================
      + *
      + * This software consists of voluntary contributions made by many
      + * individuals on behalf of the Apache Software Foundation. For more
      + * information on the Apache Software Foundation, please see
      + * <http://www.apache.org/>.
      + */
      +
      +import java.io.IOException;
      +
      +import org.xml.sax.SAXException;
      +
      +/**
      + * <p>A specialized SAX2 XML parser that processes configuration objects.</p>
      + * <p>This class mimics to be a SAX compliant XML parser. It is able to iterate
      + * over the keys in a configuration object and to generate corresponding SAX
      + * events. By registering a <code>ContentHandler</code> at an instance
      + * it is possible to perform XML processing on a configuration object.</p>
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + * @version $Id: $
      + */
      +public class BaseConfigurationXMLReader extends ConfigurationXMLReader
      +{
      + /** Stores the actual configuration.*/
      + private Configuration config;
      +
      + /**
      + * Creates a new instance of <code>BaseConfigurationXMLReader</code>.
      + */
      + public BaseConfigurationXMLReader()
      +

      { + super(); + }
      +
      + /**
      + * Creates a new instance of <code>BaseConfigurationXMLReader</code> and
      + * sets the configuration object to be parsed.
      + * @param conf the configuration to be parsed
      + */
      + public BaseConfigurationXMLReader(Configuration conf)
      + { + this(); + setConfiguration(conf); + }
      +
      + /**
      + * Returns the actual configuration to be processed.
      + * @return the actual configuration
      + */
      + public Configuration getConfiguration()
      + { + return config; + }
      +
      + /**
      + * Sets the configuration to be processed.
      + * @param conf the configuration
      + */
      + public void setConfiguration(Configuration conf)
      + { + config = conf; + }
      +
      + /**
      + * Returns the configuration to be processed.
      + * @return the actual configuration
      + */
      + public Configuration getParsedConfiguration()
      + { + return getConfiguration(); + }
      +
      + /**
      + * The main SAX event generation method. This element uses an internal
      + * <code>HierarchicalConfigurationConverter</code> object to iterate over
      + * all keys in the actual configuration and to generate corresponding SAX
      + * events.
      + * @throws IOException if no configuration object is specified
      + * @throws SAXException if a SAXException occurs during parsing
      + */
      + protected void processKeys() throws IOException, SAXException
      + { + fireElementStart(getRootName(), null); + new SAXConverter().process(getConfiguration()); + fireElementEnd(getRootName()); + }
      +
      + /**
      + * An internally used helper class to iterate over all configuration keys
      + * ant to generate corresponding SAX events.
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + */
      + class SAXConverter extends HierarchicalConfigurationConverter
      + {
      + /**
      + * Callback for the start of an element.
      + * @param name the element name
      + * @param value the element value
      + */
      + protected void elementStart(String name, Object value)
      + {
      + fireElementStart(name, null);
      + if(value != null)
      + { + fireCharacters(value.toString()); + } /* if */
      + }
      +
      + /**
      + * Callback for the end of an element.
      + * @param name the element name
      + */
      + protected void elementEnd(String name)
      + { + fireElementEnd(name); + }
      + }
      +}
      Index: src/java/org/apache/commons/configuration/ConfigurationKey.java
      ===================================================================
      RCS file: src/java/org/apache/commons/configuration/ConfigurationKey.java
      diff -N src/java/org/apache/commons/configuration/ConfigurationKey.java
      — /dev/null 1 Jan 1970 00:00:00 -0000
      +++ src/java/org/apache/commons/configuration/ConfigurationKey.java 30 Oct 2003
      17:27:12 -0000
      @@ -0,0 +1,635 @@
      +package org.apache.commons.configuration;
      +
      +/* ====================================================================
      + * The Apache Software License, Version 1.1
      + *
      + * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
      + * reserved.
      + *
      + * Redistribution and use in source and binary forms, with or without
      + * modification, are permitted provided that the following conditions
      + * are met:
      + *
      + * 1. Redistributions of source code must retain the above copyright
      + * notice, this list of conditions and the following disclaimer.
      + *
      + * 2. Redistributions in binary form must reproduce the above copyright
      + * notice, this list of conditions and the following disclaimer in
      + * the documentation and/or other materials provided with the
      + * distribution.
      + *
      + * 3. The end-user documentation included with the redistribution, if
      + * any, must include the following acknowlegement:
      + * "This product includes software developed by the
      + * Apache Software Foundation (http://www.apache.org/)."
      + * Alternately, this acknowlegement may appear in the software itself,
      + * if and wherever such third-party acknowlegements normally appear.
      + *
      + * 4. The names "The Jakarta Project", "Commons", and "Apache Software
      + * Foundation" must not be used to endorse or promote products derived
      + * from this software without prior written permission. For written
      + * permission, please contact apache@apache.org.
      + *
      + * 5. Products derived from this software may not be called "Apache"
      + * nor may "Apache" appear in their names without prior written
      + * permission of the Apache Group.
      + *
      + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
      + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
      + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
      + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
      + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
      + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      + * SUCH DAMAGE.
      + * ====================================================================
      + *
      + * This software consists of voluntary contributions made by many
      + * individuals on behalf of the Apache Software Foundation. For more
      + * information on the Apache Software Foundation, please see
      + * <http://www.apache.org/>.
      + */
      +
      +import java.io.Serializable;
      +import java.util.Iterator;
      +import java.util.NoSuchElementException;
      +
      +/**
      + * <p>A simple class that supports creation of and iteration on complex
      + * configuration keys.</p>
      + * <p>For key creation the class works similar to a StringBuffer: There are
      + * several <code>appendXXXX()</code> methods with which single parts
      + * of a key can be constructed. All these methods return a reference to the
      + * actual object so they can be written in a chain. When using this methods
      + * the exact syntax for keys need not be known.</p>
      + * <p>This class also defines a specialized iterator for configuration keys.
      + * With such an iterator a key can be tokenized into its single parts. For
      + * each part it can be checked whether it has an associated index.</p>
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + * @version $Id: $
      + */
      +public class ConfigurationKey implements Serializable
      +{
      + /** Constant for an attribute start marker.*/
      + private static final String ATTRIBUTE_START = "[@";
      +
      + /** Constant for an attribute end marker.*/
      + private static final String ATTRIBUTE_END = "]";
      +
      + /** Constant for a property delimiter.*/
      + private static final char PROPERTY_DELIMITER = '.';
      +
      + /** Constant for an index start marker.*/
      + private static final char INDEX_START = '(';
      +
      + /** Constant for an index end marker.*/
      + private static final char INDEX_END = ')';
      +
      + /** Constant for the initial StringBuffer size.*/
      + private static final int INITIAL_SIZE = 32;
      +
      + /** Holds a buffer with the so far created key.*/
      + private StringBuffer keyBuffer;
      +
      + /**
      + * Creates a new, empty instance of <code>ConfigurationKey</code>.
      + */
      + public ConfigurationKey()
      + { + keyBuffer = new StringBuffer(INITIAL_SIZE); + }
      +
      + /**
      + * Creates a new instance of <code>ConfigurationKey</code> and
      + * initializes it with the given key.
      + * @param key the key as a string
      + */
      + public ConfigurationKey(String key)
      + { + keyBuffer = new StringBuffer(key); + removeTrailingDelimiter(); + }
      +
      + /**
      + * Appends the name of a property to this key. If necessary, a
      + * property delimiter will be added.
      + * @param property the name of the property to be added
      + * @return a reference to this object
      + */
      + public ConfigurationKey append(String property)
      + {
      + if(keyBuffer.length() > 0 && !hasDelimiter()
      + && !isAttributeKey(property))
      + { + keyBuffer.append(PROPERTY_DELIMITER); + } /* if */
      +
      + keyBuffer.append(property);
      + removeTrailingDelimiter();
      + return this;
      + }
      +
      + /**
      + * Appends an index to this configuration key.
      + * @param index the index to be appended
      + * @return a reference to this object
      + */
      + public ConfigurationKey appendIndex(int index)
      + { + keyBuffer.append(INDEX_START).append(index); + keyBuffer.append(INDEX_END); + return this; + }
      +
      + /**
      + * Appends an attribute to this configuration key.
      + * @param attr the name of the attribute to be appended
      + * @return a reference to this object
      + */
      + public ConfigurationKey appendAttribute(String attr)
      + { + keyBuffer.append(constructAttributeKey(attr)); + return this; + }
      +
      + /**
      + * Checks if the passed in key is an attribute key. Such attribute keys
      + * start and end with certain marker strings. In some cases they must be
      + * treated slightly different.
      + * @param key the key (part) to be checked
      + * @return a flag if this key is an attribute key
      + */
      + public static boolean isAttributeKey(String key)
      + { + return key != null + && key.startsWith(ATTRIBUTE_START) + && key.endsWith(ATTRIBUTE_END); + }
      +
      + /**
      + * Decorates the given key so that it represents an attribute. Adds
      + * special start and end markers.
      + * @param key the key to be decorated
      + * @return the decorated attribute key
      + */
      + public static String constructAttributeKey(String key)
      + { + StringBuffer buf = new StringBuffer(); + buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END); + return buf.toString(); + }
      +
      + /**
      + * Extracts the name of the attribute from the given attribute key.
      + * This method removes the attribute markers - if any - from the
      + * specified key.
      + * @param key the attribute key
      + * @return the name of the corresponding attribute
      + */
      + public static String attributeName(String key)
      + { + return (isAttributeKey(key)) ? + removeAttributeMarkers(key) : key; + }
      +
      + /**
      + * Helper method for removing attribute markers from a key.
      + * @param key the key
      + * @return the key with removed attribute markers
      + */
      + private static String removeAttributeMarkers(String key)
      + { + return key.substring(ATTRIBUTE_START.length(), + key.length() - ATTRIBUTE_END.length()); + }
      +
      + /**
      + * Helper method that checks if the actual buffer ends with a property
      + * delimiter.
      + * @return a flag if there is a trailing delimiter
      + */
      + private boolean hasDelimiter()
      + { + return keyBuffer.length() > 0 + && keyBuffer.charAt(keyBuffer.length()-1) == PROPERTY_DELIMITER; + }
      +
      + /**
      + * Removes a trailing delimiter if there is any.
      + */
      + private void removeTrailingDelimiter()
      + {
      + while(hasDelimiter())
      + { + keyBuffer.deleteCharAt(keyBuffer.length()-1); + } /* while */
      + }
      +
      + /**
      + * Returns a string representation of this object. This is the
      + * configuration key as a plain string.
      + * @return a string for this object
      + */
      + public String toString()
      + { + return keyBuffer.toString(); + }
      +
      + /**
      + * Returns an iterator for iterating over the single components of
      + * this configuration key.
      + * @return an iterator for this key
      + */
      + public KeyIterator iterator()
      + { + return new KeyIterator(); + }
      +
      + /**
      + * Returns the actual length of this configuration key.
      + * @return the length of this key
      + */
      + public int length()
      + { + return keyBuffer.length(); + }
      +
      + /**
      + * Sets the new length of this configuration key. With this method it is
      + * possible to truncate the key, e.g. to return to a state prior calling
      + * some <code>append()</code> methods. The semantic is the same as
      + * the <code>setLength()</code> method of <code>StringBuffer</code>.
      + * @param len the new length of the key
      + */
      + public void setLength(int len)
      + { + keyBuffer.setLength(len); + }
      +
      + /**
      + * Checks if two <code>ConfigurationKey</code> objects are equal. The
      + * method can be called with strings or other objects, too.
      + * @param c the object to compare
      + * @return a flag if both objects are equal
      + */
      + public boolean equals(Object c)
      + {
      + if(c == null)
      + { + return false; + } /* if */
      +
      + return keyBuffer.toString().equals(c.toString());
      + }
      +
      + /**
      + * Returns the hash code for this object.
      + * @return the hash code
      + */
      + public int hashCode()
      + { + return keyBuffer.hashCode(); + }
      +
      + /**
      + * Returns a configuration key object that is initialized with the part
      + * of the key that is common to this key and the passed in key.
      + * @param other the other key
      + * @return a key object with the common key part
      + */
      + public ConfigurationKey commonKey(ConfigurationKey other)
      + {
      + if(other == null)
      + { + throw new IllegalArgumentException("Other key must no be null!"); + } /* if */
      +
      + ConfigurationKey result = new ConfigurationKey();
      + KeyIterator it1 = iterator();
      + KeyIterator it2 = other.iterator();
      +
      + while(it1.hasNext() && it2.hasNext()
      + && partsEqual(it1, it2))
      + {
      + if(it1.isAttribute())
      + { + result.appendAttribute(it1.currentKey()); + } /* if */
      + else
      + {
      + result.append(it1.currentKey());
      + if(it1.hasIndex)
      + { + result.appendIndex(it1.getIndex()); + } /* if */
      + } /* else */
      + } /* while */
      +
      + return result;
      + }
      +
      + /**
      + * Returns the "difference key" to a given key. This value
      + * is the part of the passed in key that differs from this key. There is
      + * the following relation:
      + * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
      + * for an arbitrary configuration key <code>key</code>.
      + * @param other the key for which the difference is to be calculated
      + * @return the difference key
      + */
      + public ConfigurationKey differenceKey(ConfigurationKey other)
      + {
      + ConfigurationKey common = commonKey(other);
      + ConfigurationKey result = new ConfigurationKey();
      +
      + if(common.length() < other.length())
      + {
      + String k = other.toString().substring(common.length());
      + // skip trailing delimiters
      + int i = 0;
      + while(i < k.length() && k.charAt == PROPERTY_DELIMITER)
      + { + i++; + } /* while */
      +
      + if(i < k.length())
      + { + result.append(k.substring(i)); + } /* if */
      + } /* if */
      +
      + return result;
      + }
      +
      + /**
      + * Helper method for comparing two key parts.
      + * @param it1 the iterator with the first part
      + * @param it2 the iterator with the second part
      + * @return a flag if both parts are equal
      + */
      + private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
      + { + return it1.nextKey().equals(it2.nextKey()) + && it1.getIndex() == it2.getIndex() + && it1.isAttribute() == it2.isAttribute(); + }
      +
      + /**
      + * A specialized iterator class for tokenizing a configuration key.
      + * This class implements the normal iterator interface. In addition it
      + * provides some specific methods for configuration keys.
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + */
      + public class KeyIterator implements Iterator, Cloneable
      + {
      + /** Stores the current key name.*/
      + private String current;
      +
      + /** Stores the start index of the actual token.*/
      + private int startIndex;
      +
      + /** Stores the end index of the actual token.*/
      + private int endIndex;
      +
      + /** Stores the index of the actual property if there is one.*/
      + private int indexValue;
      +
      + /** Stores a flag if the actual property has an index.*/
      + private boolean hasIndex;
      +
      + /** Stores a flag if the actual property is an attribute.*/
      + private boolean attribute;
      +
      + /**
      + * Helper method for determining the next indices.
      + */
      + private void findNextIndices()
      + {
      + startIndex = endIndex;
      + // skip empty names
      + while(startIndex < keyBuffer.length()
      + && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
      + { + startIndex++; + } /* while */
      +
      + // Key ends with a delimiter?
      + if(startIndex >= keyBuffer.length())
      + { + endIndex = keyBuffer.length(); + startIndex = endIndex - 1; + } /* if */
      +
      + else
      + {
      + endIndex = keyBuffer.indexOf(
      + String.valueOf(PROPERTY_DELIMITER), startIndex);
      + if(endIndex < 0)
      + {
      + endIndex = keyBuffer.indexOf(ATTRIBUTE_START, startIndex);
      + if(endIndex < 0 || endIndex == startIndex)
      + { + endIndex = keyBuffer.length(); + } /* if */
      + } /* if */
      + }
      + }
      +
      + /**
      + * Returns the next key part of this configuration key. This is a short
      + * form of <code>nextKey(false)</code>.
      + * @return the next key part
      + */
      + public String nextKey()
      + { + return nextKey(false); + }
      +
      + /**
      + * Returns the next key part of this configuration key. The boolean
      + * parameter indicates wheter a decorated key should be returned. This
      + * affects only attribute keys: if the parameter is <b>false</b>, the
      + * attribute markers are stripped from the key; if it is <b>true</b>,
      + * they remain.
      + * @param decorated a flag if the decorated key is to be returned
      + * @return the next key part
      + */
      + public String nextKey(boolean decorated)
      + {
      + if(!hasNext())
      + { + throw new NoSuchElementException("No more key parts!"); + } /* if */
      +
      + hasIndex = false;
      + indexValue = -1;
      + findNextIndices();
      + String key = keyBuffer.substring(startIndex, endIndex).toString();
      +
      + attribute = checkAttribute(key);
      + if(!attribute)
      + {
      + hasIndex = checkIndex(key);
      + if(!hasIndex)
      + { + current = key; + } /* if */
      + } /* if */
      +
      + return currentKey(decorated);
      + }
      +
      + /**
      + * Helper method for checking if the passed key is an attribute.
      + * If this is the case, the internal fields will be set.
      + * @param key the key to be checked
      + * @return a flag if the key is an attribute
      + */
      + private boolean checkAttribute(String key)
      + {
      + if(isAttributeKey(key))
      + { + current = removeAttributeMarkers(key); + return true; + } /* if */
      + else
      + { + return false; + } /* else */
      + }
      +
      + /**
      + * Helper method for checking if the passed key contains an index.
      + * If this is the case, internal fields will be set.
      + * @param key the key to be checked
      + * @return a flag if an index is defined
      + */
      + private boolean checkIndex(String key)
      + {
      + boolean result = false;
      +
      + int idx = key.indexOf(INDEX_START);
      + if(idx > 0)
      + {
      + int endidx = key.indexOf(INDEX_END, idx);
      +
      + if(endidx > idx + 1)
      + { + indexValue = Integer.parseInt(key.substring(idx+1, endidx)); + current = key.substring(0, idx); + result = true; + } /* if */
      + } /* if */
      +
      + return result;
      + }
      +
      + /**
      + * Checks if there is a next element.
      + * @return a flag if there is a next element
      + */
      + public boolean hasNext()
      + { + return endIndex < keyBuffer.length(); + }
      +
      + /**
      + * Returns the next object in the iteration.
      + * @return the next object
      + */
      + public Object next()
      + { + return nextKey(); + }
      +
      + /**
      + * Removes the current object in the iteration. This method is not
      + * supported by this iterator type, so an exception is thrown.
      + */
      + public void remove()
      + { + throw new UnsupportedOperationException("Remove not supported!"); + }
      +
      + /**
      + * Returns the current key of the iteration (without skipping to the
      + * next element). This is the same key the previous <code>next()</code>
      + * call had returned. (Short form of <code>currentKey(false)</code>.
      + * @return the current key
      + */
      + public String currentKey()
      + { + return currentKey(false); + }
      +
      + /**
      + * Returns the current key of the iteration (without skipping to the
      + * next element). The boolean parameter indicates wheter a decorated
      + * key should be returned. This affects only attribute keys: if the
      + * parameter is <b>false</b>, the attribute markers are stripped from
      + * the key; if it is <b>true</b>, they remain.
      + * @param decorated a flag if the decorated key is to be returned
      + * @return the current key
      + */
      + public String currentKey(boolean decorated)
      + { + return (decorated && isAttribute()) ? + constructAttributeKey(current) : current; + }
      +
      + /**
      + * Returns a flag if the current key is an attribute. This method can
      + * be called after <code>next()</code>.
      + * @return a flag if the current key is an attribute
      + */
      + public boolean isAttribute()
      + { + return attribute; + }
      +
      + /**
      + * Returns the index value of the current key. If the current key does
      + * not have an index, return value is -1. This method can be called
      + * after <code>next()</code>.
      + * @return the index value of the current key
      + */
      + public int getIndex()
      + { + return indexValue; + }
      +
      + /**
      + * Returns a flag if the current key has an associated index.
      + * This method can be called after <code>next()</code>.
      + * @return a flag if the current key has an index
      + */
      + public boolean hasIndex()
      + { + return hasIndex; + }
      +
      + /**
      + * Creates a clone of this object.
      + * @return a clone of this object
      + */
      + protected Object clone()
      + {
      + try
      + { + return super.clone(); + } /* try */
      + catch(CloneNotSupportedException cex)
      + { + // should not happen + return null; + } /* catch */
      + }
      +
      + }
      +}
      Index: src/java/org/apache/commons/configuration/ConfigurationXMLReader.java
      ===================================================================
      RCS file: src/java/org/apache/commons/configuration/ConfigurationXMLReader.java
      diff -N src/java/org/apache/commons/configuration/ConfigurationXMLReader.java
      — /dev/null 1 Jan 1970 00:00:00 -0000
      +++ src/java/org/apache/commons/configuration/ConfigurationXMLReader.java 30 Oct
      2003 17:27:15 -0000
      @@ -0,0 +1,377 @@
      +package org.apache.commons.configuration;
      +
      +/* ====================================================================
      + * The Apache Software License, Version 1.1
      + *
      + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
      + * reserved.
      + *
      + * Redistribution and use in source and binary forms, with or without
      + * modification, are permitted provided that the following conditions
      + * are met:
      + *
      + * 1. Redistributions of source code must retain the above copyright
      + * notice, this list of conditions and the following disclaimer.
      + *
      + * 2. Redistributions in binary form must reproduce the above copyright
      + * notice, this list of conditions and the following disclaimer in
      + * the documentation and/or other materials provided with the
      + * distribution.
      + *
      + * 3. The end-user documentation included with the redistribution, if
      + * any, must include the following acknowledgement:
      + * "This product includes software developed by the
      + * Apache Software Foundation (http://www.apache.org/)."
      + * Alternately, this acknowledgement may appear in the software itself,
      + * if and wherever such third-party acknowledgements normally appear.
      + *
      + * 4. The names "The Jakarta Project", "Commons", and "Apache Software
      + * Foundation" must not be used to endorse or promote products derived
      + * from this software without prior written permission. For written
      + * permission, please contact apache@apache.org.
      + *
      + * 5. Products derived from this software may not be called "Apache"
      + * nor may "Apache" appear in their names without prior written
      + * permission of the Apache Software Foundation.
      + *
      + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
      + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
      + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
      + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
      + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
      + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      + * SUCH DAMAGE.
      + * ====================================================================
      + *
      + * This software consists of voluntary contributions made by many
      + * individuals on behalf of the Apache Software Foundation. For more
      + * information on the Apache Software Foundation, please see
      + * <http://www.apache.org/>.
      + */
      +
      + import java.io.IOException;
      +
      +import org.xml.sax.Attributes;
      +import org.xml.sax.ContentHandler;
      +import org.xml.sax.DTDHandler;
      +import org.xml.sax.EntityResolver;
      +import org.xml.sax.ErrorHandler;
      +import org.xml.sax.InputSource;
      +import org.xml.sax.SAXException;
      +import org.xml.sax.XMLReader;
      +import org.xml.sax.helpers.AttributesImpl;
      +
      +/**
      + * <p>A base class for "faked" <code>XMLReader</code> classes
      + * that transform a configuration object in a set of SAX parsing events.</p>
      + * <p>This class provides dummy implementations for most of the methods
      + * defined in the <code>XMLReader</code> interface that are not used for this
      + * special purpose. There will be concrete sub classes that process specific
      + * configuration classes.</p>
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + * @version $Id: $
      + */
      +public abstract class ConfigurationXMLReader implements XMLReader
      +{
      + /** Constant for the namespace URI.*/
      + protected static final String NS_URI = "";
      +
      + /** Constant for the default name of the root element.*/
      + private static final String DEFAULT_ROOT_NAME = "config";
      +
      + /** An empty attributes object.*/
      + private static final Attributes EMPTY_ATTRS = new AttributesImpl();
      +
      + /** Stores the content handler.*/
      + private ContentHandler contentHandler;
      +
      + /** Stores an exception that occurred during parsing.*/
      + private SAXException exception;
      +
      + /** Stores the name for the root element.*/
      + private String rootName;
      +
      + /**
      + * Creates a new instance of <code>ConfigurationXMLReader</code>.
      + */
      + protected ConfigurationXMLReader()
      + { + super(); + setRootName(DEFAULT_ROOT_NAME); + }
      +
      + /**
      + * Parses the acutal configuration object. The passed system ID will be
      + * ignored.
      + * @param systemId the system ID (ignored)
      + * @throws IOException if no configuration was specified
      + * @throws SAXException if an error occurs during parsing
      + */
      + public void parse(String systemId) throws IOException, SAXException
      + { + parseConfiguration(); + }
      +
      + /**
      + * Parses the acutal configuration object. The passed input source will be
      + * ignored.
      + * @param input the input source (ignored)
      + * @throws IOException if no configuration was specified
      + * @throws SAXException if an error occurs during parsing
      + */
      + public void parse(InputSource input) throws IOException, SAXException
      + {+ parseConfiguration();+ }
      +
      + /**
      + * Dummy implementation of the interface method.
      + * @param name the name of the feature
      + * @return always <b>false</b> (no features are supported)
      + */
      + public boolean getFeature(String name)
      + { + return false; + }
      +
      + /**
      + * Dummy implementation of the interface method.
      + * @param name the name of the feature to be set
      + * @param value the value of the feature
      + */
      + public void setFeature(String name, boolean value)
      + { + }
      +
      + /**
      + * Returns the actually set content handler.
      + * @return the content handler
      + */
      + public ContentHandler getContentHandler()
      + { + return contentHandler; + }
      +
      + /**
      + * Sets the content handler. The object specified here will receive SAX
      + * events during parsing.
      + * @param handler the content handler
      + */
      + public void setContentHandler(ContentHandler handler)
      + { + contentHandler = handler; + }
      +
      + /**
      + * Returns the DTD handler. This class does not support DTD handlers,
      + * so this method always returns <b>null</b>.
      + * @return the DTD handler
      + */
      + public DTDHandler getDTDHandler()
      + { + return null; + }
      +
      + /**
      + * Sets the DTD handler. The passed value is ignored.
      + * @param handler the handler to be set
      + */
      + public void setDTDHandler(DTDHandler handler)
      + {+ }
      +
      + /**
      + * Returns the entity resolver. This class does not support an entity
      + * resolver, so this method always returns <b>null</b>.
      + * @return the entity resolver
      + */
      + public EntityResolver getEntityResolver()
      + { + return null; + }
      +
      + /**
      + * Sets the entity resolver. The passed value is ignored.
      + * @param resolver the entity resolver
      + */
      + public void setEntityResolver(EntityResolver resolver)
      + { + }
      +
      + /**
      + * Returns the error handler. This class does not support an error handler,
      + * so this method always returns <b>null</b>.
      + * @return the error handler
      + */
      + public ErrorHandler getErrorHandler()
      + {+ return null;+ }
      +
      + /**
      + * Sets the error handler. The passed value is ignored.
      + * @param handler the error handler
      + */
      + public void setErrorHandler(ErrorHandler handler)
      + { + }
      +
      + /**
      + * Dummy implementation of the interface method. No properties are
      + * supported, so this method always returns <b>null</b>.
      + * @param name the name of the requested property
      + * @return the property value
      + */
      + public Object getProperty(String name)
      + { + return null; + }
      +
      + /**
      + * Dummy implementation of the interface method. No properties are
      + * supported, so a call of this method just has no effect.
      + * @param name the property name
      + * @param value the property value
      + */
      + public void setProperty(String name, Object value)
      + {+ }
      +
      + /**
      + * Returns the name to be used for the root element.
      + * @return the name for the root element
      + */
      + public String getRootName()
      + { + return rootName; + }
      +
      + /**
      + * Sets the name for the root element.
      + * @param string the name for the root element.
      + */
      + public void setRootName(String string)
      + { + rootName = string; + }
      +
      + /**
      + * Fires a SAX element start event.
      + * @param name the name of the actual element
      + * @param attribs the attributes of this element (can be <b>null</b>)
      + */
      + protected void fireElementStart(String name, Attributes attribs)
      + {
      + if(getException() == null)
      + {
      + try
      + { + Attributes at = (attribs == null) ? EMPTY_ATTRS : attribs; + getContentHandler().startElement(NS_URI, name, name, at); + } /* try */
      + catch(SAXException ex)
      + { + exception = ex; + } /* catch */
      + } /* if */
      + }
      +
      + /**
      + * Fires a SAX element end event.
      + * @param name the name of the affected element
      + */
      + protected void fireElementEnd(String name)
      + {
      + if(getException() == null)
      + {
      + try
      + { + getContentHandler().endElement(NS_URI, name, name); + } /* try */
      + catch(SAXException ex)
      + {+ exception = ex;+ } /* catch */
      + } /* if */
      + }
      +
      + /**
      + * Fires a SAX characters event.
      + * @param text the text
      + */
      + protected void fireCharacters(String text)
      + {
      + if(getException() == null)
      + {
      + try
      + { + char[] ch = text.toCharArray(); + getContentHandler().characters(ch, 0, ch.length); + } /* try */
      + catch(SAXException ex)
      + { + exception = ex; + } /* catch */
      + } /* if */
      + }
      +
      + /**
      + * Returns a reference to an exception that occurred during parsing.
      + * @return a SAXExcpetion or <b>null</b> if none occurred
      + */
      + public SAXException getException()
      + { + return exception; + }
      +
      + /**
      + * Parses the configuration object and generates SAX events. This is the
      + * main processing method.
      + * @throws IOException if no configuration has been specified
      + * @throws SAXException if an error occurs during parsing
      + */
      + protected void parseConfiguration() throws IOException, SAXException
      + {
      + if(getParsedConfiguration() == null)
      + { + throw new IOException("No configuration specified!"); + } /* if */
      +
      + if(getContentHandler() != null)
      + {
      + exception = null;
      + getContentHandler().startDocument();
      + processKeys();
      + if(getException() != null)
      + { + throw getException(); + } /* if */
      + getContentHandler().endDocument();
      + } /* if */
      + }
      +
      + /**
      + * Returns a reference to the configuration that is parsed by this object.
      + * @return the parsed configuration
      + */
      + public abstract Configuration getParsedConfiguration();
      +
      + /**
      + * Processes all keys stored in the actual configuration. This method is
      + * called by <code>parseConfiguration()</code> to start the main parsing
      + * process. <code>parseConfiguration()</code> calls the content handler's
      + * <code>startDocument()</code> and <code>endElement()</code> methods
      + * and cares for exception handling. The remaining actions are left to this
      + * method that must be implemented in a concrete sub class.
      + * @throws IOException if an IO error occurs
      + * @throws SAXException if a SAX error occurs
      + */
      + protected abstract void processKeys()
      + throws IOException, SAXException;
      +}
      Index: src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
      ===================================================================
      RCS file: src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
      diff -N src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
      — /dev/null 1 Jan 1970 00:00:00 -0000
      +++ src/java/org/apache/commons/configuration/HierarchicalConfiguration.java 30
      Oct 2003 17:27:26 -0000
      @@ -0,0 +1,1032 @@
      +package org.apache.commons.configuration;
      +
      +/* ====================================================================
      + * The Apache Software License, Version 1.1
      + *
      + * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
      + * reserved.
      + *
      + * Redistribution and use in source and binary forms, with or without
      + * modification, are permitted provided that the following conditions
      + * are met:
      + *
      + * 1. Redistributions of source code must retain the above copyright
      + * notice, this list of conditions and the following disclaimer.
      + *
      + * 2. Redistributions in binary form must reproduce the above copyright
      + * notice, this list of conditions and the following disclaimer in
      + * the documentation and/or other materials provided with the
      + * distribution.
      + *
      + * 3. The end-user documentation included with the redistribution, if
      + * any, must include the following acknowledgement:
      + * "This product includes software developed by the
      + * Apache Software Foundation (http://www.apache.org/)."
      + * Alternately, this acknowledgement may appear in the software itself,
      + * if and wherever such third-party acknowledgements normally appear.
      + *
      + * 4. The names "The Jakarta Project", "Commons", and "Apache Software
      + * Foundation" must not be used to endorse or promote products derived
      + * from this software without prior written permission. For written
      + * permission, please contact apache@apache.org.
      + *
      + * 5. Products derived from this software may not be called "Apache"
      + * nor may "Apache" appear in their names without prior written
      + * permission of the Apache Software Foundation.
      + *
      + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
      + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
      + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
      + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
      + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
      + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      + * SUCH DAMAGE.
      + * ====================================================================
      + *
      + * This software consists of voluntary contributions made by many
      + * individuals on behalf of the Apache Software Foundation. For more
      + * information on the Apache Software Foundation, please see
      + * <http://www.apache.org/>.
      + */
      +
      +import java.io.Serializable;
      +import java.util.ArrayList;
      +import java.util.Collection;
      +import java.util.HashSet;
      +import java.util.Iterator;
      +import java.util.LinkedList;
      +import java.util.List;
      +import java.util.Map;
      +import java.util.Set;
      +import java.util.Stack;
      +
      +import org.apache.commons.collections.SequencedHashMap;
      +
      +/**
      + * <p>A specialized configuration class that extends its base class by the
      + * ability of keeping more structure in the stored properties.</p>
      + * <p>There are some sources of configuration data that cannot be stored
      + * very well in a <code>BaseConfiguration</code> object because then their
      + * structure is lost. This is especially true for XML documents. This class
      + * can deal with such structured configuration sources by storing the
      + * properties in a tree-like organization.</p>
      + * <p>The internal used storage form allows for a more sophisticated access to
      + * single properties. As an example consider the following XML document:</p>
      + * <p><pre>
      + * <database>
      + * <tables>
      + * <table>
      + * <name>users</name>
      + * <fields>
      + * <field>
      + * <name>lid</name>
      + * <type>long</name>
      + * </field>
      + * <field>
      + * <name>usrName</name>
      + * <type>java.lang.String</type>
      + * </field>
      + * ...
      + * </fields>
      + * </table>
      + * <table>
      + * <name>documents</name>
      + * <fields>
      + * <field>
      + * <name>docid</name>
      + * <type>long</type>
      + * </field>
      + * ...
      + * </fields>
      + * </table>
      + * ...
      + * </tables>
      + * </database>
      + * </pre></p>
      + * <p>If this document is parsed and stored in a
      + * <code>HierarchicalConfiguration</code> object (which can be done by one of
      + * the sub classes), there are enhanced possibilities of accessing properties.
      + * The keys for querying information can contain indices that select a certain
      + * element if there are multiple hits.</p>
      + * <p>For instance the key <code>tables.table(0).name</code> can be used to
      + * find out the name of the first table. In opposite
      + * <code>tables.table.name</code> would return a collection with the names of
      + * all available tables. Similarily the key
      + * <code>tables.table(1).fields.field.name</code> returns a collection with the
      + * names of all fields of the second table. If another index is added after the
      + * <code>field</code> element, a single field can be accessed:
      + * <code>tables.table(1).fields.field(0).name</code>.</p>
      + * <p>There is a <code>getMaxIndex()</code> method that returns the maximum
      + * allowed index that can be added to a given property key. This method can be
      + * used to iterate over all values defined for a certain property.</p>
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + * @version $Id: $
      + */
      +public class HierarchicalConfiguration extends AbstractConfiguration
      +{
      + /** Stores the root node of this configuration.*/
      + private Node root = new Node();
      +
      + /**
      + * Creates a new instance of <code>HierarchicalConfiguration</code>.
      + */
      + public HierarchicalConfiguration()
      + {+ super();+ }

      +
      + /**
      + * Creates a new instance of <code>HierarchicalConfiguration</code>
      + * and initializes it with default properties.
      + * @param defaults default properties to be used
      + */
      + public HierarchicalConfiguration(Configuration defaults)
      +

      { + super(defaults); + }

      +
      + /**
      + * Returns the root node of this hierarchical configuration.
      + * @return the root node
      + */
      + public Node getRoot()
      +

      { + return root; + }

      +
      + /**
      + * Sets the root node of this hierarchical configuration.
      + * @param node the root node
      + */
      + public void setRoot(Node node)
      + {
      + if(node == null)
      +

      { + throw new IllegalArgumentException("Root node must not be null!"); + }

      /* if */
      + root = node;
      + }
      +
      + /**
      + * Fetches the specified property. Performs a recursive lookup in the
      + * tree with the configuration properties.
      + * @param key the key to be looked up
      + * @return the found value
      + */
      + protected Object getPropertyDirect(String key)
      + {
      + List nodes = fetchNodeList(key);
      +
      + if(nodes.size() == 0)
      +

      { + return null; + } /* if */
      + else
      + {
      + Container cont = new Container();
      + for(Iterator it = nodes.iterator(); it.hasNext()
      + {
      + Node nd = (Node) it.next();
      + if(nd.getValue() != null)
      + { + cont.add(nd.getValue()); + } /* if */
      + } /* for */
      +
      + if(cont.size() < 1)
      + { + return null; + } /* if */
      + else
      + { + return (cont.size() == 1) ? cont.get(0) : cont; + } /* else */
      + } /* else */
      + }
      +
      + /**
      + * <p>Adds the property with the specified key.</p>
      + * <p>To be able to deal with the structure supported by this configuration
      + * implementation the passed in key is of importance, especially the
      + * indices it might contain. The following example should clearify this:
      + * Suppose the actual configuration contains the following elements:</p>
      + * <p><pre>
      + * tables
      + * +-- table
      + * +-- name = user
      + * +-- fields
      + * +-- field
      + * +-- name = uid
      + * +-- field
      + * +-- name = firstName
      + * ...
      + * +-- table
      + * +-- name = documents
      + * +-- fields
      + * ...
      + * </pre></p>
      + * <p>In this example a database structure is defined, e.g. all fields of
      + * the first table could be accessed using the key
      + * <code>tables.table(0).fields.field.name</code>. If now properties are
      + * to be added, it must be exactly specified at which position in the
      + * hierarchy the new property is to be inserted. So to add a new field name
      + * to a table it is not enough to say just</p>
      + * <p><pre>
      + * config.addProperty("tables.table.fields.field.name", "newField");
      + * </pre></p>
      + * <p>The statement given above contains some ambiguity. For instance
      + * it is not clear, to which table the new field should be added. If this
      + * method finds such an ambiguity, it is resolved by following the last
      + * valid path. Here this would be the last table. The same is true for the
      + * <code>field</code>; because there are multiple fields and no explicit
      + * index is provided, a new <code>name</code> property would be
      + * added to the last field - which is propably not what was desired.</p>
      + * <p>To make things clear explicit indices should be provided whenever
      + * possible. In the example above the exact table could be specified by
      + * providing an index for the <code>table</code> element as in
      + * <code>tables.table(1).fields</code>. By specifying an index it can also
      + * be expressed that at a given position in the configuration tree a new
      + * branch should be added. In the example above we did not want to add
      + * an additional <code>name</code> element to the last field of the table,
      + * but we want a complete new <code>field</code> element. This can be
      + * achieved by specifying an invalid index (like -1) after the element
      + * where a new branch should be created. Given this our example would run:
      + * </p><p><pre>
      + * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
      + * </pre></p>
      + * <p>With this notation it is possible to add new branches everywhere.
      + * We could for instance create a new <code>table</code> element by
      + * specifying</p>
      + * <p><pre>
      + * config.addProperty("tables.table(-1).fields.field.name", "newField2");
      + * </pre></p>
      + * <p>(Note that because after the <code>table</code> element a new
      + * branch is created indices in following elements are not relevant; the
      + * branch is new so there cannot be any ambiguities.)</p>
      + * @param key the key of the new property
      + * @param obj the value of the new property
      + */
      + protected void addPropertyDirect(String key, Object obj)
      + {
      + ConfigurationKey ckey = new ConfigurationKey(key);
      + ConfigurationKey.KeyIterator it = ckey.iterator();
      + if(!it.hasNext())
      + { + throw new IllegalArgumentException("Key must be defined!"); + } /* if */
      +
      + Node parent = createAddPath(it, findNodeToAdd(it, getRoot()));
      + Node child = new Node(it.currentKey(true));
      + child.setValue(obj);
      + parent.addChild(child);
      + }
      +
      + /**
      + * Checks if this configuration is empty. Empty means that there are
      + * no keys with any values, though there can be some (empty) nodes.
      + * @return a flag if this configuration is empty
      + */
      + public boolean isEmpty()
      + { + return !nodeDefined(getRoot()); + }
      +
      + /**
      + * Checks if the specified key is contained in this configuration.
      + * Note that for this configuration the term "contained" means
      + * that the key has an associated value. If there is a node for this key
      + * that has no value but children (either defined or undefined), this
      + * method will still return <b>false</b>.
      + * @param key the key to be chekced
      + * @return a flag if this key is contained in this configuration
      + */
      + public boolean containsKey(String key)
      + { + return getPropertyDirect(key) != null; + }
      +
      + /**
      + * Removes all values of the property with the given name.
      + * @param key the key of the property to be removed
      + */
      + public void clearProperty(String key)
      + {
      + List nodes = fetchNodeList(key);
      +
      + for(Iterator it = nodes.iterator(); it.hasNext()
      + { + removeNode((Node) it.next()); + } /* for */
      + }
      +
      + /**
      + * <p>Returns an iterator with all keys defined in this configuration.</p>
      + * <p>Note that the keys returned by this method will not contain
      + * any indices. This means that some structure will be lost.</p>
      + * @return an iterator with the defined keys in this configuration
      + */
      + public Iterator getKeys()
      + { + DefinedKeysVisitor visitor = new DefinedKeysVisitor(); + getRoot().visit(visitor, new ConfigurationKey()); + return visitor.getKeyList().iterator(); + }
      +
      + /**
      + * Creates a new <code>Configuration</code> object containing all keys
      + * that start with the specified prefix. This implementation will return
      + * a <code>HierarchicalConfiguration</code> object so that the structure
      + * of the keys will be saved.
      + * @param prefix the prefix of the keys for the subset
      + * @return a new configuration object representing the selected subset
      + */
      + public Configuration subset(String prefix)
      + {
      + Collection nodes = fetchNodeList(prefix);
      + if(nodes.isEmpty())
      + {+ return null;+ }

      /* if */
      +
      + HierarchicalConfiguration result = new HierarchicalConfiguration();
      + CloneVisitor visitor = new CloneVisitor();
      +
      + for(Iterator it = nodes.iterator(); it.hasNext()
      + {
      + Node nd = (Node) it.next();
      + nd.visit(visitor, null);
      +
      + Container children = visitor.getClone().getChildren();
      + if(children.size() > 0)
      + {
      + for(int i = 0; i < children.size(); i++)
      +

      { + result.getRoot().addChild((Node) children.get(i)); + }

      /* for */
      + } /* if */
      + else
      +

      { + // In this case we cannot shorten the key because only + // values are found without further child nodes. + result.getRoot().addChild(visitor.getClone()); + }

      /* else */
      + } /* for */
      +
      + return (result.isEmpty()) ? null : result;
      + }
      +
      + /**
      + * Returns the maximum defined index for the given key. This is
      + * useful if there are multiple values for this key. They can then be
      + * addressed separately by specifying indices from 0 to the return value
      + * of this method.
      + * @param key the key to be checked
      + * @return the maximum defined index for this key
      + */
      + public int getMaxIndex(String key)
      +

      { + return fetchNodeList(key).size() - 1; + }

      +
      + /**
      + * Helper method for fetching a list of all nodes that are addressed by
      + * the specified key.
      + * @param key the key
      + * @return a list with all affected nodes (never <b>null</b>)
      + */
      + protected List fetchNodeList(String key)
      +

      { + List nodes = new LinkedList(); + findPropertyNodes(new ConfigurationKey(key).iterator(), + getRoot(), nodes); + return nodes; + }

      +
      + /**
      + * Recursive helper method for fetching a property. This method
      + * processes all facets of a configuration key, traverses the tree of
      + * properties and fetches the the nodes of all matching properties.
      + * @param keyPart the configuration key iterator
      + * @param node the actual node
      + * @param data here the found nodes are stored
      + */
      + protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart,
      + Node node, Collection data)
      + {
      + if(!keyPart.hasNext())
      +

      { + data.add(node); + }

      /* if */
      +
      + else
      + {
      + String key = keyPart.nextKey(true);
      + Container children = node.getChildren(key);
      + if(keyPart.hasIndex())
      + {
      + if(keyPart.getIndex() < children.size()
      + && keyPart.getIndex() >= 0)
      +

      { + findPropertyNodes( + (ConfigurationKey.KeyIterator) keyPart.clone(), + (Node) children.get(keyPart.getIndex()), data); + }

      /* if */
      + } /* if */
      +
      + else
      + {
      + for(Iterator it = children.iterator(); it.hasNext()
      +

      { + findPropertyNodes( + (ConfigurationKey.KeyIterator) keyPart.clone(), + (Node) it.next(), data); + }

      /* for */
      + } /* else */
      + }
      + }
      +
      + /**
      + * Checks if the specified node is defined.
      + * @param node the node to be checked
      + * @return a flag if this node is defined
      + */
      + protected boolean nodeDefined(Node node)
      +

      { + DefinedVisitor visitor = new DefinedVisitor(); + node.visit(visitor, null); + return visitor.isDefined(); + }

      +
      + /**
      + * Removes the specified node from this configuration. This method
      + * ensures that parent nodes that become undefined by this operation
      + * are also removed.
      + * @param node the node to be removed
      + */
      + protected void removeNode(Node node)
      + {
      + Node parent = node.getParent();
      + if(parent != null)
      + {
      + parent.remove(node);
      + if(!nodeDefined(parent))
      +

      { + removeNode(parent); + }

      /* if */
      + } /* if */
      + }
      +
      + /**
      + * Finds the last existing node for an add operation. This method
      + * traverses the configuration tree along the specified key. The last
      + * existing node on this path is returned.
      + * @param keyIt the key iterator
      + * @param node the actual node
      + * @return the last existing node on the given path
      + */
      + protected Node findNodeToAdd(ConfigurationKey.KeyIterator keyIt,
      + Node node)
      + {
      + String keyPart = keyIt.nextKey(true);
      +
      + if(keyIt.hasNext())
      + {
      + Container c = node.getChildren(keyPart);
      + int idx = (keyIt.hasIndex()) ? keyIt.getIndex() : c.size() - 1;
      + if(idx < 0 || idx >= c.size())
      +

      { + return node; + }

      /* if */
      + else
      +

      { + return findNodeToAdd(keyIt, (Node) c.get(idx)); + }

      /* else */
      + } /* if */
      +
      + else
      +

      { + return node; + }

      /* else */
      + }
      +
      + /**
      + * Creates the missing nodes for adding a new property. This method
      + * ensures that there are corresponding nodes for all components of the
      + * specified configuration key.
      + * @param keyIt the key iterator
      + * @param root the base node of the path to be created
      + * @return the last node of the path
      + */
      + protected Node createAddPath(ConfigurationKey.KeyIterator keyIt,
      + Node root)
      + {
      + if(keyIt.hasNext())
      +

      { + Node child = new Node(keyIt.currentKey(true)); + root.addChild(child); + keyIt.next(); + return createAddPath(keyIt, child); + }

      /* if */
      + else
      +

      { + return root; + }

      /* else */
      + }
      +
      + /**
      + * Helper method for adding all elements of a collection to a
      + * container.
      + * @param cont the container
      + * @param items the collection to be added
      + */
      + private static void addContainer(Container cont, Collection items)
      + {
      + for(Iterator it = items.iterator(); it.hasNext()
      +

      { + cont.add(it.next()); + }

      /* for */
      + }
      +
      + /**
      + * A data class for storing (hierarchical) property information. A property
      + * can have a value and an arbitrary number of child properties.
      + *
      + * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
      + */
      + public static class Node implements Serializable, Cloneable
      + {
      + /** Stores a reference to this node's parent.*/
      + private Node parent;
      +
      + /** Stores the name of this node.*/
      + private String name;
      +
      + /** Stores the value of this node.*/
      + private Object value;
      +
      + /** Stores the children of this node.*/
      + private Map children;
      +
      + /**
      + * Creates a new instance of <code>Node</code>.
      + */
      + public Node()
      +

      { + this(null); + }

      +
      + /**
      + * Creates a new instance of <code>Node</code> and sets the name.
      + * @param name the node's name
      + */
      + public Node(String name)
      +

      { + setName(name); + }

      +
      + /**
      + * Returns the name of this node.
      + * @return the node name
      + */
      + public String getName()
      +

      { + return name; + }

      +
      + /**
      + * Returns the value of this node.
      + * @return the node value (may be <b>null</b>)
      + */
      + public Object getValue()
      +

      { + return value; + }

      +
      + /**
      + * Returns the parent of this node.
      + * @return this node's parent (can be <b>null</b>)
      + */
      + public Node getParent()
      +

      { + return parent; + }

      Attachments

        Activity

          People

            Unassigned Unassigned
            oheger Oliver Heger
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: