    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</code> interface.
    033     * </p>
    034     *
    035     * @since 1.3
    036     * @author Oliver Heger
    037     */
    038    public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
    039    {
    040        /** Stores the children of this node. */
    041        private SubNodes children;
    042
    043        /** Stores the attributes of this node. */
    044        private SubNodes attributes;
    045
    046        /** Stores a reference to this node's parent. */
    047        private ConfigurationNode parent;
    048
    049        /** Stores the value of this node. */
    050        private Object value;
    051
    052        /** Stores the reference. */
    053        private Object reference;
    054
    055        /** Stores the name of this node. */
    056        private String name;
    057
    058        /** Stores a flag if this is an attribute. */
    059        private boolean attribute;
    060
    061        /**
    062         * Creates a new uninitialized instance of
    063         * <code>DefaultConfigurationNode</code>.
    064         */
    065        public DefaultConfigurationNode()
    066        {
    067            this(null);
    068        }
    069
    070        /**
    071         * Creates a new instance of <code>DefaultConfigurationNode</code> and
    072         * initializes it with the node name.
    073         *
    074         * @param name the name of this node
    075         */
    076        public DefaultConfigurationNode(String name)
    077        {
    078            this(name, null);
    079        }
    080
    081        /**
    082         * Creates a new instance of <code>DefaultConfigurationNode</code> and
    083         * initializes it with the name and a value.
    084         *
    085         * @param name the node's name
    086         * @param value the node's value
    087         */
    088        public DefaultConfigurationNode(String name, Object value)
    089        {
    090            setName(name);
    091            setValue(value);
    092            initSubNodes();
    093        }
    094
    095        /**
    096         * Returns the name of this node.
    097         *
    098         * @return the name of this node
    099         */
    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    }