001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.util;
051:
052: import org.apache.log4j.*;
053: import java.io.*;
054: import java.util.*;
055:
056: /** An instance of this class can represent a node in a tree.
057: * Each node will have a system generated unique identifier.
058: * It may have a name (which can be non-unique).
059: * It can be linked to a Parent node or be the Root of the tree.
060: * It can have child nodes.
061: * It will have a value Object.
062: * Additionally it can have a map of attributes.
063: */
064: public class Node {
065: private static Logger log = Logger.getLogger(Node.class);
066: private static long c_nextId = 0;
067:
068: /** A unique identifier for a node which is system-generated */
069: private String m_id = null;
070:
071: /** Name for a node. Multiple Nodes can share the same name. Can be null */
072: private String m_name = null;
073:
074: /** Value for a node. Can be null */
075: private Object m_value = null;
076:
077: /** Parent node. Can be null if no parent exists */
078: private Node m_parent = null;
079:
080: /** Root node. Will have the same value as m_id if the node is a root */
081: private Node m_root = null;
082:
083: /** This will maintain name-value pairs for the node */
084: private Map m_attributes = null;
085:
086: /** This will maintain the child nodes */
087: private Collection m_children = null;
088:
089: /** This will maintain the child nodes, grandchildren, grand-grand-children & so on...
090: * as id-node pairs in the root node only */
091: private Map m_family = null;
092:
093: /** A temporary field */
094: private transient Collection m_childrenCol = null;
095:
096: /** Creates new Node */
097: public Node() {
098: this (null);
099: }
100:
101: /** Creates new Node specifying a name.
102: * @param name The node name.
103: */
104: public Node(String name) {
105: m_id = Node.getNextId();
106: m_name = name;
107:
108: // treat the node as a ROOT initially
109: m_root = this ;
110: }
111:
112: //*** getters only ***
113: /** Returns the unique identifier of the node.
114: * @return the unique identifier of the node.
115: */
116: public String getId() {
117: return m_id;
118: }
119:
120: /** Returns the Parent node, or null if this node has no parent.
121: * @return the Parent node, or null if this node has no parent.
122: */
123: public Node getParent() {
124: return m_parent;
125: }
126:
127: /** Returns the root node of the tree to which this node belongs.
128: * @return the root node of the tree to which this node belongs.
129: */
130: public Node getRoot() {
131: return m_root;
132: }
133:
134: /** Returns a collection of child nodes.
135: * @return a collection of child nodes.
136: */
137: public Collection getChildren() {
138: if (m_children == null)
139: m_childrenCol = null;
140: else if (m_childrenCol == null)
141: m_childrenCol = new Node.Children();
142: return m_childrenCol;
143: }
144:
145: /** Returns the 1st child which matches the name.
146: * @param name The node name.
147: * @return the 1st child which matches the name.
148: */
149: public Node getChildByName(String name) {
150: Node node = null;
151: if (m_children != null && !m_children.isEmpty()) {
152: for (Iterator itr = m_children.iterator(); itr.hasNext();) {
153: node = (Node) itr.next();
154: if (node.getName() == null ? name == null : node
155: .getName().equals(name))
156: break;
157: else
158: node = null;
159: }
160: }
161: return node;
162: }
163:
164: /** Returns the child by id.
165: * @param id The node identifier.
166: * @return the child by id.
167: */
168: public Node getChildById(String id) {
169: Node node = getFromFamilyById(id);
170: if (node == null || m_children == null || m_children.isEmpty()
171: || !m_children.contains(node))
172: node = null;
173: return node;
174: }
175:
176: /** Returns a node from the family by id.
177: * @param id The node identifier.
178: * @return a node from the family by id.
179: */
180: public Node getFromFamilyById(String id) {
181: if (id != null && m_root != null && m_root.m_family != null)
182: return (Node) m_root.m_family.get(id);
183: else
184: return null;
185: }
186:
187: //*** getters & setters ***
188: /** Getter for property name.
189: * @return Value of property name.
190: */
191: public String getName() {
192: return m_name;
193: }
194:
195: /** Setter for property name.
196: * @param name New value of property name.
197: */
198: public void setName(String name) {
199: m_name = name;
200: }
201:
202: /** Getter for property value.
203: * @return Value of property value.
204: */
205: public Object getValue() {
206: return m_value;
207: }
208:
209: /** Setter for property value.
210: * @param value New value of property value.
211: */
212: public void setValue(Object value) {
213: m_value = value;
214: }
215:
216: /** Getter for property attributes.
217: * @return Value of property attributes.
218: */
219: public Map getAttributes() {
220: return m_attributes;
221: }
222:
223: /** Setter for property attributes.
224: * @param attributes New value of property attributes.
225: */
226: public void setAttributes(Map attributes) {
227: m_attributes = attributes;
228: }
229:
230: /** Returns the attribute for the specified key.
231: * @param key The attribute key.
232: * @return the attribute for the specified key.
233: */
234: public Object getAttribute(Object key) {
235: if (m_attributes != null)
236: return m_attributes.get(key);
237: else
238: return null;
239: }
240:
241: /** Adds an attribute. Will replace an existing attribute having the same key.
242: * @param key The attribute key.
243: * @param value The attribute value.
244: * @return previous value associated with specified key, or null if there was no attribute for key.
245: */
246: public Object setAttribute(Object key, Object value) {
247: if (m_attributes == null)
248: m_attributes = new ListMap();
249: return m_attributes.put(key, value);
250: }
251:
252: //*** Additional Methods ***
253:
254: /** Removes an attribute .
255: * @param key The attribute key.
256: * @return the attribute that was removed, or null if there was no such attribute.
257: */
258: public Object removeAttribute(Object key) {
259: if (m_attributes != null)
260: return m_attributes.remove(key);
261: else
262: return null;
263: }
264:
265: /** Returns true if the node is its own root.
266: * @return true if the node is its own root.
267: */
268: public boolean isRoot() {
269: return this == m_root;
270: }
271:
272: /** Returns true if the node has childen.
273: * @return true if the node has childen.
274: */
275: public boolean hasChildren() {
276: return m_children != null && !m_children.isEmpty();
277: }
278:
279: /** Returns true if the parent node has any more child nodes after the current node.
280: * @return true if the parent node has any more child nodes after the current node.
281: */
282: public boolean parentHasMoreChildren() {
283: boolean result = false;
284: Node parent = getParent();
285: if (parent != null) {
286: int i = ((List) parent.m_children).indexOf(this );
287: if (i < (parent.m_children.size() - 1))
288: result = true;
289: }
290: return result;
291: }
292:
293: /** Makes the node its own root. Will remove links from existing parent node, if any.
294: */
295: public void makeRoot() {
296: if (!isRoot()) {
297: // set the new root for this node as well all its children
298: setRoot(m_root, this );
299:
300: // remove this node from the old Parent node's childList
301: if (m_parent != null)
302: m_parent.removeFromChildren(this );
303:
304: // reset the parent
305: m_parent = null;
306: }
307: }
308:
309: /** Adds a child node. Will add a link both ways.
310: * @param node The child node.
311: */
312: public void addChild(Node node) {
313: if (node != null) {
314: Node oldParentNode = node.getParent();
315: Node oldRootNode = node.getRoot();
316:
317: // take care of the parent references
318: if (oldParentNode != null)
319: oldParentNode.removeFromChildren(node);
320: node.m_parent = this ;
321:
322: // reset the family, in case 'node' is a root
323: node.m_family = null;
324:
325: // add the 'node' to the children collection
326: addToChildren(node);
327:
328: // reset the root refernces
329: if (m_root != oldRootNode)
330: node.setRoot(oldRootNode, m_root);
331: }
332: }
333:
334: /** Removes a child node. Will add a link both ways.
335: * @param node The child node.
336: * @return true if the child node was removed.
337: */
338: public boolean removeChild(Node node) {
339: boolean result = false;
340: if (node != null) {
341: result = removeFromChildren(node);
342:
343: // just make the node a Root. This will remove all old links
344: node.makeRoot();
345: }
346: return result;
347: }
348:
349: /** Removes the 1st child that matches the name
350: * @param name The node name.
351: * @return true if the child node was removed.
352: */
353: public boolean removeChild(String name) {
354: boolean result = false;
355: Node node = getChildByName(name);
356: if (node != null)
357: result = removeChild(node);
358: return result;
359: }
360:
361: /** Remove all children.
362: * @return true if any child node was removed.
363: */
364: public boolean removeChildren() {
365: Collection col = getChildren();
366: if (col != null && !col.isEmpty()) {
367: int i = m_children.size();
368: for (Iterator itr = col.iterator(); itr.hasNext();) {
369: itr.next();
370: itr.remove();
371: }
372: return i != m_children.size();
373: } else
374: return false;
375: }
376:
377: /** A helper routine to print the contents of a node.
378: * @param writer The writer to which the node contents will be printed.
379: * @throws IOException if any I/O error occurs.
380: */
381: public void printNode(java.io.Writer writer)
382: throws java.io.IOException {
383: printNode(writer, null, " ");
384: }
385:
386: /** A helper routine to print the contents of a node.
387: * @param writer The writer to which the node contents will be printed.
388: * @param pad The pad string to be used.
389: * @param padIncrement The increment string to be appended to the pad string at each successive level of child nodes.
390: * @throws IOException if any I/O error occurs.
391: */
392: public void printNode(java.io.Writer writer, String pad,
393: String padIncrement) throws java.io.IOException {
394: pad = (pad == null ? "" : pad)
395: + (padIncrement == null ? "" : padIncrement);
396:
397: // print the main fields
398: writer.write(pad
399: + "Id="
400: + getId()
401: + ", Name="
402: + getName()
403: + ", Value="
404: + getValue()
405: + ", Root="
406: + (getRoot() == null ? "null" : getRoot().getId()
407: .toString())
408: + ", Parent="
409: + (getParent() == null ? "null" : getParent().getId()
410: .toString()));
411:
412: // add the attributes, if any
413: if (getAttributes() != null && !(getAttributes().isEmpty())) {
414: writer.write(", Attributes=");
415: for (Iterator itr = getAttributes().entrySet().iterator(); itr
416: .hasNext();) {
417: Map.Entry me = (Map.Entry) itr.next();
418: writer.write(me.getKey() + "=" + me.getValue()
419: + (itr.hasNext() ? ", " : ""));
420: }
421: }
422: writer.write('\n');
423:
424: // print the family, if any
425: if (m_family != null && !m_family.isEmpty()) {
426: writer.write(pad + "Family=");
427: for (Iterator itr = m_family.keySet().iterator(); itr
428: .hasNext();) {
429: writer.write(itr.next() + (itr.hasNext() ? ", " : ""));
430: }
431: writer.write('\n');
432: }
433:
434: // recursively print the children
435: if (getChildren() != null && !(getChildren().isEmpty())) {
436: for (Iterator itr = getChildren().iterator(); itr.hasNext();)
437: ((Node) itr.next())
438: .printNode(writer, pad, padIncrement);
439: }
440: }
441:
442: //*** Private Methods ***
443: private static synchronized String getNextId() {
444: return "" + ++c_nextId;
445: }
446:
447: private void setRoot(Node oldRootNode, Node newRootNode) {
448: // Reset the root node
449: m_root = newRootNode;
450:
451: // remove the reference to this node from the old root
452: if (oldRootNode != null)
453: oldRootNode.removeFromFamily(this );
454:
455: // add a reference to this node from the new root
456: if (!isRoot() && newRootNode != null)
457: newRootNode.addToFamily(this );
458:
459: // call the setRoot() method for each child recursively
460: if (m_children != null) {
461: for (Iterator itr = m_children.iterator(); itr.hasNext();) {
462: Node node = (Node) itr.next();
463: node.setRoot(oldRootNode, newRootNode);
464: }
465: }
466: }
467:
468: private void addToFamily(Node node) {
469: if (m_family == null)
470: m_family = new ListMap();
471: m_family.put(node.getId(), node);
472: }
473:
474: private boolean removeFromFamily(Node node) {
475: if (m_family != null) {
476: int i = m_family.size();
477: m_family.remove(node.getId());
478: return i != m_family.size();
479: } else
480: return false;
481: }
482:
483: private void addToChildren(Node node) {
484: if (m_children == null)
485: m_children = new ArrayList();
486: m_children.add(node);
487: }
488:
489: private boolean removeFromChildren(Node node) {
490: if (m_children != null)
491: return m_children.remove(node);
492: else
493: return false;
494: }
495:
496: private Iterator getIterator() {
497: return new Node.NodeIterator();
498: }
499:
500: // *** INNER CLASSES ***
501: private class NodeIterator implements Iterator {
502: private Iterator m_iterator = null;
503: private Object m_lastReturned = null;
504:
505: private NodeIterator() {
506: m_iterator = Node.this .m_children.iterator();
507: }
508:
509: /** Returns true if the iteration has more elements.
510: * @return true if the iteration has more elements.
511: */
512: public boolean hasNext() {
513: return m_iterator.hasNext();
514: }
515:
516: /** Returns the next element in the iteration.
517: * @return the next element in the iteration.
518: */
519: public Object next() {
520: m_lastReturned = m_iterator.next();
521: return m_lastReturned;
522: }
523:
524: /** Removes from the underlying collection the last element returned by the iterator.
525: */
526: public void remove() {
527: m_iterator.remove();
528: Node.this .removeChild((Node) m_lastReturned);
529: }
530: }
531:
532: private class Children extends AbstractCollection {
533: /** Returns the number of elements in this collection.
534: * @return the number of elements in this collection.
535: */
536: public int size() {
537: return Node.this .m_children.size();
538: }
539:
540: /** Returns true if this collection contains the specified element.
541: * @param o element whose presence in this collection is to be tested.
542: * @return true if this collection contains the specified element.
543: */
544: public boolean contains(Object o) {
545: return Node.this .m_children.contains(o);
546: }
547:
548: /** Removes all of the elements from this collection.
549: */
550: public void clear() {
551: Node.this .removeChildren();
552: }
553:
554: /** Returns an iterator over the elements in this collection.
555: * @return an iterator over the elements in this collection.
556: */
557: public Iterator iterator() {
558: return Node.this .getIterator();
559: }
560: }
561:
562: /** Test rig
563: * @param args The arguments. Not used.
564: */
565: public static void main(String[] args) {
566: try {
567: StringWriter sWriter = new StringWriter();
568:
569: Node node1 = new Node("Node1");
570: Node node2 = new Node("Node2");
571: Node node3 = new Node("Node3");
572: Node node4 = new Node("Node4");
573: node4.setValue("Value for node4");
574: node4.setAttribute("key1", "key1Value");
575: node4.setAttribute("key2", "key2Value");
576: node4.setAttribute("key3", "key3Value");
577:
578: node1.addChild(node2);
579: node1.addChild(node3);
580: node1.addChild(node4);
581:
582: Node node5 = new Node("Node5");
583: Node node6 = new Node("Node6");
584: Node node7 = new Node("Node7");
585: Node node8 = new Node("Node8");
586:
587: node2.addChild(node5);
588: node3.addChild(node6);
589: node6.addChild(node7);
590: node3.addChild(node8);
591:
592: node1.printNode(sWriter);
593: sWriter.write('\n');
594:
595: sWriter.write("Make Node3 a root\n");
596: node3.makeRoot();
597: sWriter.write("Contents of Node1\n");
598: node1.printNode(sWriter);
599: sWriter.write('\n');
600: sWriter.write("Contents of Node3\n");
601: node3.printNode(sWriter);
602: sWriter.write('\n');
603:
604: sWriter
605: .write("Add Node3 to Node2, Remove node5 from node2, Remove an attribute\n");
606: node2.addChild(node3);
607: node4.removeAttribute("key2");
608: node2.removeChild(node5);
609: sWriter.write("Contents of Node1\n");
610: node1.printNode(sWriter);
611: sWriter.write('\n');
612:
613: sWriter.write("Removing the children from node1\n");
614: node1.removeChildren();
615: sWriter.write("Contents of Node1\n");
616: node1.printNode(sWriter);
617: sWriter.write('\n');
618: sWriter.write("Contents of Node2\n");
619: node2.printNode(sWriter);
620: sWriter.write('\n');
621: sWriter.write("Contents of Node3\n");
622: node3.printNode(sWriter);
623: sWriter.write('\n');
624: System.out.println(sWriter.toString());
625:
626: } catch (Exception e) {
627: e.printStackTrace();
628: }
629: }
630: }
|