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    }