/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration.tree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.configuration.ConfigurationRuntimeException;
/**
* <p>
* A default implementation of the <code>ConfigurationNode</code> interface.
* </p>
*
* @since 1.3
* @author Oliver Heger
*/
public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
{
/** Stores the children of this node. */
private SubNodes children;
/** Stores the attributes of this node. */
private SubNodes attributes;
/** Stores a reference to this node's parent. */
private ConfigurationNode parent;
/** Stores the value of this node. */
private Object value;
/** Stores the reference. */
private Object reference;
/** Stores the name of this node. */
private String name;
/** Stores a flag if this is an attribute. */
private boolean attribute;
/**
* Creates a new uninitialized instance of
* <code>DefaultConfigurationNode</code>.
*/
public DefaultConfigurationNode()
{
this(null);
}
/**
* Creates a new instance of <code>DefaultConfigurationNode</code> and
* initializes it with the node name.
*
* @param name the name of this node
*/
public DefaultConfigurationNode(String name)
{
this(name, null);
}
/**
* Creates a new instance of <code>DefaultConfigurationNode</code> and
* initializes it with the name and a value.
*
* @param name the node's name
* @param value the node's value
*/
public DefaultConfigurationNode(String name, Object value)
{
setName(name);
setValue(value);
initSubNodes();
}
/**
* Returns the name of this node.
*
* @return the name of this node
*/
public String getName()
{
return name;
}
/**
* Sets the name of this node.
*
* @param name the new name
*/
public void setName(String name)
{
checkState();
this.name = name;
}
/**
* Returns the value of this node.
*
* @return the value of this node
*/
public Object getValue()
{
return value;
}
/**
* Sets the value of this node.
*
* @param val the value of this node
*/
public void setValue(Object val)
{
value = val;
}
/**
* Returns the reference.
*
* @return the reference
*/
public Object getReference()
{
return reference;
}
/**
* Sets the reference.
*
* @param reference the reference object
*/
public void setReference(Object reference)
{
this.reference = reference;
}
/**
* Returns a reference to this node's parent.
*
* @return the parent node or <b>null </b> if this is the root
*/
public ConfigurationNode getParentNode()
{
return parent;
}
/**
* Sets the parent of this node.
*
* @param parent the parent of this node
*/
public void setParentNode(ConfigurationNode parent)
{
this.parent = parent;
}
/**
* Adds a new child to this node.
*
* @param child the new child
*/
public void addChild(ConfigurationNode child)
{
children.addNode(child);
child.setAttribute(false);
child.setParentNode(this);
}
/**
* Returns a list with all children of this node.
*
* @return a list with all child nodes
*/
public List getChildren()
{
return children.getSubNodes();
}
/**
* Returns the number of all children of this node.
*
* @return the number of all children
*/
public int getChildrenCount()
{
return children.getSubNodes().size();
}
/**
* Returns a list of all children with the given name.
*
* @param name the name; can be <b>null </b>, then all children are returned
* @return a list of all children with the given name
*/
public List getChildren(String name)
{
return children.getSubNodes(name);
}
/**
* Returns the number of children with the given name.
*
* @param name the name; can be <b>null </b>, then the number of all
* children is returned
* @return the number of child nodes with this name
*/
public int getChildrenCount(String name)
{
return children.getSubNodes(name).size();
}
/**
* Returns the child node with the given index.
*
* @param index the index (0-based)
* @return the child with this index
*/
public ConfigurationNode getChild(int index)
{
return children.getNode(index);
}
/**
* Removes the specified child node from this node.
*
* @param child the node to be removed
* @return a flag if a node was removed
*/
public boolean removeChild(ConfigurationNode child)
{
return children.removeNode(child);
}
/**
* Removes all children with the given name.
*
* @param childName the name of the children to be removed
* @return a flag if at least one child node was removed
*/
public boolean removeChild(String childName)
{
return children.removeNodes(childName);
}
/**
* Removes all child nodes of this node.
*/
public void removeChildren()
{
children.clear();
}
/**
* Checks if this node is an attribute node.
*
* @return a flag if this is an attribute node
*/
public boolean isAttribute()
{
return attribute;
}
/**
* Sets the attribute flag. Note: this method can only be called if the node
* is not already part of a node hierarchy.
*
* @param f the attribute flag
*/
public void setAttribute(boolean f)
{
checkState();
attribute = f;
}
/**
* Adds the specified attribute to this node.
*
* @param attr the attribute to be added
*/
public void addAttribute(ConfigurationNode attr)
{
attributes.addNode(attr);
attr.setAttribute(true);
attr.setParentNode(this);
}
/**
* Returns a list with the attributes of this node. This list contains
* <code>ConfigurationNode</code> objects, too.
*
* @return the attribute list, never <b>null </b>
*/
public List getAttributes()
{
return attributes.getSubNodes();
}
/**
* Returns the number of attributes contained in this node.
*
* @return the number of attributes
*/
public int getAttributeCount()
{
return attributes.getSubNodes().size();
}
/**
* Returns a list with all attributes of this node with the given name.
*
* @param name the attribute's name
* @return all attributes with this name
*/
public List getAttributes(String name)
{
return attributes.getSubNodes(name);
}
/**
* Returns the number of attributes of this node with the given name.
*
* @param name the name
* @return the number of attributes with this name
*/
public int getAttributeCount(String name)
{
return getAttributes(name).size();
}
/**
* Removes the specified attribute.
*
* @param node the attribute node to be removed
* @return a flag if the attribute could be removed
*/
public boolean removeAttribute(ConfigurationNode node)
{
return attributes.removeNode(node);
}
/**
* Removes all attributes with the specified name.
*
* @param name the name
* @return a flag if at least one attribute was removed
*/
public boolean removeAttribute(String name)
{
return attributes.removeNodes(name);
}
/**
* Returns the attribute with the given index.
*
* @param index the index (0-based)
* @return the attribute with this index
*/
public ConfigurationNode getAttribute(int index)
{
return attributes.getNode(index);
}
/**
* Removes all attributes of this node.
*/
public void removeAttributes()
{
attributes.clear();
}
/**
* Returns a flag if this node is defined. This means that the node contains
* some data.
*
* @return a flag whether this node is defined
*/
public boolean isDefined()
{
return getValue() != null || getChildrenCount() > 0
|| getAttributeCount() > 0;
}
/**
* Visits this node and all its sub nodes.
*
* @param visitor the visitor
*/
public void visit(ConfigurationNodeVisitor visitor)
{
if (visitor == null)
{
throw new IllegalArgumentException("Visitor must not be null!");
}
if (!visitor.terminate())
{
visitor.visitBeforeChildren(this);
children.visit(visitor);
attributes.visit(visitor);
visitor.visitAfterChildren(this);
} /* if */
}
/**
* Creates a copy of this object. This is not a deep copy, the children are
* not cloned.
*
* @return a copy of this object
*/
public Object clone()
{
try
{
DefaultConfigurationNode copy = (DefaultConfigurationNode) super
.clone();
copy.initSubNodes();
return copy;
}
catch (CloneNotSupportedException cex)
{
// should not happen
throw new ConfigurationRuntimeException("Cannot clone " + getClass());
}
}
/**
* Checks if a modification of this node is allowed. Some properties of a
* node must not be changed when the node has a parent. This method checks
* this and throws a runtime exception if necessary.
*/
protected void checkState()
{
if (getParentNode() != null)
{
throw new IllegalStateException(
"Node cannot be modified when added to a parent!");
}
}
/**
* Creates a <code>SubNodes</code> instance that is used for storing
* either this node's children or attributes.
*
* @param attributes <b>true</b> if the returned instance is used for
* storing attributes, <b>false</b> for storing child nodes
* @return the <code>SubNodes</code> object to use
*/
protected SubNodes createSubNodes(boolean attributes)
{
return new SubNodes();
}
/**
* Deals with the reference when a node is removed. This method is called
* for each removed child node or attribute. It can be overloaded in sub
* classes, for which the reference has a concrete meaning and remove
* operations need some update actions. This default implementation is
* empty.
*/
protected void removeReference()
{
}
/**
* Helper method for initializing the sub nodes objects.
*/
private void initSubNodes()
{
children = createSubNodes(false);
attributes = createSubNodes(true);
}
/**
* An internally used helper class for managing a collection of sub nodes.
*/
protected static class SubNodes
{
/** Stores a list for the sub nodes. */
private List nodes;
/** Stores a map for accessing subnodes by name. */
private Map namedNodes;
/**
* Adds a new sub node.
*
* @param node the node to add
*/
public void addNode(ConfigurationNode node)
{
if (node == null || node.getName() == null)
{
throw new IllegalArgumentException(
"Node to add must have a defined name!");
}
node.setParentNode(null); // reset, will later be set
if (nodes == null)
{
nodes = new ArrayList();
namedNodes = new HashMap();
}
nodes.add(node);
List lst = (List) namedNodes.get(node.getName());
if (lst == null)
{
lst = new LinkedList();
namedNodes.put(node.getName(), lst);
}
lst.add(node);
}
/**
* Removes a sub node.
*
* @param node the node to remove
* @return a flag if the node could be removed
*/
public boolean removeNode(ConfigurationNode node)
{
if (nodes != null && node != null && nodes.contains(node))
{
detachNode(node);
nodes.remove(node);
List lst = (List) namedNodes.get(node.getName());
if (lst != null)
{
lst.remove(node);
if (lst.isEmpty())
{
namedNodes.remove(node.getName());
}
}
return true;
}
else
{
return false;
}
}
/**
* Removes all sub nodes with the given name.
*
* @param name the name
* @return a flag if at least on sub node was removed
*/
public boolean removeNodes(String name)
{
if (nodes != null && name != null)
{
List lst = (List) namedNodes.remove(name);
if (lst != null)
{
detachNodes(lst);
nodes.removeAll(lst);
return true;
}
}
return false;
}
/**
* Removes all sub nodes.
*/
public void clear()
{
if (nodes != null)
{
detachNodes(nodes);
nodes = null;
namedNodes = null;
}
}
/**
* Returns the node with the given index. If this index cannot be found,
* an <code>IndexOutOfBoundException</code> exception will be thrown.
*
* @param index the index (0-based)
* @return the sub node at the specified index
*/
public ConfigurationNode getNode(int index)
{
if (nodes == null)
{
throw new IndexOutOfBoundsException("No sub nodes available!");
}
return (ConfigurationNode) nodes.get(index);
}
/**
* Returns a list with all stored sub nodes. The return value is never
* <b>null</b>.
*
* @return a list with the sub nodes
*/
public List getSubNodes()
{
return (nodes == null) ? Collections.EMPTY_LIST : Collections
.unmodifiableList(nodes);
}
/**
* Returns a list of the sub nodes with the given name. The return value
* is never <b>null</b>.
*
* @param name the name; if <b>null</b> is passed, all sub nodes will
* be returned
* @return all sub nodes with this name
*/
public List getSubNodes(String name)
{
if (name == null)
{
return getSubNodes();
}
List result;
if (nodes == null)
{
result = null;
}
else
{
result = (List) namedNodes.get(name);
}
return (result == null) ? Collections.EMPTY_LIST : Collections
.unmodifiableList(result);
}
/**
* Let the passed in visitor visit all sub nodes.
*
* @param visitor the visitor
*/
public void visit(ConfigurationNodeVisitor visitor)
{
if (nodes != null)
{
for (Iterator it = nodes.iterator(); it.hasNext()
&& !visitor.terminate();)
{
((ConfigurationNode) it.next()).visit(visitor);
}
}
}
/**
* This method is called whenever a sub node is removed from this
* object. It ensures that the removed node's parent is reset and its
* <code>removeReference()</code> method gets called.
*
* @param subNode the node to be removed
*/
protected void detachNode(ConfigurationNode subNode)
{
subNode.setParentNode(null);
if (subNode instanceof DefaultConfigurationNode)
{
((DefaultConfigurationNode) subNode).removeReference();
}
}
/**
* Detaches a list of sub nodes. This method calls
* <code>detachNode()</code> for each node contained in the list.
*
* @param subNodes the list with nodes to be detached
*/
protected void detachNodes(Collection subNodes)
{
for (Iterator it = subNodes.iterator(); it.hasNext();)
{
detachNode((ConfigurationNode) it.next());
}
}
}
}
|