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