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} interface.
33   * </p>
34   *
35   * @since 1.3
36   * @author <a
37   * href="http://commons.apache.org/configuration/team-list.html">Commons
38   * Configuration team</a>
39   * @version $Id: DefaultConfigurationNode.java 1301991 2012-03-17 20:18:02Z sebb $
40   */
41  public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
42  {
43      /** Stores the children of this node. */
44      private SubNodes children;
45  
46      /** Stores the attributes of this node. */
47      private SubNodes attributes;
48  
49      /** Stores a reference to this node's parent. */
50      private ConfigurationNode parent;
51  
52      /** Stores the value of this node. */
53      private Object value;
54  
55      /** Stores the reference. */
56      private Object reference;
57  
58      /** Stores the name of this node. */
59      private String name;
60  
61      /** Stores a flag if this is an attribute. */
62      private boolean attribute;
63  
64      /**
65       * Creates a new uninitialized instance of {@code DefaultConfigurationNode}.
66       */
67      public DefaultConfigurationNode()
68      {
69          this(null);
70      }
71  
72      /**
73       * Creates a new instance of {@code DefaultConfigurationNode} and
74       * initializes it with the node name.
75       *
76       * @param name the name of this node
77       */
78      public DefaultConfigurationNode(String name)
79      {
80          this(name, null);
81      }
82  
83      /**
84       * Creates a new instance of {@code DefaultConfigurationNode} and
85       * initializes it with the name and a value.
86       *
87       * @param name the node's name
88       * @param value the node's value
89       */
90      public DefaultConfigurationNode(String name, Object value)
91      {
92          setName(name);
93          setValue(value);
94          initSubNodes();
95      }
96  
97      /**
98       * Returns the name of this node.
99       *
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 }