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: /**
029: * <p>
030: * A default implementation of the <code>ConfigurationNode</code> interface.
031: * </p>
032: *
033: * @since 1.3
034: * @author Oliver Heger
035: */
036: public class DefaultConfigurationNode implements ConfigurationNode,
037: Cloneable {
038: /** Stores the children of this node. */
039: private SubNodes children;
040:
041: /** Stores the attributes of this node. */
042: private SubNodes attributes;
043:
044: /** Stores a reference to this node's parent. */
045: private ConfigurationNode parent;
046:
047: /** Stores the value of this node. */
048: private Object value;
049:
050: /** Stores the reference. */
051: private Object reference;
052:
053: /** Stores the name of this node. */
054: private String name;
055:
056: /** Stores a flag if this is an attribute. */
057: private boolean attribute;
058:
059: /**
060: * Creates a new uninitialized instance of
061: * <code>DefaultConfigurationNode</code>.
062: */
063: public DefaultConfigurationNode() {
064: this (null);
065: }
066:
067: /**
068: * Creates a new instance of <code>DefaultConfigurationNode</code> and
069: * initializes it with the node name.
070: *
071: * @param name the name of this node
072: */
073: public DefaultConfigurationNode(String name) {
074: this (name, null);
075: }
076:
077: /**
078: * Creates a new instance of <code>DefaultConfigurationNode</code> and
079: * initializes it with the name and a value.
080: *
081: * @param name the node's name
082: * @param value the node's value
083: */
084: public DefaultConfigurationNode(String name, Object value) {
085: setName(name);
086: setValue(value);
087: initSubNodes();
088: }
089:
090: /**
091: * Returns the name of this node.
092: *
093: * @return the name of this node
094: */
095: public String getName() {
096: return name;
097: }
098:
099: /**
100: * Sets the name of this node.
101: *
102: * @param name the new name
103: */
104: public void setName(String name) {
105: checkState();
106: this .name = name;
107: }
108:
109: /**
110: * Returns the value of this node.
111: *
112: * @return the value of this node
113: */
114: public Object getValue() {
115: return value;
116: }
117:
118: /**
119: * Sets the value of this node.
120: *
121: * @param val the value of this node
122: */
123: public void setValue(Object val) {
124: value = val;
125: }
126:
127: /**
128: * Returns the reference.
129: *
130: * @return the reference
131: */
132: public Object getReference() {
133: return reference;
134: }
135:
136: /**
137: * Sets the reference.
138: *
139: * @param reference the reference object
140: */
141: public void setReference(Object reference) {
142: this .reference = reference;
143: }
144:
145: /**
146: * Returns a reference to this node's parent.
147: *
148: * @return the parent node or <b>null </b> if this is the root
149: */
150: public ConfigurationNode getParentNode() {
151: return parent;
152: }
153:
154: /**
155: * Sets the parent of this node.
156: *
157: * @param parent the parent of this node
158: */
159: public void setParentNode(ConfigurationNode parent) {
160: this .parent = parent;
161: }
162:
163: /**
164: * Adds a new child to this node.
165: *
166: * @param child the new child
167: */
168: public void addChild(ConfigurationNode child) {
169: children.addNode(child);
170: child.setAttribute(false);
171: child.setParentNode(this );
172: }
173:
174: /**
175: * Returns a list with all children of this node.
176: *
177: * @return a list with all child nodes
178: */
179: public List getChildren() {
180: return children.getSubNodes();
181: }
182:
183: /**
184: * Returns the number of all children of this node.
185: *
186: * @return the number of all children
187: */
188: public int getChildrenCount() {
189: return children.getSubNodes().size();
190: }
191:
192: /**
193: * Returns a list of all children with the given name.
194: *
195: * @param name the name; can be <b>null </b>, then all children are returned
196: * @return a list of all children with the given name
197: */
198: public List getChildren(String name) {
199: return children.getSubNodes(name);
200: }
201:
202: /**
203: * Returns the number of children with the given name.
204: *
205: * @param name the name; can be <b>null </b>, then the number of all
206: * children is returned
207: * @return the number of child nodes with this name
208: */
209: public int getChildrenCount(String name) {
210: return children.getSubNodes(name).size();
211: }
212:
213: /**
214: * Returns the child node with the given index.
215: *
216: * @param index the index (0-based)
217: * @return the child with this index
218: */
219: public ConfigurationNode getChild(int index) {
220: return children.getNode(index);
221: }
222:
223: /**
224: * Removes the specified child node from this node.
225: *
226: * @param child the node to be removed
227: * @return a flag if a node was removed
228: */
229: public boolean removeChild(ConfigurationNode child) {
230: return children.removeNode(child);
231: }
232:
233: /**
234: * Removes all children with the given name.
235: *
236: * @param childName the name of the children to be removed
237: * @return a flag if at least one child node was removed
238: */
239: public boolean removeChild(String childName) {
240: return children.removeNodes(childName);
241: }
242:
243: /**
244: * Removes all child nodes of this node.
245: */
246: public void removeChildren() {
247: children.clear();
248: }
249:
250: /**
251: * Checks if this node is an attribute node.
252: *
253: * @return a flag if this is an attribute node
254: */
255: public boolean isAttribute() {
256: return attribute;
257: }
258:
259: /**
260: * Sets the attribute flag. Note: this method can only be called if the node
261: * is not already part of a node hierarchy.
262: *
263: * @param f the attribute flag
264: */
265: public void setAttribute(boolean f) {
266: checkState();
267: attribute = f;
268: }
269:
270: /**
271: * Adds the specified attribute to this node.
272: *
273: * @param attr the attribute to be added
274: */
275: public void addAttribute(ConfigurationNode attr) {
276: attributes.addNode(attr);
277: attr.setAttribute(true);
278: attr.setParentNode(this );
279: }
280:
281: /**
282: * Returns a list with the attributes of this node. This list contains
283: * <code>ConfigurationNode</code> objects, too.
284: *
285: * @return the attribute list, never <b>null </b>
286: */
287: public List getAttributes() {
288: return attributes.getSubNodes();
289: }
290:
291: /**
292: * Returns the number of attributes contained in this node.
293: *
294: * @return the number of attributes
295: */
296: public int getAttributeCount() {
297: return attributes.getSubNodes().size();
298: }
299:
300: /**
301: * Returns a list with all attributes of this node with the given name.
302: *
303: * @param name the attribute's name
304: * @return all attributes with this name
305: */
306: public List getAttributes(String name) {
307: return attributes.getSubNodes(name);
308: }
309:
310: /**
311: * Returns the number of attributes of this node with the given name.
312: *
313: * @param name the name
314: * @return the number of attributes with this name
315: */
316: public int getAttributeCount(String name) {
317: return getAttributes(name).size();
318: }
319:
320: /**
321: * Removes the specified attribute.
322: *
323: * @param node the attribute node to be removed
324: * @return a flag if the attribute could be removed
325: */
326: public boolean removeAttribute(ConfigurationNode node) {
327: return attributes.removeNode(node);
328: }
329:
330: /**
331: * Removes all attributes with the specified name.
332: *
333: * @param name the name
334: * @return a flag if at least one attribute was removed
335: */
336: public boolean removeAttribute(String name) {
337: return attributes.removeNodes(name);
338: }
339:
340: /**
341: * Returns the attribute with the given index.
342: *
343: * @param index the index (0-based)
344: * @return the attribute with this index
345: */
346: public ConfigurationNode getAttribute(int index) {
347: return attributes.getNode(index);
348: }
349:
350: /**
351: * Removes all attributes of this node.
352: */
353: public void removeAttributes() {
354: attributes.clear();
355: }
356:
357: /**
358: * Returns a flag if this node is defined. This means that the node contains
359: * some data.
360: *
361: * @return a flag whether this node is defined
362: */
363: public boolean isDefined() {
364: return getValue() != null || getChildrenCount() > 0
365: || getAttributeCount() > 0;
366: }
367:
368: /**
369: * Visits this node and all its sub nodes.
370: *
371: * @param visitor the visitor
372: */
373: public void visit(ConfigurationNodeVisitor visitor) {
374: if (visitor == null) {
375: throw new IllegalArgumentException(
376: "Visitor must not be null!");
377: }
378:
379: if (!visitor.terminate()) {
380: visitor.visitBeforeChildren(this );
381: children.visit(visitor);
382: attributes.visit(visitor);
383: visitor.visitAfterChildren(this );
384: } /* if */
385: }
386:
387: /**
388: * Creates a copy of this object. This is not a deep copy, the children are
389: * not cloned.
390: *
391: * @return a copy of this object
392: */
393: public Object clone() {
394: try {
395: DefaultConfigurationNode copy = (DefaultConfigurationNode) super
396: .clone();
397: copy.initSubNodes();
398: return copy;
399: } catch (CloneNotSupportedException cex) {
400: return null; // should not happen
401: }
402: }
403:
404: /**
405: * Checks if a modification of this node is allowed. Some properties of a
406: * node must not be changed when the node has a parent. This method checks
407: * this and throws a runtime exception if necessary.
408: */
409: protected void checkState() {
410: if (getParentNode() != null) {
411: throw new IllegalStateException(
412: "Node cannot be modified when added to a parent!");
413: } /* if */
414: }
415:
416: /**
417: * Creates a <code>SubNodes</code> instance that is used for storing
418: * either this node's children or attributes.
419: *
420: * @param attributes <b>true</b> if the returned instance is used for
421: * storing attributes, <b>false</b> for storing child nodes
422: * @return the <code>SubNodes</code> object to use
423: */
424: protected SubNodes createSubNodes(boolean attributes) {
425: return new SubNodes();
426: }
427:
428: /**
429: * Deals with the reference when a node is removed. This method is called
430: * for each removed child node or attribute. It can be overloaded in sub
431: * classes, for which the reference has a concrete meaning and remove
432: * operations need some update actions. This default implementation is
433: * empty.
434: */
435: protected void removeReference() {
436: }
437:
438: /**
439: * Helper method for initializing the sub nodes objects.
440: */
441: private void initSubNodes() {
442: children = createSubNodes(false);
443: attributes = createSubNodes(true);
444: }
445:
446: /**
447: * An internally used helper class for managing a collection of sub nodes.
448: */
449: protected static class SubNodes {
450: /** Stores a list for the sub nodes. */
451: private List nodes;
452:
453: /** Stores a map for accessing subnodes by name. */
454: private Map namedNodes;
455:
456: /**
457: * Adds a new sub node.
458: *
459: * @param node the node to add
460: */
461: public void addNode(ConfigurationNode node) {
462: if (node == null || node.getName() == null) {
463: throw new IllegalArgumentException(
464: "Node to add must have a defined name!");
465: }
466: node.setParentNode(null); // reset, will later be set
467:
468: if (nodes == null) {
469: nodes = new ArrayList();
470: namedNodes = new HashMap();
471: }
472:
473: nodes.add(node);
474: List lst = (List) namedNodes.get(node.getName());
475: if (lst == null) {
476: lst = new LinkedList();
477: namedNodes.put(node.getName(), lst);
478: }
479: lst.add(node);
480: }
481:
482: /**
483: * Removes a sub node.
484: *
485: * @param node the node to remove
486: * @return a flag if the node could be removed
487: */
488: public boolean removeNode(ConfigurationNode node) {
489: if (nodes != null && node != null && nodes.contains(node)) {
490: detachNode(node);
491: nodes.remove(node);
492:
493: List lst = (List) namedNodes.get(node.getName());
494: if (lst != null) {
495: lst.remove(node);
496: if (lst.isEmpty()) {
497: namedNodes.remove(node.getName());
498: }
499: }
500: return true;
501: }
502:
503: else {
504: return false;
505: }
506: }
507:
508: /**
509: * Removes all sub nodes with the given name.
510: *
511: * @param name the name
512: * @return a flag if at least on sub node was removed
513: */
514: public boolean removeNodes(String name) {
515: if (nodes != null && name != null) {
516: List lst = (List) namedNodes.remove(name);
517: if (lst != null) {
518: detachNodes(lst);
519: nodes.removeAll(lst);
520: return true;
521: }
522: }
523: return false;
524: }
525:
526: /**
527: * Removes all sub nodes.
528: */
529: public void clear() {
530: if (nodes != null) {
531: detachNodes(nodes);
532: nodes = null;
533: namedNodes = null;
534: }
535: }
536:
537: /**
538: * Returns the node with the given index. If this index cannot be found,
539: * an <code>IndexOutOfBoundException</code> exception will be thrown.
540: *
541: * @param index the index (0-based)
542: * @return the sub node at the specified index
543: */
544: public ConfigurationNode getNode(int index) {
545: if (nodes == null) {
546: throw new IndexOutOfBoundsException(
547: "No sub nodes available!");
548: }
549: return (ConfigurationNode) nodes.get(index);
550: }
551:
552: /**
553: * Returns a list with all stored sub nodes. The return value is never
554: * <b>null</b>.
555: *
556: * @return a list with the sub nodes
557: */
558: public List getSubNodes() {
559: return (nodes == null) ? Collections.EMPTY_LIST
560: : Collections.unmodifiableList(nodes);
561: }
562:
563: /**
564: * Returns a list of the sub nodes with the given name. The return value
565: * is never <b>null</b>.
566: *
567: * @param name the name; if <b>null</b> is passed, all sub nodes will
568: * be returned
569: * @return all sub nodes with this name
570: */
571: public List getSubNodes(String name) {
572: if (name == null) {
573: return getSubNodes();
574: }
575:
576: List result;
577: if (nodes == null) {
578: result = null;
579: } else {
580: result = (List) namedNodes.get(name);
581: }
582:
583: return (result == null) ? Collections.EMPTY_LIST
584: : Collections.unmodifiableList(result);
585: }
586:
587: /**
588: * Let the passed in visitor visit all sub nodes.
589: *
590: * @param visitor the visitor
591: */
592: public void visit(ConfigurationNodeVisitor visitor) {
593: if (nodes != null) {
594: for (Iterator it = nodes.iterator(); it.hasNext()
595: && !visitor.terminate();) {
596: ((ConfigurationNode) it.next()).visit(visitor);
597: }
598: }
599: }
600:
601: /**
602: * This method is called whenever a sub node is removed from this
603: * object. It ensures that the removed node's parent is reset and its
604: * <code>removeReference()</code> method gets called.
605: *
606: * @param subNode the node to be removed
607: */
608: protected void detachNode(ConfigurationNode subNode) {
609: subNode.setParentNode(null);
610: if (subNode instanceof DefaultConfigurationNode) {
611: ((DefaultConfigurationNode) subNode).removeReference();
612: }
613: }
614:
615: /**
616: * Detaches a list of sub nodes. This method calls
617: * <code>detachNode()</code> for each node contained in the list.
618: *
619: * @param subNodes the list with nodes to be detached
620: */
621: protected void detachNodes(Collection subNodes) {
622: for (Iterator it = subNodes.iterator(); it.hasNext();) {
623: detachNode((ConfigurationNode) it.next());
624: }
625: }
626: }
627: }
|