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  /***
29   * <p>
30   * A default implementation of the <code>ConfigurationNode</code> interface.
31   * </p>
32   *
33   * @since 1.3
34   * @author Oliver Heger
35   */
36  public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
37  {
38      /*** Stores the children of this node. */
39      private SubNodes children;
40  
41      /*** Stores the attributes of this node. */
42      private SubNodes attributes;
43  
44      /*** Stores a reference to this node's parent. */
45      private ConfigurationNode parent;
46  
47      /*** Stores the value of this node. */
48      private Object value;
49  
50      /*** Stores the reference. */
51      private Object reference;
52  
53      /*** Stores the name of this node. */
54      private String name;
55  
56      /*** Stores a flag if this is an attribute. */
57      private boolean attribute;
58  
59      /***
60       * Creates a new uninitialized instance of
61       * <code>DefaultConfigurationNode</code>.
62       */
63      public DefaultConfigurationNode()
64      {
65          this(null);
66      }
67  
68      /***
69       * Creates a new instance of <code>DefaultConfigurationNode</code> and
70       * initializes it with the node name.
71       *
72       * @param name the name of this node
73       */
74      public DefaultConfigurationNode(String name)
75      {
76          this(name, null);
77      }
78  
79      /***
80       * Creates a new instance of <code>DefaultConfigurationNode</code> and
81       * initializes it with the name and a value.
82       *
83       * @param name the node's name
84       * @param value the node's value
85       */
86      public DefaultConfigurationNode(String name, Object value)
87      {
88          setName(name);
89          setValue(value);
90          initSubNodes();
91      }
92  
93      /***
94       * Returns the name of this node.
95       *
96       * @return the name of this node
97       */
98      public String getName()
99      {
100         return name;
101     }
102 
103     /***
104      * Sets the name of this node.
105      *
106      * @param name the new name
107      */
108     public void setName(String name)
109     {
110         checkState();
111         this.name = name;
112     }
113 
114     /***
115      * Returns the value of this node.
116      *
117      * @return the value of this node
118      */
119     public Object getValue()
120     {
121         return value;
122     }
123 
124     /***
125      * Sets the value of this node.
126      *
127      * @param val the value of this node
128      */
129     public void setValue(Object val)
130     {
131         value = val;
132     }
133 
134     /***
135      * Returns the reference.
136      *
137      * @return the reference
138      */
139     public Object getReference()
140     {
141         return reference;
142     }
143 
144     /***
145      * Sets the reference.
146      *
147      * @param reference the reference object
148      */
149     public void setReference(Object reference)
150     {
151         this.reference = reference;
152     }
153 
154     /***
155      * Returns a reference to this node's parent.
156      *
157      * @return the parent node or <b>null </b> if this is the root
158      */
159     public ConfigurationNode getParentNode()
160     {
161         return parent;
162     }
163 
164     /***
165      * Sets the parent of this node.
166      *
167      * @param parent the parent of this node
168      */
169     public void setParentNode(ConfigurationNode parent)
170     {
171         this.parent = parent;
172     }
173 
174     /***
175      * Adds a new child to this node.
176      *
177      * @param child the new child
178      */
179     public void addChild(ConfigurationNode child)
180     {
181         children.addNode(child);
182         child.setAttribute(false);
183         child.setParentNode(this);
184     }
185 
186     /***
187      * Returns a list with all children of this node.
188      *
189      * @return a list with all child nodes
190      */
191     public List getChildren()
192     {
193         return children.getSubNodes();
194     }
195 
196     /***
197      * Returns the number of all children of this node.
198      *
199      * @return the number of all children
200      */
201     public int getChildrenCount()
202     {
203         return children.getSubNodes().size();
204     }
205 
206     /***
207      * Returns a list of all children with the given name.
208      *
209      * @param name the name; can be <b>null </b>, then all children are returned
210      * @return a list of all children with the given name
211      */
212     public List getChildren(String name)
213     {
214         return children.getSubNodes(name);
215     }
216 
217     /***
218      * Returns the number of children with the given name.
219      *
220      * @param name the name; can be <b>null </b>, then the number of all
221      * children is returned
222      * @return the number of child nodes with this name
223      */
224     public int getChildrenCount(String name)
225     {
226         return children.getSubNodes(name).size();
227     }
228 
229     /***
230      * Returns the child node with the given index.
231      *
232      * @param index the index (0-based)
233      * @return the child with this index
234      */
235     public ConfigurationNode getChild(int index)
236     {
237         return children.getNode(index);
238     }
239 
240     /***
241      * Removes the specified child node from this node.
242      *
243      * @param child the node to be removed
244      * @return a flag if a node was removed
245      */
246     public boolean removeChild(ConfigurationNode child)
247     {
248         return children.removeNode(child);
249     }
250 
251     /***
252      * Removes all children with the given name.
253      *
254      * @param childName the name of the children to be removed
255      * @return a flag if at least one child node was removed
256      */
257     public boolean removeChild(String childName)
258     {
259         return children.removeNodes(childName);
260     }
261 
262     /***
263      * Removes all child nodes of this node.
264      */
265     public void removeChildren()
266     {
267         children.clear();
268     }
269 
270     /***
271      * Checks if this node is an attribute node.
272      *
273      * @return a flag if this is an attribute node
274      */
275     public boolean isAttribute()
276     {
277         return attribute;
278     }
279 
280     /***
281      * Sets the attribute flag. Note: this method can only be called if the node
282      * is not already part of a node hierarchy.
283      *
284      * @param f the attribute flag
285      */
286     public void setAttribute(boolean f)
287     {
288         checkState();
289         attribute = f;
290     }
291 
292     /***
293      * Adds the specified attribute to this node.
294      *
295      * @param attr the attribute to be added
296      */
297     public void addAttribute(ConfigurationNode attr)
298     {
299         attributes.addNode(attr);
300         attr.setAttribute(true);
301         attr.setParentNode(this);
302     }
303 
304     /***
305      * Returns a list with the attributes of this node. This list contains
306      * <code>ConfigurationNode</code> objects, too.
307      *
308      * @return the attribute list, never <b>null </b>
309      */
310     public List getAttributes()
311     {
312         return attributes.getSubNodes();
313     }
314 
315     /***
316      * Returns the number of attributes contained in this node.
317      *
318      * @return the number of attributes
319      */
320     public int getAttributeCount()
321     {
322         return attributes.getSubNodes().size();
323     }
324 
325     /***
326      * Returns a list with all attributes of this node with the given name.
327      *
328      * @param name the attribute's name
329      * @return all attributes with this name
330      */
331     public List getAttributes(String name)
332     {
333         return attributes.getSubNodes(name);
334     }
335 
336     /***
337      * Returns the number of attributes of this node with the given name.
338      *
339      * @param name the name
340      * @return the number of attributes with this name
341      */
342     public int getAttributeCount(String name)
343     {
344         return getAttributes(name).size();
345     }
346 
347     /***
348      * Removes the specified attribute.
349      *
350      * @param node the attribute node to be removed
351      * @return a flag if the attribute could be removed
352      */
353     public boolean removeAttribute(ConfigurationNode node)
354     {
355         return attributes.removeNode(node);
356     }
357 
358     /***
359      * Removes all attributes with the specified name.
360      *
361      * @param name the name
362      * @return a flag if at least one attribute was removed
363      */
364     public boolean removeAttribute(String name)
365     {
366         return attributes.removeNodes(name);
367     }
368 
369     /***
370      * Returns the attribute with the given index.
371      *
372      * @param index the index (0-based)
373      * @return the attribute with this index
374      */
375     public ConfigurationNode getAttribute(int index)
376     {
377         return attributes.getNode(index);
378     }
379 
380     /***
381      * Removes all attributes of this node.
382      */
383     public void removeAttributes()
384     {
385         attributes.clear();
386     }
387 
388     /***
389      * Returns a flag if this node is defined. This means that the node contains
390      * some data.
391      *
392      * @return a flag whether this node is defined
393      */
394     public boolean isDefined()
395     {
396         return getValue() != null || getChildrenCount() > 0
397                 || getAttributeCount() > 0;
398     }
399 
400     /***
401      * Visits this node and all its sub nodes.
402      *
403      * @param visitor the visitor
404      */
405     public void visit(ConfigurationNodeVisitor visitor)
406     {
407         if (visitor == null)
408         {
409             throw new IllegalArgumentException("Visitor must not be null!");
410         }
411 
412         if (!visitor.terminate())
413         {
414             visitor.visitBeforeChildren(this);
415             children.visit(visitor);
416             attributes.visit(visitor);
417             visitor.visitAfterChildren(this);
418         } /* if */
419     }
420 
421     /***
422      * Creates a copy of this object. This is not a deep copy, the children are
423      * not cloned.
424      *
425      * @return a copy of this object
426      */
427     public Object clone()
428     {
429         try
430         {
431             DefaultConfigurationNode copy = (DefaultConfigurationNode) super
432                     .clone();
433             copy.initSubNodes();
434             return copy;
435         }
436         catch (CloneNotSupportedException cex)
437         {
438             return null; // should not happen
439         }
440     }
441 
442     /***
443      * Checks if a modification of this node is allowed. Some properties of a
444      * node must not be changed when the node has a parent. This method checks
445      * this and throws a runtime exception if necessary.
446      */
447     protected void checkState()
448     {
449         if (getParentNode() != null)
450         {
451             throw new IllegalStateException(
452                     "Node cannot be modified when added to a parent!");
453         } /* if */
454     }
455 
456     /***
457      * Creates a <code>SubNodes</code> instance that is used for storing
458      * either this node's children or attributes.
459      *
460      * @param attributes <b>true</b> if the returned instance is used for
461      * storing attributes, <b>false</b> for storing child nodes
462      * @return the <code>SubNodes</code> object to use
463      */
464     protected SubNodes createSubNodes(boolean attributes)
465     {
466         return new SubNodes();
467     }
468 
469     /***
470      * Deals with the reference when a node is removed. This method is called
471      * for each removed child node or attribute. It can be overloaded in sub
472      * classes, for which the reference has a concrete meaning and remove
473      * operations need some update actions. This default implementation is
474      * empty.
475      */
476     protected void removeReference()
477     {
478     }
479 
480     /***
481      * Helper method for initializing the sub nodes objects.
482      */
483     private void initSubNodes()
484     {
485         children = createSubNodes(false);
486         attributes = createSubNodes(true);
487     }
488 
489     /***
490      * An internally used helper class for managing a collection of sub nodes.
491      */
492     protected static class SubNodes
493     {
494         /*** Stores a list for the sub nodes. */
495         private List nodes;
496 
497         /*** Stores a map for accessing subnodes by name. */
498         private Map namedNodes;
499 
500         /***
501          * Adds a new sub node.
502          *
503          * @param node the node to add
504          */
505         public void addNode(ConfigurationNode node)
506         {
507             if (node == null || node.getName() == null)
508             {
509                 throw new IllegalArgumentException(
510                         "Node to add must have a defined name!");
511             }
512             node.setParentNode(null);  // reset, will later be set
513 
514             if (nodes == null)
515             {
516                 nodes = new ArrayList();
517                 namedNodes = new HashMap();
518             }
519 
520             nodes.add(node);
521             List lst = (List) namedNodes.get(node.getName());
522             if (lst == null)
523             {
524                 lst = new LinkedList();
525                 namedNodes.put(node.getName(), lst);
526             }
527             lst.add(node);
528         }
529 
530         /***
531          * Removes a sub node.
532          *
533          * @param node the node to remove
534          * @return a flag if the node could be removed
535          */
536         public boolean removeNode(ConfigurationNode node)
537         {
538             if (nodes != null && node != null && nodes.contains(node))
539             {
540                 detachNode(node);
541                 nodes.remove(node);
542 
543                 List lst = (List) namedNodes.get(node.getName());
544                 if (lst != null)
545                 {
546                     lst.remove(node);
547                     if (lst.isEmpty())
548                     {
549                         namedNodes.remove(node.getName());
550                     }
551                 }
552                 return true;
553             }
554 
555             else
556             {
557                 return false;
558             }
559         }
560 
561         /***
562          * Removes all sub nodes with the given name.
563          *
564          * @param name the name
565          * @return a flag if at least on sub node was removed
566          */
567         public boolean removeNodes(String name)
568         {
569             if (nodes != null && name != null)
570             {
571                 List lst = (List) namedNodes.remove(name);
572                 if (lst != null)
573                 {
574                     detachNodes(lst);
575                     nodes.removeAll(lst);
576                     return true;
577                 }
578             }
579             return false;
580         }
581 
582         /***
583          * Removes all sub nodes.
584          */
585         public void clear()
586         {
587             if (nodes != null)
588             {
589                 detachNodes(nodes);
590                 nodes = null;
591                 namedNodes = null;
592             }
593         }
594 
595         /***
596          * Returns the node with the given index. If this index cannot be found,
597          * an <code>IndexOutOfBoundException</code> exception will be thrown.
598          *
599          * @param index the index (0-based)
600          * @return the sub node at the specified index
601          */
602         public ConfigurationNode getNode(int index)
603         {
604             if (nodes == null)
605             {
606                 throw new IndexOutOfBoundsException("No sub nodes available!");
607             }
608             return (ConfigurationNode) nodes.get(index);
609         }
610 
611         /***
612          * Returns a list with all stored sub nodes. The return value is never
613          * <b>null</b>.
614          *
615          * @return a list with the sub nodes
616          */
617         public List getSubNodes()
618         {
619             return (nodes == null) ? Collections.EMPTY_LIST : Collections
620                     .unmodifiableList(nodes);
621         }
622 
623         /***
624          * Returns a list of the sub nodes with the given name. The return value
625          * is never <b>null</b>.
626          *
627          * @param name the name; if <b>null</b> is passed, all sub nodes will
628          * be returned
629          * @return all sub nodes with this name
630          */
631         public List getSubNodes(String name)
632         {
633             if (name == null)
634             {
635                 return getSubNodes();
636             }
637 
638             List result;
639             if (nodes == null)
640             {
641                 result = null;
642             }
643             else
644             {
645                 result = (List) namedNodes.get(name);
646             }
647 
648             return (result == null) ? Collections.EMPTY_LIST : Collections
649                     .unmodifiableList(result);
650         }
651 
652         /***
653          * Let the passed in visitor visit all sub nodes.
654          *
655          * @param visitor the visitor
656          */
657         public void visit(ConfigurationNodeVisitor visitor)
658         {
659             if (nodes != null)
660             {
661                 for (Iterator it = nodes.iterator(); it.hasNext()
662                         && !visitor.terminate();)
663                 {
664                     ((ConfigurationNode) it.next()).visit(visitor);
665                 }
666             }
667         }
668 
669         /***
670          * This method is called whenever a sub node is removed from this
671          * object. It ensures that the removed node's parent is reset and its
672          * <code>removeReference()</code> method gets called.
673          *
674          * @param subNode the node to be removed
675          */
676         protected void detachNode(ConfigurationNode subNode)
677         {
678             subNode.setParentNode(null);
679             if (subNode instanceof DefaultConfigurationNode)
680             {
681                 ((DefaultConfigurationNode) subNode).removeReference();
682             }
683         }
684 
685         /***
686          * Detaches a list of sub nodes. This method calls
687          * <code>detachNode()</code> for each node contained in the list.
688          *
689          * @param subNodes the list with nodes to be detached
690          */
691         protected void detachNodes(Collection subNodes)
692         {
693             for (Iterator it = subNodes.iterator(); it.hasNext();)
694             {
695                 detachNode((ConfigurationNode) it.next());
696             }
697         }
698     }
699 }