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    }