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