1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 }
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;
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 }
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);
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 }