Author: oheger Date: Sat Mar 8 12:42:32 2008 New Revision: 635081 URL: http://svn.apache.org/viewvc?rev=635081&view=rev Log: Initial implementation of hierarchical configurations that are based on node handlers
Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java - copied, changed from r632835, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/HierarchicalConfiguration.java commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/InMemoryConfiguration.java - copied, changed from r632835, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/HierarchicalConfiguration.java commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/SubConfiguration.java - copied, changed from r632835, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/SubnodeConfiguration.java Copied: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java (from r632835, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/HierarchicalConfiguration.java) URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java?p2=commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java&p1=commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/HierarchicalConfiguration.java&r1=632835&r2=635081&rev=635081&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/HierarchicalConfiguration.java (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java Sat Mar 8 12:42:32 2008 @@ -14,210 +14,93 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.commons.configuration2; -import java.io.Serializable; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Stack; import org.apache.commons.configuration2.event.ConfigurationEvent; import org.apache.commons.configuration2.event.ConfigurationListener; -import org.apache.commons.configuration2.tree.ConfigurationNode; -import org.apache.commons.configuration2.tree.ConfigurationNodeVisitor; -import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter; -import org.apache.commons.configuration2.tree.DefaultConfigurationNode; -import org.apache.commons.configuration2.tree.DefaultExpressionEngine; -import org.apache.commons.configuration2.tree.ExpressionEngine; -import org.apache.commons.configuration2.tree.NodeAddData; +import org.apache.commons.configuration2.expr.DefaultExpressionEngine; +import org.apache.commons.configuration2.expr.ExpressionEngine; +import org.apache.commons.configuration2.expr.NodeAddData; +import org.apache.commons.configuration2.expr.NodeHandler; +import org.apache.commons.configuration2.expr.NodeList; +import org.apache.commons.configuration2.expr.NodeVisitor; +import org.apache.commons.configuration2.expr.NodeVisitorAdapter; /** - * <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. Similarly 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> - * <p>Since the 1.3 release of <em>Commons Configuration</em> hierarchical - * configurations support an <em>expression engine</em>. This expression engine - * is responsible for evaluating the passed in configuration keys and map them - * to the stored properties. The examples above are valid for the default - * expression engine, which is used when a new <code>HierarchicalConfiguration</code> - * instance is created. With the <code>setExpressionEngine()</code> method a - * different expression engine can be set. For instance with - * <code>[EMAIL PROTECTED] org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine}</code> - * there is an expression engine available that supports configuration keys in - * XPATH syntax.</p> - * <p>In addition to the events common for all configuration classes hierarchical - * configurations support some more events that correspond to some specific - * methods and features: - * <dl><dt><em>EVENT_ADD_NODES</em></dt><dd>The <code>addNodes()</code> method - * was called; the event object contains the key, to which the nodes were added, - * and a collection with the new nodes as value.</dd> - * <dt><em>EVENT_CLEAR_TREE</em></dt><dd>The <code>clearTree()</code> method was - * called; the event object stores the key of the removed sub tree.</dd> - * <dt><em>EVENT_SUBNODE_CHANGED</em></dt><dd>A <code>SubnodeConfiguration</code> - * that was created from this configuration has been changed. The value property - * of the event object contains the original event object as it was sent by the - * subnode configuration.</dd></dl></p> - * <p><em>Note:</em>Configuration objects of this type can be read concurrently - * by multiple threads. However if one of these threads modifies the object, - * synchronization has to be performed manually.</p> + * <p>A base class for hierarchical configurations.</p> + * <p>This class implements the major part of the functionality required for + * dealing with hierarchical node structures. It provides fundamental algorithms + * for traversing and manipulating such structures. Access to the node objects + * is controlled by a <code>[EMAIL PROTECTED] NodeHandler}</code> object; therefore this + * base class can operate on arbitrary node types. (By making use of Java generics, + * this can even be achieved in a type-safe manner.)</p> + * <p>Concrete subclasses must initialize this base class with an appropriate + * <code>[EMAIL PROTECTED] NodeHandler}</code> instance. They also have to define a method + * that returns the root node of the maintained node hierarchy.</p> * * @author Oliver Heger * @version $Id$ + * @since 2.0 + * @param <T> the type of the nodes this configuration deals with */ -public class HierarchicalConfiguration extends AbstractConfiguration implements Serializable, Cloneable +public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration implements Cloneable { /** * Constant for the clear tree event. - * @since 1.3 */ public static final int EVENT_CLEAR_TREE = 10; /** - * Constant for the add nodes event. - * @since 1.3 - */ - public static final int EVENT_ADD_NODES = 11; - - /** * Constant for the subnode configuration modified event. - * @since 1.5 */ public static final int EVENT_SUBNODE_CHANGED = 12; - /** - * The serial version UID. - */ - private static final long serialVersionUID = 3373812230395363192L; - /** Stores the default expression engine to be used for new objects.*/ private static ExpressionEngine defaultExpressionEngine; - /** Stores the root configuration node.*/ - private ConfigurationNode rootNode; - /** Stores the expression engine for this instance.*/ - private transient ExpressionEngine expressionEngine; + private ExpressionEngine expressionEngine; + + /** Stores the node handler for accessing the internally used nodes.*/ + private NodeHandler<T> nodeHandler; /** * Creates a new instance of <code>HierarchicalConfiguration</code>. */ - public HierarchicalConfiguration() + protected AbstractHierarchicalConfiguration(NodeHandler<T> handler) { - setRootNode(new DefaultConfigurationNode()); + nodeHandler = handler; } /** - * Creates a new instance of <code>HierarchicalConfiguration</code> and - * copies all data contained in the specified configuration into the new - * one. + * Returns the <code>NodeHandler</code> used by this configuration. * - * @param c the configuration that is to be copied (if <b>null</b>, this - * constructor will behave like the standard constructor) - * @since 1.4 + * @return the node handler */ - public HierarchicalConfiguration(HierarchicalConfiguration c) + public NodeHandler<T> getNodeHandler() { - this(); - if (c != null) - { - CloneVisitor visitor = new CloneVisitor(); - c.getRootNode().visit(visitor); - setRootNode(visitor.getClone()); - } + return nodeHandler; } /** * Returns the root node of this hierarchical configuration. * * @return the root node - * @since 1.3 */ - public ConfigurationNode getRootNode() - { - return rootNode; - } - - /** - * Sets the root node of this hierarchical configuration. - * - * @param rootNode the root node - * @since 1.3 - */ - public void setRootNode(ConfigurationNode rootNode) - { - if (rootNode == null) - { - throw new IllegalArgumentException("Root node must not be null!"); - } - this.rootNode = rootNode; - } + public abstract T getRootNode(); /** * Returns the default expression engine. * * @return the default expression engine - * @since 1.3 */ public static synchronized ExpressionEngine getDefaultExpressionEngine() { @@ -235,7 +118,6 @@ * impact all instances, for which no specific engine is set. * * @param engine the new default expression engine - * @since 1.3 */ public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine) { @@ -252,7 +134,6 @@ * the default expression engine will be returned. * * @return the current expression engine - * @since 1.3 */ public ExpressionEngine getExpressionEngine() { @@ -266,7 +147,6 @@ * * @param expressionEngine the new expression engine; can be <b>null</b>, * then the default expression engine will be used - * @since 1.3 */ public void setExpressionEngine(ExpressionEngine expressionEngine) { @@ -282,7 +162,7 @@ */ public Object getProperty(String key) { - List<ConfigurationNode> nodes = fetchNodeList(key); + NodeList<T> nodes = fetchNodeList(key); if (nodes.size() == 0) { @@ -291,11 +171,12 @@ else { List<Object> list = new ArrayList<Object>(); - for (ConfigurationNode node : nodes) + for (int i = 0; i < nodes.size(); i++) { - if (node.getValue() != null) + Object value = nodes.getValue(i, getNodeHandler()); + if (value != null) { - list.add(node.getValue()); + list.add(value); } } @@ -321,81 +202,9 @@ @Override protected void addPropertyDirect(String key, Object obj) { - NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key); - ConfigurationNode node = processNodeAddData(data); - node.setValue(obj); - } - - /** - * Adds a collection of nodes at the specified position of the configuration - * tree. This method works similar to <code>addProperty()</code>, but - * instead of a single property a whole collection of nodes can be added - - * and thus complete configuration sub trees. E.g. with this method it is - * possible to add parts of another <code>HierarchicalConfiguration</code> - * object to this object. (However be aware that a - * <code>ConfigurationNode</code> object can only belong to a single - * configuration. So if nodes from one configuration are directly added to - * another one using this method, the structure of the source configuration - * will be broken. In this case you should clone the nodes to be added - * before calling <code>addNodes()</code>.) If the passed in key refers to - * an existing and unique node, the new nodes are added to this node. - * Otherwise a new node will be created at the specified position in the - * hierarchy. - * - * @param key the key where the nodes are to be added; can be <b>null </b>, - * then they are added to the root node - * @param nodes a collection with the <code>Node</code> objects to be - * added - */ - public void addNodes(String key, Collection<? extends ConfigurationNode> nodes) - { - if (nodes == null || nodes.isEmpty()) - { - return; - } - - fireEvent(EVENT_ADD_NODES, key, nodes, true); - ConfigurationNode parent; - List<ConfigurationNode> target = fetchNodeList(key); - if (target.size() == 1) - { - // existing unique key - parent = target.get(0); - } - else - { - // otherwise perform an add operation - parent = processNodeAddData(getExpressionEngine().prepareAdd(getRootNode(), key)); - } - - if (parent.isAttribute()) - { - throw new IllegalArgumentException("Cannot add nodes to an attribute node!"); - } - - // a visitor to ensure that the nodes' references are cleared; this is - // necessary if the nodes are moved from another configuration - ConfigurationNodeVisitor clearRefVisitor = new ConfigurationNodeVisitorAdapter() - { - public void visitBeforeChildren(ConfigurationNode node) - { - node.setReference(null); - } - }; - - for (ConfigurationNode child : nodes) - { - if (child.isAttribute()) - { - parent.addAttribute(child); - } - else - { - parent.addChild(child); - } - child.visit(clearRefVisitor); - } - fireEvent(EVENT_ADD_NODES, key, nodes, false); + NodeAddData<T> data = getExpressionEngine().prepareAdd(getRootNode(), + key, getNodeHandler()); + processNodeAddData(data, obj); } /** @@ -410,77 +219,8 @@ } /** - * 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. The nodes selected by the prefix (it is possible - * that multiple nodes are selected) are mapped to the root node of the - * returned configuration, i.e. their children and attributes will become - * children and attributes of the new root node. However a value of the root - * node is only set if exactly one of the selected nodes contain a value (if - * multiple nodes have a value, there is simply no way to decide how these - * values are merged together). Note that the returned - * <code>Configuration</code> object is not connected to its source - * configuration: updates on the source configuration are not reflected in - * the subset and vice versa. - * - * @param prefix the prefix of the keys for the subset - * @return a new configuration object representing the selected subset - */ - @Override - @SuppressWarnings("serial") - public Configuration subset(String prefix) - { - Collection<ConfigurationNode> nodes = fetchNodeList(prefix); - if (nodes.isEmpty()) - { - return new HierarchicalConfiguration(); - } - - final HierarchicalConfiguration parent = this; - HierarchicalConfiguration result = new HierarchicalConfiguration() - { - // Override interpolate to always interpolate on the parent - protected Object interpolate(Object value) - { - return parent.interpolate(value); - } - }; - CloneVisitor visitor = new CloneVisitor(); - - // Initialize the new root node - Object value = null; - int valueCount = 0; - for (ConfigurationNode nd : nodes) - { - if (nd.getValue() != null) - { - value = nd.getValue(); - valueCount++; - } - nd.visit(visitor); - - for (ConfigurationNode child : visitor.getClone().getChildren()) - { - result.getRootNode().addChild(child); - } - for (ConfigurationNode attr : visitor.getClone().getAttributes()) - { - result.getRootNode().addAttribute(attr); - } - } - - // Determine the value of the new root - if (valueCount == 1) - { - result.getRootNode().setValue(value); - } - return (result.isEmpty()) ? new HierarchicalConfiguration() : result; - } - - /** * <p> - * Returns a hierarchical subnode configuration object that wraps the + * Returns a hierarchical sub configuration object that wraps the * configuration node specified by the given key. This method provides an * easy means of accessing sub trees of a hierarchical configuration. In the * returned configuration the sub tree can directly be accessed, it becomes @@ -494,58 +234,56 @@ * <code>subset()</code> supports arbitrary subsets of configuration nodes * while <code>configurationAt()</code> only returns a single sub tree. * Please refer to the documentation of the - * <code>SubnodeConfiguration</code> class to obtain further information - * about subnode configurations and when they should be used. + * <code>[EMAIL PROTECTED] SubConfiguration}</code> class to obtain further information + * about sub configurations and when they should be used. * </p> * <p> * With the <code>supportUpdate</code> flag the behavior of the returned - * <code>SubnodeConfiguration</code> regarding updates of its parent - * configuration can be determined. A subnode configuration operates on the + * <code>SubConfiguration</code> regarding updates of its parent + * configuration can be determined. A sub configuration operates on the * same nodes as its parent, so changes at one configuration are normally * directly visible for the other configuration. There are however changes - * of the parent configuration, which are not recognized by the subnode + * of the parent configuration, which are not recognized by the sub * configuration per default. An example for this is a reload operation (for * file-based configurations): Here the complete node set of the parent - * configuration is replaced, but the subnode configuration still references - * the old nodes. If such changes should be detected by the subnode + * configuration is replaced, but the sub configuration still references + * the old nodes. If such changes should be detected by the sub * configuration, the <code>supportUpdates</code> flag must be set to - * <b>true</b>. This causes the subnode configuration to reevaluate the key + * <b>true</b>. This causes the sub configuration to reevaluate the key * used for its creation each time it is accessed. This guarantees that the - * subnode configuration always stays in sync with its key, even if the + * sub configuration always stays in sync with its key, even if the * parent configuration's data significantly changes. If such a change * makes the key invalid - because it now no longer points to exactly one - * node -, the subnode configuration is not reconstructed, but keeps its + * node -, the sub configuration is not reconstructed, but keeps its * old data. It is then quasi detached from its parent. * </p> * * @param key the key that selects the sub tree - * @param supportUpdates a flag whether the returned subnode configuration + * @param supportUpdates a flag whether the returned sub configuration * should be able to handle updates of its parent * @return a hierarchical configuration that contains this sub tree - * @see SubnodeConfiguration - * @since 1.5 + * @see SubConfiguration */ - public SubnodeConfiguration configurationAt(String key, boolean supportUpdates) + public AbstractHierarchicalConfiguration<T> configurationAt(String key, boolean supportUpdates) { - List<ConfigurationNode> nodes = fetchNodeList(key); - if (nodes.size() != 1) + NodeList<T> nodes = fetchNodeList(key); + if (nodes.size() != 1 || !nodes.isNode(0)) { throw new IllegalArgumentException("Passed in key must select exactly one node: " + key); } - return supportUpdates ? createSubnodeConfiguration(nodes.get(0), key) : createSubnodeConfiguration(nodes.get(0)); + return supportUpdates ? createSubnodeConfiguration(nodes.getNode(0), key) : createSubnodeConfiguration(nodes.getNode(0)); } /** - * Returns a hierarchical subnode configuration for the node specified by + * Returns a hierarchical sub configuration for the node specified by * the given key. This is a short form for <code>configurationAt(key, * <b>false</b>)</code>. - * + * * @param key the key that selects the sub tree * @return a hierarchical configuration that contains this sub tree - * @see SubnodeConfiguration - * @since 1.3 + * @see SubConfiguration */ - public SubnodeConfiguration configurationAt(String key) + public AbstractHierarchicalConfiguration<T> configurationAt(String key) { return configurationAt(key, false); } @@ -575,48 +313,46 @@ * @param key the key for selecting the desired nodes * @return a list with hierarchical configuration objects; each * configuration represents one of the nodes selected by the passed in key - * @since 1.3 */ - public List<HierarchicalConfiguration> configurationsAt(String key) + public List<AbstractHierarchicalConfiguration<T>> configurationsAt(String key) { - List<ConfigurationNode> nodes = fetchNodeList(key); - List<HierarchicalConfiguration> configs = new ArrayList<HierarchicalConfiguration>(nodes.size()); - for (ConfigurationNode node : nodes) + NodeList<T> nodes = fetchNodeList(key); + List<AbstractHierarchicalConfiguration<T>> configs = new ArrayList<AbstractHierarchicalConfiguration<T>>( + nodes.size()); + for (int index = 0; index < nodes.size(); index++) { - configs.add(createSubnodeConfiguration(node)); + configs.add(createSubnodeConfiguration(nodes.getNode(index))); } return configs; } /** - * Creates a subnode configuration for the specified node. This method is + * Creates a sub configuration for the specified node. This method is * called by <code>configurationAt()</code> and * <code>configurationsAt()</code>. * - * @param node the node, for which a subnode configuration is to be created + * @param node the node, for which a sub configuration is to be created * @return the configuration for the given node - * @since 1.3 */ - protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node) + protected SubConfiguration<T> createSubnodeConfiguration(T node) { - SubnodeConfiguration result = new SubnodeConfiguration(this, node); + SubConfiguration<T> result = new SubConfiguration<T>(this, node); registerSubnodeConfiguration(result); return result; } /** - * Creates a new subnode configuration for the specified node and sets its - * construction key. A subnode configuration created this way will be aware + * Creates a new sub configuration for the specified node and sets its + * construction key. A sub configuration created this way will be aware * of structural changes of its parent. * - * @param node the node, for which a subnode configuration is to be created + * @param node the node, for which a sub configuration is to be created * @param subnodeKey the key used to construct the configuration * @return the configuration for the given node - * @since 1.5 */ - protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node, String subnodeKey) + protected SubConfiguration<T> createSubnodeConfiguration(T node, String subnodeKey) { - SubnodeConfiguration result = createSubnodeConfiguration(node); + SubConfiguration<T> result = createSubnodeConfiguration(node); result.setSubnodeKey(subnodeKey); return result; } @@ -628,7 +364,6 @@ * and notifies the registered listeners. * * @param event the event describing the change - * @since 1.5 */ protected void subnodeConfigurationChanged(ConfigurationEvent event) { @@ -636,14 +371,13 @@ } /** - * Registers this instance at the given subnode configuration. This + * Registers this instance at the given sub configuration. This * implementation will register a change listener, so that modifications of - * the subnode configuration can be tracked. + * the sub configuration can be tracked. * - * @param config the subnode configuration - * @since 1.5 + * @param config the sub configuration */ - void registerSubnodeConfiguration(SubnodeConfiguration config) + void registerSubnodeConfiguration(SubConfiguration<T> config) { config.addConfigurationListener(new ConfigurationListener() { @@ -681,7 +415,7 @@ fireEvent(EVENT_SET_PROPERTY, key, value, true); // Update the existing nodes for this property - Iterator<ConfigurationNode> itNodes = fetchNodeList(key).iterator(); + NodeList<T> nodes = fetchNodeList(key); Iterator<?> itValues; if (!isDelimiterParsingDisabled()) { @@ -692,9 +426,11 @@ itValues = Collections.singleton(value).iterator(); } - while (itNodes.hasNext() && itValues.hasNext()) + int index = 0; + while (index < nodes.size() && itValues.hasNext()) { - itNodes.next().setValue(itValues.next()); + nodes.setValue(index, itValues.next(), getNodeHandler()); + index++; } // Add additional nodes if necessary @@ -704,9 +440,9 @@ } // Remove remaining nodes - while (itNodes.hasNext()) + while (index < nodes.size()) { - clearNode(itNodes.next()); + removeListElement(nodes, index++, true); } fireEvent(EVENT_SET_PROPERTY, key, value, false); @@ -722,14 +458,7 @@ */ public void clearTree(String key) { - fireEvent(EVENT_CLEAR_TREE, key, null, true); - List<ConfigurationNode> nodes = fetchNodeList(key); - - for (ConfigurationNode node : nodes) - { - removeNode(node); - } - fireEvent(EVENT_CLEAR_TREE, key, nodes, false); + removeNodeList(key, EVENT_CLEAR_TREE, false); } /** @@ -742,15 +471,57 @@ @Override public void clearProperty(String key) { - fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); - List<ConfigurationNode> nodes = fetchNodeList(key); + removeNodeList(key, EVENT_CLEAR_PROPERTY, true); + } - for (ConfigurationNode node : nodes) + /** + * Removes the list element with the specified index from this + * configuration. This method calls the appropriate remove method depending + * on the type of the list element. + * + * @param nodes the node list + * @param index the index + * @param clear a flag whether the element should only be cleared or completely removed + */ + private void removeListElement(NodeList<T> nodes, int index, boolean clear) + { + if (nodes.isNode(index)) + { + if (clear) + { + clearNode(nodes.getNode(index)); + } + else + { + removeNode(nodes.getNode(index)); + } + } + else { - clearNode(node); + T parent = nodes.getAttributeParent(index); + getNodeHandler().removeAttribute(parent, + nodes.getName(index, getNodeHandler())); + removeNodeIfUndefined(parent); } + } + + /** + * Removes or clears all nodes or attributes matched by the given key. + * @param key the key + * @param event the event to fire + * @param clear determines whether the elements are cleared or removed + */ + private void removeNodeList(String key, int event, boolean clear) + { + fireEvent(event, key, null, true); + NodeList<T> nodes = fetchNodeList(key); - fireEvent(EVENT_CLEAR_PROPERTY, key, null, false); + for (int index = 0; index < nodes.size(); index++) + { + removeListElement(nodes, index, clear); + } + + fireEvent(EVENT_CLEAR_TREE, key, nodes, false); } /** @@ -763,7 +534,7 @@ public Iterator<String> getKeys() { DefinedKeysVisitor visitor = new DefinedKeysVisitor(); - getRootNode().visit(visitor); + visit(getRootNode(), visitor); return visitor.getKeyList().iterator(); } @@ -780,17 +551,17 @@ public Iterator<String> getKeys(String prefix) { DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); - List<ConfigurationNode> nodes = fetchNodeList(prefix); + NodeList<T> nodes = fetchNodeList(prefix); - for (ConfigurationNode node : nodes) + for (int i = 0; i < nodes.size(); i++) { - for (ConfigurationNode child : node.getChildren()) - { - child.visit(visitor); - } - for (ConfigurationNode attr : node.getAttributes()) + if(nodes.isNode(i)) { - attr.visit(visitor); + for(T child : getNodeHandler().getChildren(nodes.getNode(i))) + { + visit(child, visitor); + } + visitor.appendAttributes(nodes.getNode(i), prefix, getNodeHandler()); } } @@ -812,68 +583,39 @@ } /** - * Creates a copy of this object. This new configuration object will contain - * copies of all nodes in the same structure. Registered event listeners - * won't be cloned; so they are not registered at the returned copy. + * Helper method for fetching a list of all nodes that are addressed by the + * specified key. * - * @return the copy - * @since 1.2 + * @param key the key + * @return a list with all affected nodes (never <b>null </b>) */ - @Override - public Object clone() + protected NodeList<T> fetchNodeList(String key) { - try - { - HierarchicalConfiguration copy = (HierarchicalConfiguration) super.clone(); - - // clone the nodes, too - CloneVisitor v = new CloneVisitor(); - getRootNode().visit(v); - copy.setRootNode(v.getClone()); - - return copy; - } - catch (CloneNotSupportedException cex) - { - // should not happen - throw new ConfigurationRuntimeException(cex); - } + return getExpressionEngine().query(getRootNode(), key, getNodeHandler()); } /** - * Returns a configuration with the same content as this configuration, but - * with all variables replaced by their actual values. This implementation - * is specific for hierarchical configurations. It clones the current - * configuration and runs a specialized visitor on the clone, which performs - * interpolation on the single configuration nodes. + * Visits the specified configuration node. This method implements the + * traversal of the node hierarchy starting with the specified node. * - * @return a configuration with all variables interpolated - * @since 1.5 + * @param node the node to be visited + * @param visitor the visitor */ - @Override - public Configuration interpolatedConfiguration() + protected void visit(T node, NodeVisitor<T> visitor) { - HierarchicalConfiguration c = (HierarchicalConfiguration) clone(); - c.getRootNode().visit(new ConfigurationNodeVisitorAdapter() + if (!visitor.terminate()) { - public void visitAfterChildren(ConfigurationNode node) + visitor.visitBeforeChildren(node, getNodeHandler()); + + for (Iterator<T> it = getNodeHandler().getChildren(node).iterator(); it + .hasNext() + && !visitor.terminate();) { - node.setValue(interpolate(node.getValue())); + visit(it.next(), visitor); } - }); - return c; - } - /** - * 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<ConfigurationNode> fetchNodeList(String key) - { - return getExpressionEngine().query(getRootNode(), key); + visitor.visitAfterChildren(node, getNodeHandler()); + } } /** @@ -882,10 +624,10 @@ * @param node the node to be checked * @return a flag if this node is defined */ - protected boolean nodeDefined(ConfigurationNode node) + protected boolean nodeDefined(T node) { - DefinedVisitor visitor = new DefinedVisitor(); - node.visit(visitor); + DefinedVisitor<T> visitor = new DefinedVisitor<T>(); + visit(node, visitor); return visitor.isDefined(); } @@ -896,16 +638,13 @@ * * @param node the node to be removed */ - protected void removeNode(ConfigurationNode node) + protected void removeNode(T node) { - ConfigurationNode parent = node.getParentNode(); + T parent = getNodeHandler().getParent(node); if (parent != null) { - parent.removeChild(node); - if (!nodeDefined(parent)) - { - removeNode(parent); - } + getNodeHandler().removeChild(parent, node); + removeNodeIfUndefined(parent); } } @@ -915,9 +654,19 @@ * * @param node the node to be cleared */ - protected void clearNode(ConfigurationNode node) + protected void clearNode(T node) + { + getNodeHandler().setValue(node, null); + removeNodeIfUndefined(node); + } + + /** + * Removes the specified node if it is undefined. + * + * @param node the node + */ + private void removeNodeIfUndefined(T node) { - node.setValue(null); if (!nodeDefined(node)) { removeNode(node); @@ -925,81 +674,59 @@ } /** - * Creates a new <code>Node</code> object with the specified name. This - * method can be overloaded in derived classes if a specific node type is - * needed. This base implementation always returns a new object of the - * <code>Node</code> class. + * Creates a new node object with the specified name. This base implementation + * delegates to the <code>NodeHandler</code> for creating a new node. * + * @param parent the parent of the new node * @param name the name of the new node * @return the new node */ - protected ConfigurationNode createNode(String name) + protected T createNode(T parent, String name) { - return new DefaultConfigurationNode(name); + return getNodeHandler().addChild(parent, name); } /** - * Helper method for processing a node add data object obtained from the - * expression engine. This method will create all new nodes. + * Helper method for processing a <code>NodeAddData</code> object obtained from the + * expression engine. This method will create all new nodes and set the value + * of the last node, which represents the newly added property. * * @param data the data object - * @return the new node - * @since 1.3 + * @param value the value of the new property + * @return the new node (<b>null</b> if an attribute was added) */ - private ConfigurationNode processNodeAddData(NodeAddData data) + protected T processNodeAddData(NodeAddData<T> data, Object value) { - ConfigurationNode node = data.getParent(); + T node = data.getParent(); // Create missing nodes on the path for (String nodeName : data.getPathNodes()) { - ConfigurationNode child = createNode(nodeName); - node.addChild(child); + T child = createNode(node, nodeName); node = child; } - // Add new target node - ConfigurationNode child = createNode(data.getNewNodeName()); + // Add the new property if (data.isAttribute()) { - node.addAttribute(child); + getNodeHandler().setAttributeValue(node, data.getNewNodeName(), + value); + return null; } else { - node.addChild(child); + T child = createNode(node, data.getNewNodeName()); + getNodeHandler().setValue(child, value); + return child; } - return child; - } - - /** - * Clears all reference fields in a node structure. A configuration node can - * store a so-called "reference". The meaning of this data is - * determined by a concrete sub class. Typically such references are - * specific for a configuration instance. If this instance is cloned or - * copied, they must be cleared. This can be done using this method. - * - * @param node the root node of the node hierarchy, in which the references - * are to be cleared - * @since 1.4 - */ - protected static void clearReferences(ConfigurationNode node) - { - node.visit(new ConfigurationNodeVisitorAdapter() - { - public void visitBeforeChildren(ConfigurationNode node) - { - node.setReference(null); - } - }); } /** * A specialized visitor that checks if a node is defined. * "Defined" in this terms means that the node or at least one of * its sub nodes is associated with a value. - * */ - static class DefinedVisitor extends ConfigurationNodeVisitorAdapter + private static class DefinedVisitor<T> extends NodeVisitorAdapter<T> { /** Stores the defined flag. */ private boolean defined; @@ -1022,9 +749,9 @@ * @param node the actual node */ @Override - public void visitBeforeChildren(ConfigurationNode node) + public void visitBeforeChildren(T node, NodeHandler<T> handler) { - defined = node.getValue() != null; + defined = handler.isDefined(node); } /** @@ -1042,7 +769,7 @@ * A specialized visitor that fills a list with keys that are defined in a * node hierarchy. */ - class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter + private class DefinedKeysVisitor extends NodeVisitorAdapter<T> { /** Stores the list to be filled. */ private Set<String> keyList; @@ -1086,9 +813,10 @@ * node's key from the stack. * * @param node the node + * @param handler the node handler */ @Override - public void visitAfterChildren(ConfigurationNode node) + public void visitAfterChildren(T node, NodeHandler<T> handler) { parentKeys.pop(); } @@ -1098,191 +826,38 @@ * to the internal list. * * @param node the node to be visited + * @param handler the node handler */ @Override - public void visitBeforeChildren(ConfigurationNode node) + public void visitBeforeChildren(T node, NodeHandler<T> handler) { String parentKey = parentKeys.isEmpty() ? null : (String) parentKeys.peek(); - String key = getExpressionEngine().nodeKey(node, parentKey); + String key = getExpressionEngine().nodeKey(node, parentKey, handler); parentKeys.push(key); - if (node.getValue() != null) + if (handler.getValue(node) != null) { keyList.add(key); } - } - } - /** - * A specialized visitor that is able to create a deep copy of a node - * hierarchy. - */ - static class CloneVisitor extends ConfigurationNodeVisitorAdapter - { - /** A stack with the actual object to be copied. */ - private Stack<ConfigurationNode> copyStack; - - /** Stores the result of the clone process. */ - private ConfigurationNode result; - - /** - * Creates a new instance of <code>CloneVisitor</code>. - */ - public CloneVisitor() - { - copyStack = new Stack<ConfigurationNode>(); - } - - /** - * Visits the specified node after its children have been processed. - * - * @param node the node - */ - @Override - public void visitAfterChildren(ConfigurationNode node) - { - ConfigurationNode copy = copyStack.pop(); - if (copyStack.isEmpty()) - { - result = copy; - } + appendAttributes(node, key, handler); } /** - * Visits and copies the specified node. + * Adds the keys of the attributes of the given node to the internal key + * list. * - * @param node the node + * @param node the parent node + * @param parentKey the key of the parent node + * @param handler the node handler */ - @Override - public void visitBeforeChildren(ConfigurationNode node) + public void appendAttributes(T node, String parentKey, + NodeHandler<T> handler) { - ConfigurationNode copy = (ConfigurationNode) node.clone(); - copy.setParentNode(null); - - if (!copyStack.isEmpty()) + for (String attr : handler.getAttributes(node)) { - if (node.isAttribute()) - { - copyStack.peek().addAttribute(copy); - } - else - { - copyStack.peek().addChild(copy); - } + keyList.add(getExpressionEngine().attributeKey(node, parentKey, + attr, handler)); } - - copyStack.push(copy); } - - /** - * Returns the result of the clone process. This is the root node of the - * cloned node hierarchy. - * - * @return the cloned root node - */ - public ConfigurationNode getClone() - { - return result; - } - } - - /** - * A specialized visitor base class that can be used for storing the tree of - * configuration nodes. The basic idea is that each node can be associated - * with a reference object. This reference object has a concrete meaning in - * a derived class, e.g. an entry in a JNDI context or an XML element. When - * the configuration tree is set up, the <code>load()</code> method is - * responsible for setting the reference objects. When the configuration - * tree is later modified, new nodes do not have a defined reference object. - * This visitor class processes all nodes and finds the ones without a - * defined reference object. For those nodes the <code>insert()</code> - * method is called, which must be defined in concrete sub classes. This - * method can perform all steps to integrate the new node into the original - * structure. - */ - protected abstract static class BuilderVisitor extends ConfigurationNodeVisitorAdapter - { - /** - * Visits the specified node before its children have been traversed. - * - * @param node the node to visit - */ - @Override - public void visitBeforeChildren(ConfigurationNode node) - { - Collection<ConfigurationNode> subNodes = new LinkedList<ConfigurationNode>(node.getChildren()); - subNodes.addAll(node.getAttributes()); - Iterator<ConfigurationNode> children = subNodes.iterator(); - ConfigurationNode sibling1 = null; - ConfigurationNode nd = null; - - while (children.hasNext()) - { - // find the next new node - do - { - sibling1 = nd; - nd = children.next(); - } while (nd.getReference() != null && children.hasNext()); - - if (nd.getReference() == null) - { - // find all following new nodes - List<ConfigurationNode> newNodes = new LinkedList<ConfigurationNode>(); - newNodes.add(nd); - while (children.hasNext()) - { - nd = children.next(); - if (nd.getReference() == null) - { - newNodes.add(nd); - } - else - { - break; - } - } - - // Insert all new nodes - ConfigurationNode sibling2 = (nd.getReference() == null) ? null : nd; - for (ConfigurationNode insertNode : newNodes) - { - if (insertNode.getReference() == null) - { - Object ref = insert(insertNode, node, sibling1, sibling2); - if (ref != null) - { - insertNode.setReference(ref); - } - sibling1 = insertNode; - } - } - } - } - } - - /** - * Inserts a new node into the structure constructed by this builder. - * This method is called for each node that has been added to the - * configuration tree after the configuration has been loaded from its - * source. These new nodes have to be inserted into the original - * structure. The passed in nodes define the position of the node to be - * inserted: its parent and the siblings between to insert. The return - * value is interpreted as the new reference of the affected - * <code>Node</code> object; if it is not <b>null </b>, it is passed - * to the node's <code>setReference()</code> method. - * - * @param newNode the node to be inserted - * @param parent the parent node - * @param sibling1 the sibling after which the node is to be inserted; - * can be <b>null </b> if the new node is going to be the first child - * node - * @param sibling2 the sibling before which the node is to be inserted; - * can be <b>null </b> if the new node is going to be the last child - * node - * @return the reference object for the node to be inserted - */ - protected abstract Object insert(ConfigurationNode newNode, - ConfigurationNode parent, ConfigurationNode sibling1, - ConfigurationNode sibling2); } }