View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.configuration.tree;
    18
    19  import java.util.ArrayList;
    20  import java.util.Collection;
    21  import java.util.Collections;
    22  import java.util.HashMap;
    23  import java.util.Iterator;
    24  import java.util.LinkedList;
    25  import java.util.List;
    26  import java.util.Map;
    27
    28  import org.apache.commons.configuration.ConfigurationRuntimeException;
    29
    30  /***
    31   * <p>
    32   * A default implementation of the <code>ConfigurationNode</code> interface.
    33   * </p>
    34   *
    35   * @since 1.3
    36   * @author Oliver Heger
    37   */
    38  public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
    39  {
    40      /*** Stores the children of this node. */
    41      private SubNodes children;
    42
    43      /*** Stores the attributes of this node. */
    44      private SubNodes attributes;
    45
    46      /*** Stores a reference to this node's parent. */
    47      private ConfigurationNode parent;
    48
    49      /*** Stores the value of this node. */
    50      private Object value;
    51
    52      /*** Stores the reference. */
    53      private Object reference;
    54
    55      /*** Stores the name of this node. */
    56      private String name;
    57
    58      /*** Stores a flag if this is an attribute. */
    59      private boolean attribute;
    60
    61      /***
    62       * Creates a new uninitialized instance of
    63       * <code>DefaultConfigurationNode</code>.
    64       */
    65      public DefaultConfigurationNode()
    66      {
    67          this(null);
    68      }
    69
    70      /***
    71       * Creates a new instance of <code>DefaultConfigurationNode</code> and
    72       * initializes it with the node name.
    73       *
    74       * @param name the name of this node
    75       */
    76      public DefaultConfigurationNode(String name)
    77      {
    78          this(name, null);
    79      }
    80
    81      /***
    82       * Creates a new instance of <code>DefaultConfigurationNode</code> and
    83       * initializes it with the name and a value.
    84       *
    85       * @param name the node's name
    86       * @param value the node's value
    87       */
    88      public DefaultConfigurationNode(String name, Object value)
    89      {
    90          setName(name);
    91          setValue(value);
    92          initSubNodes();
    93      }
    94
    95      /***
    96       * Returns the name of this node.
    97       *
    98       * @return the name of this node
    99       */
    100     public String getName()
    101     {
    102         return name;
    103     }
    104
    105     /***
    106      * Sets the name of this node.
    107      *
    108      * @param name the new name
    109      */
    110     public void setName(String name)
    111     {
    112         checkState();
    113         this.name = name;
    114     }
    115
    116     /***
    117      * Returns the value of this node.
    118      *
    119      * @return the value of this node
    120      */
    121     public Object getValue()
    122     {
    123         return value;
    124     }
    125
    126     /***
    127      * Sets the value of this node.
    128      *
    129      * @param val the value of this node
    130      */
    131     public void setValue(Object val)
    132     {
    133         value = val;
    134     }
    135
    136     /***
    137      * Returns the reference.
    138      *
    139      * @return the reference
    140      */
    141     public Object getReference()
    142     {
    143         return reference;
    144     }
    145
    146     /***
    147      * Sets the reference.
    148      *
    149      * @param reference the reference object
    150      */
    151     public void setReference(Object reference)
    152     {
    153         this.reference = reference;
    154     }
    155
    156     /***
    157      * Returns a reference to this node's parent.
    158      *
    159      * @return the parent node or <b>null </b> if this is the root
    160      */
    161     public ConfigurationNode getParentNode()
    162     {
    163         return parent;
    164     }
    165
    166     /***
    167      * Sets the parent of this node.
    168      *
    169      * @param parent the parent of this node
    170      */
    171     public void setParentNode(ConfigurationNode parent)
    172     {
    173         this.parent = parent;
    174     }
    175
    176     /***
    177      * Adds a new child to this node.
    178      *
    179      * @param child the new child
    180      */
    181     public void addChild(ConfigurationNode child)
    182     {
    183         children.addNode(child);
    184         child.setAttribute(false);
    185         child.setParentNode(this);
    186     }
    187
    188     /***
    189      * Returns a list with all children of this node.
    190      *
    191      * @return a list with all child nodes
    192      */
    193     public List getChildren()
    194     {
    195         return children.getSubNodes();
    196     }
    197
    198     /***
    199      * Returns the number of all children of this node.
    200      *
    201      * @return the number of all children
    202      */
    203     public int getChildrenCount()
    204     {
    205         return children.getSubNodes().size();
    206     }
    207
    208     /***
    209      * Returns a list of all children with the given name.
    210      *
    211      * @param name the name; can be <b>null </b>, then all children are returned
    212      * @return a list of all children with the given name
    213      */
    214     public List getChildren(String name)
    215     {
    216         return children.getSubNodes(name);
    217     }
    218
    219     /***
    220      * Returns the number of children with the given name.
    221      *
    222      * @param name the name; can be <b>null </b>, then the number of all
    223      * children is returned
    224      * @return the number of child nodes with this name
    225      */
    226     public int getChildrenCount(String name)
    227     {
    228         return children.getSubNodes(name).size();
    229     }
    230
    231     /***
    232      * Returns the child node with the given index.
    233      *
    234      * @param index the index (0-based)
    235      * @return the child with this index
    236      */
    237     public ConfigurationNode getChild(int index)
    238     {
    239         return children.getNode(index);
    240     }
    241
    242     /***
    243      * Removes the specified child node from this node.
    244      *
    245      * @param child the node to be removed
    246      * @return a flag if a node was removed
    247      */
    248     public boolean removeChild(ConfigurationNode child)
    249     {
    250         return children.removeNode(child);
    251     }
    252
    253     /***
    254      * Removes all children with the given name.
    255      *
    256      * @param childName the name of the children to be removed
    257      * @return a flag if at least one child node was removed
    258      */
    259     public boolean removeChild(String childName)
    260     {
    261         return children.removeNodes(childName);
    262     }
    263
    264     /***
    265      * Removes all child nodes of this node.
    266      */
    267     public void removeChildren()
    268     {
    269         children.clear();
    270     }
    271
    272     /***
    273      * Checks if this node is an attribute node.
    274      *
    275      * @return a flag if this is an attribute node
    276      */
    277     public boolean isAttribute()
    278     {
    279         return attribute;
    280     }
    281
    282     /***
    283      * Sets the attribute flag. Note: this method can only be called if the node
    284      * is not already part of a node hierarchy.
    285      *
    286      * @param f the attribute flag
    287      */
    288     public void setAttribute(boolean f)
    289     {
    290         checkState();
    291         attribute = f;
    292     }
    293
    294     /***
    295      * Adds the specified attribute to this node.
    296      *
    297      * @param attr the attribute to be added
    298      */
    299     public void addAttribute(ConfigurationNode attr)
    300     {
    301         attributes.addNode(attr);
    302         attr.setAttribute(true);
    303         attr.setParentNode(this);
    304     }
    305
    306     /***
    307      * Returns a list with the attributes of this node. This list contains
    308      * <code>ConfigurationNode</code> objects, too.
    309      *
    310      * @return the attribute list, never <b>null </b>
    311      */
    312     public List getAttributes()
    313     {
    314         return attributes.getSubNodes();
    315     }
    316
    317     /***
    318      * Returns the number of attributes contained in this node.
    319      *
    320      * @return the number of attributes
    321      */
    322     public int getAttributeCount()
    323     {
    324         return attributes.getSubNodes().size();
    325     }
    326
    327     /***
    328      * Returns a list with all attributes of this node with the given name.
    329      *
    330      * @param name the attribute's name
    331      * @return all attributes with this name
    332      */
    333     public List getAttributes(String name)
    334     {
    335         return attributes.getSubNodes(name);
    336     }
    337
    338     /***
    339      * Returns the number of attributes of this node with the given name.
    340      *
    341      * @param name the name
    342      * @return the number of attributes with this name
    343      */
    344     public int getAttributeCount(String name)
    345     {
    346         return getAttributes(name).size();
    347     }
    348
    349     /***
    350      * Removes the specified attribute.
    351      *
    352      * @param node the attribute node to be removed
    353      * @return a flag if the attribute could be removed
    354      */
    355     public boolean removeAttribute(ConfigurationNode node)
    356     {
    357         return attributes.removeNode(node);
    358     }
    359
    360     /***
    361      * Removes all attributes with the specified name.
    362      *
    363      * @param name the name
    364      * @return a flag if at least one attribute was removed
    365      */
    366     public boolean removeAttribute(String name)
    367     {
    368         return attributes.removeNodes(name);
    369     }
    370
    371     /***
    372      * Returns the attribute with the given index.
    373      *
    374      * @param index the index (0-based)
    375      * @return the attribute with this index
    376      */
    377     public ConfigurationNode getAttribute(int index)
    378     {
    379         return attributes.getNode(index);
    380     }
    381
    382     /***
    383      * Removes all attributes of this node.
    384      */
    385     public void removeAttributes()
    386     {
    387         attributes.clear();
    388     }
    389
    390     /***
    391      * Returns a flag if this node is defined. This means that the node contains
    392      * some data.
    393      *
    394      * @return a flag whether this node is defined
    395      */
    396     public boolean isDefined()
    397     {
    398         return getValue() != null || getChildrenCount() > 0
    399                 || getAttributeCount() > 0;
    400     }
    401
    402     /***
    403      * Visits this node and all its sub nodes.
    404      *
    405      * @param visitor the visitor
    406      */
    407     public void visit(ConfigurationNodeVisitor visitor)
    408     {
    409         if (visitor == null)
    410         {
    411             throw new IllegalArgumentException("Visitor must not be null!");
    412         }
    413
    414         if (!visitor.terminate())
    415         {
    416             visitor.visitBeforeChildren(this);
    417             children.visit(visitor);
    418             attributes.visit(visitor);
    419             visitor.visitAfterChildren(this);
    420         } /* if */
    421     }
    422
    423     /***
    424      * Creates a copy of this object. This is not a deep copy, the children are
    425      * not cloned.
    426      *
    427      * @return a copy of this object
    428      */
    429     public Object clone()
    430     {
    431         try
    432         {
    433             DefaultConfigurationNode copy = (DefaultConfigurationNode) super
    434                     .clone();
    435             copy.initSubNodes();
    436             return copy;
    437         }
    438         catch (CloneNotSupportedException cex)
    439         {
    440             // should not happen
    441             throw new ConfigurationRuntimeException("Cannot clone " + getClass());
    442         }
    443     }
    444
    445     /***
    446      * Checks if a modification of this node is allowed. Some properties of a
    447      * node must not be changed when the node has a parent. This method checks
    448      * this and throws a runtime exception if necessary.
    449      */
    450     protected void checkState()
    451     {
    452         if (getParentNode() != null)
    453         {
    454             throw new IllegalStateException(
    455                     "Node cannot be modified when added to a parent!");
    456         }
    457     }
    458
    459     /***
    460      * Creates a <code>SubNodes</code> instance that is used for storing
    461      * either this node's children or attributes.
    462      *
    463      * @param attributes <b>true</b> if the returned instance is used for
    464      * storing attributes, <b>false</b> for storing child nodes
    465      * @return the <code>SubNodes</code> object to use
    466      */
    467     protected SubNodes createSubNodes(boolean attributes)
    468     {
    469         return new SubNodes();
    470     }
    471
    472     /***
    473      * Deals with the reference when a node is removed. This method is called
    474      * for each removed child node or attribute. It can be overloaded in sub
    475      * classes, for which the reference has a concrete meaning and remove
    476      * operations need some update actions. This default implementation is
    477      * empty.
    478      */
    479     protected void removeReference()
    480     {
    481     }
    482
    483     /***
    484      * Helper method for initializing the sub nodes objects.
    485      */
    486     private void initSubNodes()
    487     {
    488         children = createSubNodes(false);
    489         attributes = createSubNodes(true);
    490     }
    491
    492     /***
    493      * An internally used helper class for managing a collection of sub nodes.
    494      */
    495     protected static class SubNodes
    496     {
    497         /*** Stores a list for the sub nodes. */
    498         private List nodes;
    499
    500         /*** Stores a map for accessing subnodes by name. */
    501         private Map namedNodes;
    502
    503         /***
    504          * Adds a new sub node.
    505          *
    506          * @param node the node to add
    507          */
    508         public void addNode(ConfigurationNode node)
    509         {
    510             if (node == null || node.getName() == null)
    511             {
    512                 throw new IllegalArgumentException(
    513                         "Node to add must have a defined name!");
    514             }
    515             node.setParentNode(null);  // reset, will later be set
    516
    517             if (nodes == null)
    518             {
    519                 nodes = new ArrayList();
    520                 namedNodes = new HashMap();
    521             }
    522
    523             nodes.add(node);
    524             List lst = (List) namedNodes.get(node.getName());
    525             if (lst == null)
    526             {
    527                 lst = new LinkedList();
    528                 namedNodes.put(node.getName(), lst);
    529             }
    530             lst.add(node);
    531         }
    532
    533         /***
    534          * Removes a sub node.
    535          *
    536          * @param node the node to remove
    537          * @return a flag if the node could be removed
    538          */
    539         public boolean removeNode(ConfigurationNode node)
    540         {
    541             if (nodes != null && node != null && nodes.contains(node))
    542             {
    543                 detachNode(node);
    544                 nodes.remove(node);
    545
    546                 List lst = (List) namedNodes.get(node.getName());
    547                 if (lst != null)
    548                 {
    549                     lst.remove(node);
    550                     if (lst.isEmpty())
    551                     {
    552                         namedNodes.remove(node.getName());
    553                     }
    554                 }
    555                 return true;
    556             }
    557
    558             else
    559             {
    560                 return false;
    561             }
    562         }
    563
    564         /***
    565          * Removes all sub nodes with the given name.
    566          *
    567          * @param name the name
    568          * @return a flag if at least on sub node was removed
    569          */
    570         public boolean removeNodes(String name)
    571         {
    572             if (nodes != null && name != null)
    573             {
    574                 List lst = (List) namedNodes.remove(name);
    575                 if (lst != null)
    576                 {
    577                     detachNodes(lst);
    578                     nodes.removeAll(lst);
    579                     return true;
    580                 }
    581             }
    582             return false;
    583         }
    584
    585         /***
    586          * Removes all sub nodes.
    587          */
    588         public void clear()
    589         {
    590             if (nodes != null)
    591             {
    592                 detachNodes(nodes);
    593                 nodes = null;
    594                 namedNodes = null;
    595             }
    596         }
    597
    598         /***
    599          * Returns the node with the given index. If this index cannot be found,
    600          * an <code>IndexOutOfBoundException</code> exception will be thrown.
    601          *
    602          * @param index the index (0-based)
    603          * @return the sub node at the specified index
    604          */
    605         public ConfigurationNode getNode(int index)
    606         {
    607             if (nodes == null)
    608             {
    609                 throw new IndexOutOfBoundsException("No sub nodes available!");
    610             }
    611             return (ConfigurationNode) nodes.get(index);
    612         }
    613
    614         /***
    615          * Returns a list with all stored sub nodes. The return value is never
    616          * <b>null</b>.
    617          *
    618          * @return a list with the sub nodes
    619          */
    620         public List getSubNodes()
    621         {
    622             return (nodes == null) ? Collections.EMPTY_LIST : Collections
    623                     .unmodifiableList(nodes);
    624         }
    625
    626         /***
    627          * Returns a list of the sub nodes with the given name. The return value
    628          * is never <b>null</b>.
    629          *
    630          * @param name the name; if <b>null</b> is passed, all sub nodes will
    631          * be returned
    632          * @return all sub nodes with this name
    633          */
    634         public List getSubNodes(String name)
    635         {
    636             if (name == null)
    637             {
    638                 return getSubNodes();
    639             }
    640
    641             List result;
    642             if (nodes == null)
    643             {
    644                 result = null;
    645             }
    646             else
    647             {
    648                 result = (List) namedNodes.get(name);
    649             }
    650
    651             return (result == null) ? Collections.EMPTY_LIST : Collections
    652                     .unmodifiableList(result);
    653         }
    654
    655         /***
    656          * Let the passed in visitor visit all sub nodes.
    657          *
    658          * @param visitor the visitor
    659          */
    660         public void visit(ConfigurationNodeVisitor visitor)
    661         {
    662             if (nodes != null)
    663             {
    664                 for (Iterator it = nodes.iterator(); it.hasNext()
    665                         && !visitor.terminate();)
    666                 {
    667                     ((ConfigurationNode) it.next()).visit(visitor);
    668                 }
    669             }
    670         }
    671
    672         /***
    673          * This method is called whenever a sub node is removed from this
    674          * object. It ensures that the removed node's parent is reset and its
    675          * <code>removeReference()</code> method gets called.
    676          *
    677          * @param subNode the node to be removed
    678          */
    679         protected void detachNode(ConfigurationNode subNode)
    680         {
    681             subNode.setParentNode(null);
    682             if (subNode instanceof DefaultConfigurationNode)
    683             {
    684                 ((DefaultConfigurationNode) subNode).removeReference();
    685             }
    686         }
    687
    688         /***
    689          * Detaches a list of sub nodes. This method calls
    690          * <code>detachNode()</code> for each node contained in the list.
    691          *
    692          * @param subNodes the list with nodes to be detached
    693          */
    694         protected void detachNodes(Collection subNodes)
    695         {
    696             for (Iterator it = subNodes.iterator(); it.hasNext();)
    697             {
    698                 detachNode((ConfigurationNode) it.next());
    699             }
    700         }
    701     }
    702 }