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 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 }
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
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);
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 }