0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.commons.configuration;
0019:
0020: import java.io.Serializable;
0021: import java.util.ArrayList;
0022: import java.util.Collection;
0023: import java.util.Iterator;
0024: import java.util.LinkedList;
0025: import java.util.List;
0026: import java.util.Set;
0027: import java.util.Stack;
0028:
0029: import org.apache.commons.collections.set.ListOrderedSet;
0030: import org.apache.commons.collections.iterators.SingletonIterator;
0031: import org.apache.commons.configuration.tree.ConfigurationNode;
0032: import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter;
0033: import org.apache.commons.configuration.tree.DefaultConfigurationNode;
0034: import org.apache.commons.configuration.tree.DefaultExpressionEngine;
0035: import org.apache.commons.configuration.tree.ExpressionEngine;
0036: import org.apache.commons.configuration.tree.NodeAddData;
0037: import org.apache.commons.lang.StringUtils;
0038:
0039: /**
0040: * <p>A specialized configuration class that extends its base class by the
0041: * ability of keeping more structure in the stored properties.</p><p>There
0042: * are some sources of configuration data that cannot be stored very well in a
0043: * <code>BaseConfiguration</code> object because then their structure is lost.
0044: * This is especially true for XML documents. This class can deal with such
0045: * structured configuration sources by storing the properties in a tree-like
0046: * organization.</p><p>The internal used storage form allows for a more
0047: * sophisticated access to single properties. As an example consider the
0048: * following XML document:</p><p>
0049: *
0050: * <pre>
0051: * <database>
0052: * <tables>
0053: * <table>
0054: * <name>users</name>
0055: * <fields>
0056: * <field>
0057: * <name>lid</name>
0058: * <type>long</name>
0059: * </field>
0060: * <field>
0061: * <name>usrName</name>
0062: * <type>java.lang.String</type>
0063: * </field>
0064: * ...
0065: * </fields>
0066: * </table>
0067: * <table>
0068: * <name>documents</name>
0069: * <fields>
0070: * <field>
0071: * <name>docid</name>
0072: * <type>long</type>
0073: * </field>
0074: * ...
0075: * </fields>
0076: * </table>
0077: * ...
0078: * </tables>
0079: * </database>
0080: * </pre>
0081: *
0082: * </p><p>If this document is parsed and stored in a
0083: * <code>HierarchicalConfiguration</code> object (which can be done by one of
0084: * the sub classes), there are enhanced possibilities of accessing properties.
0085: * The keys for querying information can contain indices that select a certain
0086: * element if there are multiple hits.</p><p>For instance the key
0087: * <code>tables.table(0).name</code> can be used to find out the name of the
0088: * first table. In opposite <code>tables.table.name</code> would return a
0089: * collection with the names of all available tables. Similarily the key
0090: * <code>tables.table(1).fields.field.name</code> returns a collection with
0091: * the names of all fields of the second table. If another index is added after
0092: * the <code>field</code> element, a single field can be accessed:
0093: * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a
0094: * <code>getMaxIndex()</code> method that returns the maximum allowed index
0095: * that can be added to a given property key. This method can be used to iterate
0096: * over all values defined for a certain property.</p>
0097: *
0098: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
0099: * @version $Id: HierarchicalConfiguration.java,v 1.14 2004/12/02 22:05:52
0100: * ebourg Exp $
0101: */
0102: public class HierarchicalConfiguration extends AbstractConfiguration
0103: implements Serializable, Cloneable {
0104: /** Constant for the clear tree event.*/
0105: public static final int EVENT_CLEAR_TREE = 10;
0106:
0107: /** Constant for the add nodes event.*/
0108: public static final int EVENT_ADD_NODES = 11;
0109:
0110: /**
0111: * The serial version UID.
0112: */
0113: private static final long serialVersionUID = 3373812230395363192L;
0114:
0115: /** Stores the default expression engine to be used for new objects.*/
0116: private static ExpressionEngine defaultExpressionEngine = new DefaultExpressionEngine();
0117:
0118: /** Stores the root node of this configuration. This field is required for
0119: * backwards compatibility only.
0120: */
0121: private Node root;
0122:
0123: /** Stores the root configuration node.*/
0124: private ConfigurationNode rootNode;
0125:
0126: /** Stores the expression engine for this instance.*/
0127: private ExpressionEngine expressionEngine;
0128:
0129: /**
0130: * Creates a new instance of <code>HierarchicalConfiguration</code>.
0131: */
0132: public HierarchicalConfiguration() {
0133: setRootNode(new Node());
0134: }
0135:
0136: /**
0137: * Creates a new instance of <code>HierarchicalConfiguration</code> and
0138: * copies all data contained in the specified configuration into the new
0139: * one.
0140: *
0141: * @param c the configuration that is to be copied (if <b>null</b>, this
0142: * constructor will behave like the standard constructor)
0143: * @since 1.4
0144: */
0145: public HierarchicalConfiguration(HierarchicalConfiguration c) {
0146: this ();
0147: if (c != null) {
0148: CloneVisitor visitor = new CloneVisitor();
0149: c.getRootNode().visit(visitor);
0150: setRootNode(visitor.getClone());
0151: }
0152: }
0153:
0154: /**
0155: * Returns the root node of this hierarchical configuration. This method
0156: * exists for backwards compatibility only. New code should use the
0157: * <code>{@link #getRootNode()}</code> method instead, which operates on
0158: * the preferred data type <code>ConfigurationNode</code>.
0159: *
0160: * @return the root node
0161: */
0162: public Node getRoot() {
0163: return root;
0164: }
0165:
0166: /**
0167: * Sets the root node of this hierarchical configuration. This method
0168: * exists for backwards compatibility only. New code should use the
0169: * <code>{@link #setRootNode(ConfigurationNode)}</code> method instead,
0170: * which operates on the preferred data type <code>ConfigurationNode</code>.
0171: *
0172: * @param node the root node
0173: */
0174: public void setRoot(Node node) {
0175: if (node == null) {
0176: throw new IllegalArgumentException(
0177: "Root node must not be null!");
0178: }
0179: root = node;
0180: rootNode = null;
0181: }
0182:
0183: /**
0184: * Returns the root node of this hierarchical configuration.
0185: *
0186: * @return the root node
0187: * @since 1.3
0188: */
0189: public ConfigurationNode getRootNode() {
0190: return (rootNode != null) ? rootNode : root;
0191: }
0192:
0193: /**
0194: * Sets the root node of this hierarchical configuration.
0195: *
0196: * @param rootNode the root node
0197: * @since 1.3
0198: */
0199: public void setRootNode(ConfigurationNode rootNode) {
0200: if (rootNode == null) {
0201: throw new IllegalArgumentException(
0202: "Root node must not be null!");
0203: }
0204: this .rootNode = rootNode;
0205:
0206: // For backward compatibility also set the old root field.
0207: root = (rootNode instanceof Node) ? (Node) rootNode : new Node(
0208: rootNode);
0209: }
0210:
0211: /**
0212: * Returns the default expression engine.
0213: *
0214: * @return the default expression engine
0215: * @since 1.3
0216: */
0217: public static ExpressionEngine getDefaultExpressionEngine() {
0218: return defaultExpressionEngine;
0219: }
0220:
0221: /**
0222: * Sets the default expression engine. This expression engine will be used
0223: * if no specific engine was set for an instance. It is shared between all
0224: * hierarchical configuration instances. So modifying its properties will
0225: * impact all instances, for which no specific engine is set.
0226: *
0227: * @param engine the new default expression engine
0228: * @since 1.3
0229: */
0230: public static void setDefaultExpressionEngine(
0231: ExpressionEngine engine) {
0232: if (engine == null) {
0233: throw new IllegalArgumentException(
0234: "Default expression engine must not be null!");
0235: }
0236: defaultExpressionEngine = engine;
0237: }
0238:
0239: /**
0240: * Returns the expression engine used by this configuration. This method
0241: * will never return <b>null</b>; if no specific expression engine was set,
0242: * the default expression engine will be returned.
0243: *
0244: * @return the current expression engine
0245: * @since 1.3
0246: */
0247: public ExpressionEngine getExpressionEngine() {
0248: return (expressionEngine != null) ? expressionEngine
0249: : getDefaultExpressionEngine();
0250: }
0251:
0252: /**
0253: * Sets the expression engine to be used by this configuration. All property
0254: * keys this configuration has to deal with will be interpreted by this
0255: * engine.
0256: *
0257: * @param expressionEngine the new expression engine; can be <b>null</b>,
0258: * then the default expression engine will be used
0259: * @since 1.3
0260: */
0261: public void setExpressionEngine(ExpressionEngine expressionEngine) {
0262: this .expressionEngine = expressionEngine;
0263: }
0264:
0265: /**
0266: * Fetches the specified property. This task is delegated to the associated
0267: * expression engine.
0268: *
0269: * @param key the key to be looked up
0270: * @return the found value
0271: */
0272: public Object getProperty(String key) {
0273: List nodes = fetchNodeList(key);
0274:
0275: if (nodes.size() == 0) {
0276: return null;
0277: } else {
0278: List list = new ArrayList();
0279: for (Iterator it = nodes.iterator(); it.hasNext();) {
0280: ConfigurationNode node = (ConfigurationNode) it.next();
0281: if (node.getValue() != null) {
0282: list.add(node.getValue());
0283: }
0284: }
0285:
0286: if (list.size() < 1) {
0287: return null;
0288: } else {
0289: return (list.size() == 1) ? list.get(0) : list;
0290: }
0291: }
0292: }
0293:
0294: /**
0295: * Adds the property with the specified key. This task will be delegated to
0296: * the associated <code>ExpressionEngine</code>, so the passed in key
0297: * must match the requirements of this implementation.
0298: *
0299: * @param key the key of the new property
0300: * @param obj the value of the new property
0301: */
0302: protected void addPropertyDirect(String key, Object obj) {
0303: NodeAddData data = getExpressionEngine().prepareAdd(
0304: getRootNode(), key);
0305: ConfigurationNode node = processNodeAddData(data);
0306: node.setValue(obj);
0307: }
0308:
0309: /**
0310: * Adds a collection of nodes at the specified position of the configuration
0311: * tree. This method works similar to <code>addProperty()</code>, but
0312: * instead of a single property a whole collection of nodes can be added -
0313: * and thus complete configuration sub trees. E.g. with this method it is
0314: * possible to add parts of another <code>HierarchicalConfiguration</code>
0315: * object to this object. If the passed in key refers to an existing and
0316: * unique node, the new nodes are added to this node. Otherwise a new node
0317: * will be created at the specified position in the hierarchy.
0318: *
0319: * @param key the key where the nodes are to be added; can be <b>null </b>,
0320: * then they are added to the root node
0321: * @param nodes a collection with the <code>Node</code> objects to be
0322: * added
0323: */
0324: public void addNodes(String key, Collection nodes) {
0325: if (nodes == null || nodes.isEmpty()) {
0326: return;
0327: }
0328:
0329: fireEvent(EVENT_ADD_NODES, key, nodes, true);
0330: ConfigurationNode parent;
0331: List target = fetchNodeList(key);
0332: if (target.size() == 1) {
0333: // existing unique key
0334: parent = (ConfigurationNode) target.get(0);
0335: } else {
0336: // otherwise perform an add operation
0337: parent = processNodeAddData(getExpressionEngine()
0338: .prepareAdd(getRootNode(), key));
0339: }
0340:
0341: if (parent.isAttribute()) {
0342: throw new IllegalArgumentException(
0343: "Cannot add nodes to an attribute node!");
0344: }
0345: for (Iterator it = nodes.iterator(); it.hasNext();) {
0346: ConfigurationNode child = (ConfigurationNode) it.next();
0347: if (child.isAttribute()) {
0348: parent.addAttribute(child);
0349: } else {
0350: parent.addChild(child);
0351: }
0352: }
0353: fireEvent(EVENT_ADD_NODES, key, nodes, false);
0354: }
0355:
0356: /**
0357: * Checks if this configuration is empty. Empty means that there are no keys
0358: * with any values, though there can be some (empty) nodes.
0359: *
0360: * @return a flag if this configuration is empty
0361: */
0362: public boolean isEmpty() {
0363: return !nodeDefined(getRootNode());
0364: }
0365:
0366: /**
0367: * Creates a new <code>Configuration</code> object containing all keys
0368: * that start with the specified prefix. This implementation will return a
0369: * <code>HierarchicalConfiguration</code> object so that the structure of
0370: * the keys will be saved.
0371: *
0372: * @param prefix the prefix of the keys for the subset
0373: * @return a new configuration object representing the selected subset
0374: */
0375: public Configuration subset(String prefix) {
0376: Collection nodes = fetchNodeList(prefix);
0377: if (nodes.isEmpty()) {
0378: return new HierarchicalConfiguration();
0379: }
0380:
0381: final HierarchicalConfiguration parent = this ;
0382: HierarchicalConfiguration result = new HierarchicalConfiguration() {
0383: // Override interpolate to always interpolate on the parent
0384: protected Object interpolate(Object value) {
0385: return parent.interpolate(value);
0386: }
0387: };
0388: CloneVisitor visitor = new CloneVisitor();
0389:
0390: for (Iterator it = nodes.iterator(); it.hasNext();) {
0391: ConfigurationNode nd = (ConfigurationNode) it.next();
0392: nd.visit(visitor);
0393:
0394: for (Iterator it2 = visitor.getClone().getChildren()
0395: .iterator(); it2.hasNext();) {
0396: result.getRootNode().addChild(
0397: (ConfigurationNode) it2.next());
0398: }
0399: for (Iterator it2 = visitor.getClone().getAttributes()
0400: .iterator(); it2.hasNext();) {
0401: result.getRootNode().addAttribute(
0402: (ConfigurationNode) it2.next());
0403: }
0404: }
0405:
0406: return (result.isEmpty()) ? new HierarchicalConfiguration()
0407: : result;
0408: }
0409:
0410: /**
0411: * <p>
0412: * Returns a hierarchical subnode configuration object that wraps the
0413: * configuration node specified by the given key. This method provides an
0414: * easy means of accessing sub trees of a hierarchical configuration. In the
0415: * returned configuration the sub tree can directly be accessed, it becomes
0416: * the root node of this configuration. Because of this the passed in key
0417: * must select exactly one configuration node; otherwise an
0418: * <code>IllegalArgumentException</code> will be thrown.
0419: * </p>
0420: * <p>
0421: * The difference between this method and the
0422: * <code>{@link #subset(String)}</code> method is that
0423: * <code>subset()</code> supports arbitrary subsets of configuration nodes
0424: * while <code>configurationAt()</code> only returns a single sub tree.
0425: * Please refer to the documentation of the
0426: * <code>SubnodeConfiguration</code> class to obtain further information
0427: * about subnode configurations and when they should be used.
0428: * </p>
0429: *
0430: * @param key the key that selects the sub tree
0431: * @return a hierarchical configuration that contains this sub tree
0432: * @see SubnodeConfiguration
0433: * @since 1.3
0434: */
0435: public SubnodeConfiguration configurationAt(String key) {
0436: List nodes = fetchNodeList(key);
0437: if (nodes.size() != 1) {
0438: throw new IllegalArgumentException(
0439: "Passed in key must select exactly one node: "
0440: + key);
0441: }
0442: return createSubnodeConfiguration((ConfigurationNode) nodes
0443: .get(0));
0444: }
0445:
0446: /**
0447: * Returns a list of sub configurations for all configuration nodes selected
0448: * by the given key. This method will evaluate the passed in key (using the
0449: * current <code>ExpressionEngine</code>) and then create a subnode
0450: * configuration for each returned node (like
0451: * <code>{@link #configurationAt(String)}</code>}). This is especially
0452: * useful when dealing with list-like structures. As an example consider the
0453: * configuration that contains data about database tables and their fields.
0454: * If you need access to all fields of a certain table, you can simply do
0455: *
0456: * <pre>
0457: * List fields = config.configurationsAt("tables.table(0).fields.field");
0458: * for(Iterator it = fields.iterator(); it.hasNext();)
0459: * {
0460: * HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
0461: * // now the children and attributes of the field node can be
0462: * // directly accessed
0463: * String fieldName = sub.getString("name");
0464: * String fieldType = sub.getString("type");
0465: * ...
0466: * </pre>
0467: *
0468: * @param key the key for selecting the desired nodes
0469: * @return a list with hierarchical configuration objects; each
0470: * configuration represents one of the nodes selected by the passed in key
0471: * @since 1.3
0472: */
0473: public List configurationsAt(String key) {
0474: List nodes = fetchNodeList(key);
0475: List configs = new ArrayList(nodes.size());
0476: for (Iterator it = nodes.iterator(); it.hasNext();) {
0477: configs
0478: .add(createSubnodeConfiguration((ConfigurationNode) it
0479: .next()));
0480: }
0481: return configs;
0482: }
0483:
0484: /**
0485: * Creates a subnode configuration for the specified node. This method is
0486: * called by <code>configurationAt()</code> and
0487: * <code>configurationsAt()</code>.
0488: *
0489: * @param node the node, for which a subnode configuration is to be created
0490: * @return the configuration for the given node
0491: * @since 1.3
0492: */
0493: protected SubnodeConfiguration createSubnodeConfiguration(
0494: ConfigurationNode node) {
0495: return new SubnodeConfiguration(this , node);
0496: }
0497:
0498: /**
0499: * Checks if the specified key is contained in this configuration. Note that
0500: * for this configuration the term "contained" means that the key
0501: * has an associated value. If there is a node for this key that has no
0502: * value but children (either defined or undefined), this method will still
0503: * return <b>false </b>.
0504: *
0505: * @param key the key to be chekced
0506: * @return a flag if this key is contained in this configuration
0507: */
0508: public boolean containsKey(String key) {
0509: return getProperty(key) != null;
0510: }
0511:
0512: /**
0513: * Sets the value of the specified property.
0514: *
0515: * @param key the key of the property to set
0516: * @param value the new value of this property
0517: */
0518: public void setProperty(String key, Object value) {
0519: fireEvent(EVENT_SET_PROPERTY, key, value, true);
0520:
0521: Iterator itNodes = fetchNodeList(key).iterator();
0522: Iterator itValues;
0523: if (!isDelimiterParsingDisabled()) {
0524: itValues = PropertyConverter.toIterator(value,
0525: getListDelimiter());
0526: } else {
0527: itValues = new SingletonIterator(value);
0528: }
0529: while (itNodes.hasNext() && itValues.hasNext()) {
0530: ((ConfigurationNode) itNodes.next()).setValue(itValues
0531: .next());
0532: }
0533:
0534: // Add additional nodes if necessary
0535: while (itValues.hasNext()) {
0536: addPropertyDirect(key, itValues.next());
0537: }
0538:
0539: // Remove remaining nodes
0540: while (itNodes.hasNext()) {
0541: clearNode((ConfigurationNode) itNodes.next());
0542: }
0543:
0544: fireEvent(EVENT_SET_PROPERTY, key, value, false);
0545: }
0546:
0547: /**
0548: * Removes all values of the property with the given name and of keys that
0549: * start with this name. So if there is a property with the key
0550: * "foo" and a property with the key "foo.bar", a call
0551: * of <code>clearTree("foo")</code> would remove both properties.
0552: *
0553: * @param key the key of the property to be removed
0554: */
0555: public void clearTree(String key) {
0556: fireEvent(EVENT_CLEAR_TREE, key, null, true);
0557: List nodes = fetchNodeList(key);
0558:
0559: for (Iterator it = nodes.iterator(); it.hasNext();) {
0560: removeNode((ConfigurationNode) it.next());
0561: }
0562: fireEvent(EVENT_CLEAR_TREE, key, nodes, false);
0563: }
0564:
0565: /**
0566: * Removes the property with the given key. Properties with names that start
0567: * with the given key (i.e. properties below the specified key in the
0568: * hierarchy) won't be affected.
0569: *
0570: * @param key the key of the property to be removed
0571: */
0572: public void clearProperty(String key) {
0573: fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
0574: List nodes = fetchNodeList(key);
0575:
0576: for (Iterator it = nodes.iterator(); it.hasNext();) {
0577: clearNode((ConfigurationNode) it.next());
0578: }
0579:
0580: fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
0581: }
0582:
0583: /**
0584: * Returns an iterator with all keys defined in this configuration.
0585: * Note that the keys returned by this method will not contain any
0586: * indices. This means that some structure will be lost.</p>
0587: *
0588: * @return an iterator with the defined keys in this configuration
0589: */
0590: public Iterator getKeys() {
0591: DefinedKeysVisitor visitor = new DefinedKeysVisitor();
0592: getRootNode().visit(visitor);
0593:
0594: return visitor.getKeyList().iterator();
0595: }
0596:
0597: /**
0598: * Returns an iterator with all keys defined in this configuration that
0599: * start with the given prefix. The returned keys will not contain any
0600: * indices.
0601: *
0602: * @param prefix the prefix of the keys to start with
0603: * @return an iterator with the found keys
0604: */
0605: public Iterator getKeys(String prefix) {
0606: DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
0607: List nodes = fetchNodeList(prefix);
0608:
0609: for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();) {
0610: ConfigurationNode node = (ConfigurationNode) itNodes.next();
0611: for (Iterator it = node.getChildren().iterator(); it
0612: .hasNext();) {
0613: ((ConfigurationNode) it.next()).visit(visitor);
0614: }
0615: for (Iterator it = node.getAttributes().iterator(); it
0616: .hasNext();) {
0617: ((ConfigurationNode) it.next()).visit(visitor);
0618: }
0619: }
0620:
0621: return visitor.getKeyList().iterator();
0622: }
0623:
0624: /**
0625: * Returns the maximum defined index for the given key. This is useful if
0626: * there are multiple values for this key. They can then be addressed
0627: * separately by specifying indices from 0 to the return value of this
0628: * method.
0629: *
0630: * @param key the key to be checked
0631: * @return the maximum defined index for this key
0632: */
0633: public int getMaxIndex(String key) {
0634: return fetchNodeList(key).size() - 1;
0635: }
0636:
0637: /**
0638: * Creates a copy of this object. This new configuration object will contain
0639: * copies of all nodes in the same structure. Registered event listeners
0640: * won't be cloned; so they are not registered at the returned copy.
0641: *
0642: * @return the copy
0643: * @since 1.2
0644: */
0645: public Object clone() {
0646: try {
0647: HierarchicalConfiguration copy = (HierarchicalConfiguration) super
0648: .clone();
0649:
0650: // clone the nodes, too
0651: CloneVisitor v = new CloneVisitor();
0652: getRootNode().visit(v);
0653: copy.setRootNode(v.getClone());
0654:
0655: return copy;
0656: } catch (CloneNotSupportedException cex) {
0657: // should not happen
0658: throw new ConfigurationRuntimeException(cex);
0659: }
0660: }
0661:
0662: /**
0663: * Helper method for fetching a list of all nodes that are addressed by the
0664: * specified key.
0665: *
0666: * @param key the key
0667: * @return a list with all affected nodes (never <b>null </b>)
0668: */
0669: protected List fetchNodeList(String key) {
0670: return getExpressionEngine().query(getRootNode(), key);
0671: }
0672:
0673: /**
0674: * Recursive helper method for fetching a property. This method processes
0675: * all facets of a configuration key, traverses the tree of properties and
0676: * fetches the the nodes of all matching properties.
0677: *
0678: * @param keyPart the configuration key iterator
0679: * @param node the actual node
0680: * @param nodes here the found nodes are stored
0681: * @deprecated Property keys are now evaluated by the expression engine
0682: * associated with the configuration; this method will no longer be called.
0683: * If you want to modify the way properties are looked up, consider
0684: * implementing you own <code>ExpressionEngine</code> implementation.
0685: */
0686: protected void findPropertyNodes(
0687: ConfigurationKey.KeyIterator keyPart, Node node,
0688: Collection nodes) {
0689: }
0690:
0691: /**
0692: * Checks if the specified node is defined.
0693: *
0694: * @param node the node to be checked
0695: * @return a flag if this node is defined
0696: * @deprecated Use the method <code>{@link #nodeDefined(ConfigurationNode)}</code>
0697: * instead.
0698: */
0699: protected boolean nodeDefined(Node node) {
0700: return nodeDefined((ConfigurationNode) node);
0701: }
0702:
0703: /**
0704: * Checks if the specified node is defined.
0705: *
0706: * @param node the node to be checked
0707: * @return a flag if this node is defined
0708: */
0709: protected boolean nodeDefined(ConfigurationNode node) {
0710: DefinedVisitor visitor = new DefinedVisitor();
0711: node.visit(visitor);
0712: return visitor.isDefined();
0713: }
0714:
0715: /**
0716: * Removes the specified node from this configuration. This method ensures
0717: * that parent nodes that become undefined by this operation are also
0718: * removed.
0719: *
0720: * @param node the node to be removed
0721: * @deprecated Use the method <code>{@link #removeNode(ConfigurationNode)}</code>
0722: * instead.
0723: */
0724: protected void removeNode(Node node) {
0725: removeNode((ConfigurationNode) node);
0726: }
0727:
0728: /**
0729: * Removes the specified node from this configuration. This method ensures
0730: * that parent nodes that become undefined by this operation are also
0731: * removed.
0732: *
0733: * @param node the node to be removed
0734: */
0735: protected void removeNode(ConfigurationNode node) {
0736: ConfigurationNode parent = node.getParentNode();
0737: if (parent != null) {
0738: parent.removeChild(node);
0739: if (!nodeDefined(parent)) {
0740: removeNode(parent);
0741: }
0742: }
0743: }
0744:
0745: /**
0746: * Clears the value of the specified node. If the node becomes undefined by
0747: * this operation, it is removed from the hierarchy.
0748: *
0749: * @param node the node to be cleard
0750: * @deprecated Use the method <code>{@link #clearNode(ConfigurationNode)}</code>
0751: * instead
0752: */
0753: protected void clearNode(Node node) {
0754: clearNode((ConfigurationNode) node);
0755: }
0756:
0757: /**
0758: * Clears the value of the specified node. If the node becomes undefined by
0759: * this operation, it is removed from the hierarchy.
0760: *
0761: * @param node the node to be cleard
0762: */
0763: protected void clearNode(ConfigurationNode node) {
0764: node.setValue(null);
0765: if (!nodeDefined(node)) {
0766: removeNode(node);
0767: }
0768: }
0769:
0770: /**
0771: * Returns a reference to the parent node of an add operation. Nodes for new
0772: * properties can be added as children of this node. If the path for the
0773: * specified key does not exist so far, it is created now.
0774: *
0775: * @param keyIt the iterator for the key of the new property
0776: * @param startNode the node to start the search with
0777: * @return the parent node for the add operation
0778: * @deprecated Adding new properties is now to a major part delegated to the
0779: * <code>ExpressionEngine</code> associated with this configuration instance.
0780: * This method will no longer be called. Developers who want to modify the
0781: * process of adding new properties should consider implementing their own
0782: * expression engine.
0783: */
0784: protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt,
0785: Node startNode) {
0786: return null;
0787: }
0788:
0789: /**
0790: * Finds the last existing node for an add operation. This method traverses
0791: * the configuration tree along the specified key. The last existing node on
0792: * this path is returned.
0793: *
0794: * @param keyIt the key iterator
0795: * @param node the actual node
0796: * @return the last existing node on the given path
0797: * @deprecated Adding new properties is now to a major part delegated to the
0798: * <code>ExpressionEngine</code> associated with this configuration instance.
0799: * This method will no longer be called. Developers who want to modify the
0800: * process of adding new properties should consider implementing their own
0801: * expression engine.
0802: */
0803: protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt,
0804: Node node) {
0805: return null;
0806: }
0807:
0808: /**
0809: * Creates the missing nodes for adding a new property. This method ensures
0810: * that there are corresponding nodes for all components of the specified
0811: * configuration key.
0812: *
0813: * @param keyIt the key iterator
0814: * @param root the base node of the path to be created
0815: * @return the last node of the path
0816: * @deprecated Adding new properties is now to a major part delegated to the
0817: * <code>ExpressionEngine</code> associated with this configuration instance.
0818: * This method will no longer be called. Developers who want to modify the
0819: * process of adding new properties should consider implementing their own
0820: * expression engine.
0821: */
0822: protected Node createAddPath(ConfigurationKey.KeyIterator keyIt,
0823: Node root) {
0824: return null;
0825: }
0826:
0827: /**
0828: * Creates a new <code>Node</code> object with the specified name. This
0829: * method can be overloaded in derived classes if a specific node type is
0830: * needed. This base implementation always returns a new object of the
0831: * <code>Node</code> class.
0832: *
0833: * @param name the name of the new node
0834: * @return the new node
0835: */
0836: protected Node createNode(String name) {
0837: return new Node(name);
0838: }
0839:
0840: /**
0841: * Helper method for processing a node add data object obtained from the
0842: * expression engine. This method will create all new nodes.
0843: *
0844: * @param data the data object
0845: * @return the new node
0846: * @since 1.3
0847: */
0848: private ConfigurationNode processNodeAddData(NodeAddData data) {
0849: ConfigurationNode node = data.getParent();
0850:
0851: // Create missing nodes on the path
0852: for (Iterator it = data.getPathNodes().iterator(); it.hasNext();) {
0853: ConfigurationNode child = createNode((String) it.next());
0854: node.addChild(child);
0855: node = child;
0856: }
0857:
0858: // Add new target node
0859: ConfigurationNode child = createNode(data.getNewNodeName());
0860: if (data.isAttribute()) {
0861: node.addAttribute(child);
0862: } else {
0863: node.addChild(child);
0864: }
0865: return child;
0866: }
0867:
0868: /**
0869: * Clears all reference fields in a node structure. A configuration node can
0870: * store a so-called "reference". The meaning of this data is
0871: * determined by a concrete sub class. Typically such references are
0872: * specific for a configuration instance. If this instance is cloned or
0873: * copied, they must be cleared. This can be done using this method.
0874: *
0875: * @param node the root node of the node hierarchy, in which the references
0876: * are to be cleared
0877: * @since 1.4
0878: */
0879: protected static void clearReferences(ConfigurationNode node) {
0880: node.visit(new ConfigurationNodeVisitorAdapter() {
0881: public void visitBeforeChildren(ConfigurationNode node) {
0882: node.setReference(null);
0883: }
0884: });
0885: }
0886:
0887: /**
0888: * A data class for storing (hierarchical) property information. A property
0889: * can have a value and an arbitrary number of child properties. From
0890: * version 1.3 on this class is only a thin wrapper over the
0891: * <code>{@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}</code>
0892: * class that exists mainly for the purpose of backwards compatibility.
0893: */
0894: public static class Node extends DefaultConfigurationNode implements
0895: Serializable {
0896: /**
0897: * The serial version UID.
0898: */
0899: private static final long serialVersionUID = -6357500633536941775L;
0900:
0901: /**
0902: * Creates a new instance of <code>Node</code>.
0903: */
0904: public Node() {
0905: super ();
0906: }
0907:
0908: /**
0909: * Creates a new instance of <code>Node</code> and sets the name.
0910: *
0911: * @param name the node's name
0912: */
0913: public Node(String name) {
0914: super (name);
0915: }
0916:
0917: /**
0918: * Creates a new instance of <code>Node</code> and sets the name and the value.
0919: *
0920: * @param name the node's name
0921: * @param value the value
0922: */
0923: public Node(String name, Object value) {
0924: super (name, value);
0925: }
0926:
0927: /**
0928: * Creates a new instance of <code>Node</code> based on the given
0929: * source node. All properties of the source node, including its
0930: * children and attributes, will be copied.
0931: *
0932: * @param src the node to be copied
0933: */
0934: public Node(ConfigurationNode src) {
0935: this (src.getName(), src.getValue());
0936: setReference(src.getReference());
0937: for (Iterator it = src.getChildren().iterator(); it
0938: .hasNext();) {
0939: addChild((ConfigurationNode) it.next());
0940: }
0941: for (Iterator it = src.getAttributes().iterator(); it
0942: .hasNext();) {
0943: addAttribute((ConfigurationNode) it.next());
0944: }
0945: }
0946:
0947: /**
0948: * Returns the parent of this node.
0949: *
0950: * @return this node's parent (can be <b>null</b>)
0951: */
0952: public Node getParent() {
0953: return (Node) getParentNode();
0954: }
0955:
0956: /**
0957: * Sets the parent of this node.
0958: *
0959: * @param node the parent node
0960: */
0961: public void setParent(Node node) {
0962: setParentNode(node);
0963: }
0964:
0965: /**
0966: * Adds the given node to the children of this node.
0967: *
0968: * @param node the child to be added
0969: */
0970: public void addChild(Node node) {
0971: addChild((ConfigurationNode) node);
0972: }
0973:
0974: /**
0975: * Returns a flag whether this node has child elements.
0976: *
0977: * @return <b>true</b> if there is a child node, <b>false</b> otherwise
0978: */
0979: public boolean hasChildren() {
0980: return getChildrenCount() > 0 || getAttributeCount() > 0;
0981: }
0982:
0983: /**
0984: * Removes the specified child from this node.
0985: *
0986: * @param child the child node to be removed
0987: * @return a flag if the child could be found
0988: */
0989: public boolean remove(Node child) {
0990: return child.isAttribute() ? removeAttribute(child)
0991: : removeChild(child);
0992: }
0993:
0994: /**
0995: * Removes all children with the given name.
0996: *
0997: * @param name the name of the children to be removed
0998: * @return a flag if children with this name existed
0999: */
1000: public boolean remove(String name) {
1001: boolean childrenRemoved = removeChild(name);
1002: boolean attrsRemoved = removeAttribute(name);
1003: return childrenRemoved || attrsRemoved;
1004: }
1005:
1006: /**
1007: * A generic method for traversing this node and all of its children.
1008: * This method sends the passed in visitor to this node and all of its
1009: * children.
1010: *
1011: * @param visitor the visitor
1012: * @param key here a configuration key with the name of the root node of
1013: * the iteration can be passed; if this key is not <b>null </b>, the
1014: * full pathes to the visited nodes are builded and passed to the
1015: * visitor's <code>visit()</code> methods
1016: */
1017: public void visit(NodeVisitor visitor, ConfigurationKey key) {
1018: int length = 0;
1019: if (key != null) {
1020: length = key.length();
1021: if (getName() != null) {
1022: key
1023: .append(StringUtils
1024: .replace(
1025: isAttribute() ? ConfigurationKey
1026: .constructAttributeKey(getName())
1027: : getName(),
1028: String
1029: .valueOf(ConfigurationKey.PROPERTY_DELIMITER),
1030: ConfigurationKey.ESCAPED_DELIMITER));
1031: }
1032: }
1033:
1034: visitor.visitBeforeChildren(this , key);
1035:
1036: for (Iterator it = getChildren().iterator(); it.hasNext()
1037: && !visitor.terminate();) {
1038: ((Node) it.next()).visit(visitor, key);
1039: }
1040: for (Iterator it = getAttributes().iterator(); it.hasNext()
1041: && !visitor.terminate();) {
1042: ((Node) it.next()).visit(visitor, key);
1043: }
1044:
1045: if (key != null) {
1046: key.setLength(length);
1047: }
1048: visitor.visitAfterChildren(this , key);
1049: }
1050: }
1051:
1052: /**
1053: * <p>Definition of a visitor class for traversing a node and all of its
1054: * children.</p><p>This class defines the interface of a visitor for
1055: * <code>Node</code> objects and provides a default implementation. The
1056: * method <code>visit()</code> of <code>Node</code> implements a generic
1057: * iteration algorithm based on the <em>Visitor</em> pattern. By providing
1058: * different implementations of visitors it is possible to collect different
1059: * data during the iteration process.</p>
1060: *
1061: */
1062: public static class NodeVisitor {
1063: /**
1064: * Visits the specified node. This method is called during iteration for
1065: * each node before its children have been visited.
1066: *
1067: * @param node the actual node
1068: * @param key the key of this node (may be <b>null </b>)
1069: */
1070: public void visitBeforeChildren(Node node, ConfigurationKey key) {
1071: }
1072:
1073: /**
1074: * Visits the specified node after its children have been processed.
1075: * This gives a visitor the opportunity of collecting additional data
1076: * after the child nodes have been visited.
1077: *
1078: * @param node the node to be visited
1079: * @param key the key of this node (may be <b>null </b>)
1080: */
1081: public void visitAfterChildren(Node node, ConfigurationKey key) {
1082: }
1083:
1084: /**
1085: * Returns a flag that indicates if iteration should be stopped. This
1086: * method is called after each visited node. It can be useful for
1087: * visitors that search a specific node. If this node is found, the
1088: * whole process can be stopped. This base implementation always returns
1089: * <b>false </b>.
1090: *
1091: * @return a flag if iteration should be stopped
1092: */
1093: public boolean terminate() {
1094: return false;
1095: }
1096: }
1097:
1098: /**
1099: * A specialized visitor that checks if a node is defined.
1100: * "Defined" in this terms means that the node or at least one of
1101: * its sub nodes is associated with a value.
1102: *
1103: */
1104: static class DefinedVisitor extends ConfigurationNodeVisitorAdapter {
1105: /** Stores the defined flag. */
1106: private boolean defined;
1107:
1108: /**
1109: * Checks if iteration should be stopped. This can be done if the first
1110: * defined node is found.
1111: *
1112: * @return a flag if iteration should be stopped
1113: */
1114: public boolean terminate() {
1115: return isDefined();
1116: }
1117:
1118: /**
1119: * Visits the node. Checks if a value is defined.
1120: *
1121: * @param node the actual node
1122: */
1123: public void visitBeforeChildren(ConfigurationNode node) {
1124: defined = node.getValue() != null;
1125: }
1126:
1127: /**
1128: * Returns the defined flag.
1129: *
1130: * @return the defined flag
1131: */
1132: public boolean isDefined() {
1133: return defined;
1134: }
1135: }
1136:
1137: /**
1138: * A specialized visitor that fills a list with keys that are defined in a
1139: * node hierarchy.
1140: */
1141: class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter {
1142: /** Stores the list to be filled. */
1143: private Set keyList;
1144:
1145: /** A stack with the keys of the already processed nodes. */
1146: private Stack parentKeys;
1147:
1148: /**
1149: * Default constructor.
1150: */
1151: public DefinedKeysVisitor() {
1152: keyList = new ListOrderedSet();
1153: parentKeys = new Stack();
1154: }
1155:
1156: /**
1157: * Creates a new <code>DefinedKeysVisitor</code> instance and sets the
1158: * prefix for the keys to fetch.
1159: *
1160: * @param prefix the prefix
1161: */
1162: public DefinedKeysVisitor(String prefix) {
1163: this ();
1164: parentKeys.push(prefix);
1165: }
1166:
1167: /**
1168: * Returns the list with all defined keys.
1169: *
1170: * @return the list with the defined keys
1171: */
1172: public Set getKeyList() {
1173: return keyList;
1174: }
1175:
1176: /**
1177: * Visits the node after its children has been processed. Removes this
1178: * node's key from the stack.
1179: *
1180: * @param node the node
1181: */
1182: public void visitAfterChildren(ConfigurationNode node) {
1183: parentKeys.pop();
1184: }
1185:
1186: /**
1187: * Visits the specified node. If this node has a value, its key is added
1188: * to the internal list.
1189: *
1190: * @param node the node to be visited
1191: */
1192: public void visitBeforeChildren(ConfigurationNode node) {
1193: String parentKey = parentKeys.isEmpty() ? null
1194: : (String) parentKeys.peek();
1195: String key = getExpressionEngine().nodeKey(node, parentKey);
1196: parentKeys.push(key);
1197: if (node.getValue() != null) {
1198: keyList.add(key);
1199: }
1200: }
1201: }
1202:
1203: /**
1204: * A specialized visitor that is able to create a deep copy of a node
1205: * hierarchy.
1206: */
1207: static class CloneVisitor extends ConfigurationNodeVisitorAdapter {
1208: /** A stack with the actual object to be copied. */
1209: private Stack copyStack;
1210:
1211: /** Stores the result of the clone process. */
1212: private ConfigurationNode result;
1213:
1214: /**
1215: * Creates a new instance of <code>CloneVisitor</code>.
1216: */
1217: public CloneVisitor() {
1218: copyStack = new Stack();
1219: }
1220:
1221: /**
1222: * Visits the specified node after its children have been processed.
1223: *
1224: * @param node the node
1225: */
1226: public void visitAfterChildren(ConfigurationNode node) {
1227: ConfigurationNode copy = (ConfigurationNode) copyStack
1228: .pop();
1229: if (copyStack.isEmpty()) {
1230: result = copy;
1231: }
1232: }
1233:
1234: /**
1235: * Visits and copies the specified node.
1236: *
1237: * @param node the node
1238: */
1239: public void visitBeforeChildren(ConfigurationNode node) {
1240: ConfigurationNode copy = (ConfigurationNode) node.clone();
1241: copy.setParentNode(null);
1242:
1243: if (!copyStack.isEmpty()) {
1244: if (node.isAttribute()) {
1245: ((ConfigurationNode) copyStack.peek())
1246: .addAttribute(copy);
1247: } else {
1248: ((ConfigurationNode) copyStack.peek())
1249: .addChild(copy);
1250: }
1251: }
1252:
1253: copyStack.push(copy);
1254: }
1255:
1256: /**
1257: * Returns the result of the clone process. This is the root node of the
1258: * cloned node hierarchy.
1259: *
1260: * @return the cloned root node
1261: */
1262: public ConfigurationNode getClone() {
1263: return result;
1264: }
1265: }
1266:
1267: /**
1268: * A specialized visitor base class that can be used for storing the tree of
1269: * configuration nodes. The basic idea is that each node can be associated
1270: * with a reference object. This reference object has a concrete meaning in
1271: * a derived class, e.g. an entry in a JNDI context or an XML element. When
1272: * the configuration tree is set up, the <code>load()</code> method is
1273: * responsible for setting the reference objects. When the configuration
1274: * tree is later modified, new nodes do not have a defined reference object.
1275: * This visitor class processes all nodes and finds the ones without a
1276: * defined reference object. For those nodes the <code>insert()</code>
1277: * method is called, which must be defined in concrete sub classes. This
1278: * method can perform all steps to integrate the new node into the original
1279: * structure.
1280: *
1281: */
1282: protected abstract static class BuilderVisitor extends NodeVisitor {
1283: /**
1284: * Visits the specified node before its children have been traversed.
1285: *
1286: * @param node the node to visit
1287: * @param key the current key
1288: */
1289: public void visitBeforeChildren(Node node, ConfigurationKey key) {
1290: Collection subNodes = new LinkedList(node.getChildren());
1291: subNodes.addAll(node.getAttributes());
1292: Iterator children = subNodes.iterator();
1293: Node sibling1 = null;
1294: Node nd = null;
1295:
1296: while (children.hasNext()) {
1297: // find the next new node
1298: do {
1299: sibling1 = nd;
1300: nd = (Node) children.next();
1301: } while (nd.getReference() != null
1302: && children.hasNext());
1303:
1304: if (nd.getReference() == null) {
1305: // find all following new nodes
1306: List newNodes = new LinkedList();
1307: newNodes.add(nd);
1308: while (children.hasNext()) {
1309: nd = (Node) children.next();
1310: if (nd.getReference() == null) {
1311: newNodes.add(nd);
1312: } else {
1313: break;
1314: }
1315: }
1316:
1317: // Insert all new nodes
1318: Node sibling2 = (nd.getReference() == null) ? null
1319: : nd;
1320: for (Iterator it = newNodes.iterator(); it
1321: .hasNext();) {
1322: Node insertNode = (Node) it.next();
1323: if (insertNode.getReference() == null) {
1324: Object ref = insert(insertNode, node,
1325: sibling1, sibling2);
1326: if (ref != null) {
1327: insertNode.setReference(ref);
1328: }
1329: sibling1 = insertNode;
1330: }
1331: }
1332: }
1333: }
1334: }
1335:
1336: /**
1337: * Inserts a new node into the structure constructed by this builder.
1338: * This method is called for each node that has been added to the
1339: * configuration tree after the configuration has been loaded from its
1340: * source. These new nodes have to be inserted into the original
1341: * structure. The passed in nodes define the position of the node to be
1342: * inserted: its parent and the siblings between to insert. The return
1343: * value is interpreted as the new reference of the affected
1344: * <code>Node</code> object; if it is not <b>null </b>, it is passed
1345: * to the node's <code>setReference()</code> method.
1346: *
1347: * @param newNode the node to be inserted
1348: * @param parent the parent node
1349: * @param sibling1 the sibling after which the node is to be inserted;
1350: * can be <b>null </b> if the new node is going to be the first child
1351: * node
1352: * @param sibling2 the sibling before which the node is to be inserted;
1353: * can be <b>null </b> if the new node is going to be the last child
1354: * node
1355: * @return the reference object for the node to be inserted
1356: */
1357: protected abstract Object insert(Node newNode, Node parent,
1358: Node sibling1, Node sibling2);
1359: }
1360: }
|