View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration.tree;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.configuration.ConfigurationRuntimeException;
29  
30  /***
31   * <p>
32   * A default implementation of the <code>ConfigurationNode</code> interface.
33   * </p>
34   *
35   * @since 1.3
36   * @author Oliver Heger
37   */
38  public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
39  {
40      /*** Stores the children of this node. */
41      private SubNodes children;
42  
43      /*** Stores the attributes of this node. */
44      private SubNodes attributes;
45  
46      /*** Stores a reference to this node's parent. */
47      private ConfigurationNode parent;
48  
49      /*** Stores the value of this node. */
50      private Object value;
51  
52      /*** Stores the reference. */
53      private Object reference;
54  
55      /*** Stores the name of this node. */
56      private String name;
57  
58      /*** Stores a flag if this is an attribute. */
59      private boolean attribute;
60  
61      /***
62       * Creates a new uninitialized instance of
63       * <code>DefaultConfigurationNode</code>.
64       */
65      public DefaultConfigurationNode()
66      {
67          this(null);
68      }
69  
70      /***
71       * Creates a new instance of <code>DefaultConfigurationNode</code> and
72       * initializes it with the node name.
73       *
74       * @param name the name of this node
75       */
76      public DefaultConfigurationNode(String name)
77      {
78          this(name, null);
79      }
80  
81      /***
82       * Creates a new instance of <code>DefaultConfigurationNode</code> and
83       * initializes it with the name and a value.
84       *
85       * @param name the node's name
86       * @param value the node's value
87       */
88      public DefaultConfigurationNode(String name, Object value)
89      {
90          setName(name);
91          setValue(value);
92          initSubNodes();
93      }
94  
95      /***
96       * Returns the name of this node.
97       *
98       * @return the name of this node
99       */
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 }