001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.configuration.tree;
018
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.Collections;
022 import java.util.HashMap;
023 import java.util.Iterator;
024 import java.util.LinkedList;
025 import java.util.List;
026 import java.util.Map;
027
028 import org.apache.commons.configuration.ConfigurationRuntimeException;
029
030 /**
031 * <p>
032 * A default implementation of the {@code ConfigurationNode} interface.
033 * </p>
034 *
035 * @since 1.3
036 * @author <a
037 * href="http://commons.apache.org/configuration/team-list.html">Commons
038 * Configuration team</a>
039 * @version $Id: DefaultConfigurationNode.java 1206314 2011-11-25 20:50:51Z oheger $
040 */
041 public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
042 {
043 /** Stores the children of this node. */
044 private SubNodes children;
045
046 /** Stores the attributes of this node. */
047 private SubNodes attributes;
048
049 /** Stores a reference to this node's parent. */
050 private ConfigurationNode parent;
051
052 /** Stores the value of this node. */
053 private Object value;
054
055 /** Stores the reference. */
056 private Object reference;
057
058 /** Stores the name of this node. */
059 private String name;
060
061 /** Stores a flag if this is an attribute. */
062 private boolean attribute;
063
064 /**
065 * Creates a new uninitialized instance of {@code DefaultConfigurationNode}.
066 */
067 public DefaultConfigurationNode()
068 {
069 this(null);
070 }
071
072 /**
073 * Creates a new instance of {@code DefaultConfigurationNode} and
074 * initializes it with the node name.
075 *
076 * @param name the name of this node
077 */
078 public DefaultConfigurationNode(String name)
079 {
080 this(name, null);
081 }
082
083 /**
084 * Creates a new instance of {@code DefaultConfigurationNode} and
085 * initializes it with the name and a value.
086 *
087 * @param name the node's name
088 * @param value the node's value
089 */
090 public DefaultConfigurationNode(String name, Object value)
091 {
092 setName(name);
093 setValue(value);
094 initSubNodes();
095 }
096
097 /**
098 * Returns the name of this node.
099 *
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 (ConfigurationNode) 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 }