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 */
017package org.apache.commons.configuration.tree;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027
028import 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 1301991 2012-03-17 20:18:02Z sebb $
040 */
041public 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 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}