Source code: com/aendvari/common/osm/OsmNode.java
1 /*
2 * OsmNode.java
3 *
4 * Copyright (c) 2001, 2002 Aendvari, Ltd. All Rights Reserved.
5 *
6 * See the file LICENSE for terms of use.
7 *
8 */
9
10 package com.aendvari.common.osm;
11
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Map;
18
19 import com.aendvari.common.osm.OsmException;
20
21 /**
22 * <p>Represents a single node in the Object Space Model tree.
23 * An <code>OsmNode</code> has a parent and zero or more child nodes. A single <code>Object</code>
24 * value may be stored within an <code>OsmNode</code>.</p>
25 *
26 * @author Trevor Milne
27 *
28 */
29
30 public class OsmNode
31 {
32 /** The node name. */
33 protected String name;
34
35 /** The node value. */
36 protected Object value;
37
38 /** The parent node. */
39 protected OsmNode parent;
40
41 /** Children nodes. */
42 protected ArrayList children;
43
44 /** The child position in the parent. */
45 protected int position;
46
47 /** The attributes of this node. */
48 protected HashMap attributes;
49
50
51 /* Constructors. */
52
53
54 /**
55 * Constructs an <code>OsmNode</code> instance.
56 *
57 */
58
59 protected OsmNode()
60 {
61 name = null;
62 value = null;
63 parent = null;
64 children = new ArrayList();
65 position = -1;
66 attributes = new HashMap();
67 }
68
69 /**
70 * Constructs an <code>OsmNode</code> instance as a copy of the supplied node.
71 * Only the value name, value, and attributes are copied.
72 *
73 * @param setNode The node to copy.
74 *
75 */
76
77 protected OsmNode(OsmNode setNode)
78 {
79 this();
80
81 name = setNode.name;
82 value = setNode.value;
83 attributes = new HashMap(setNode.attributes);
84 }
85
86 /**
87 * Constructs an <code>OsmNode</code> instance using the supplied value.
88 *
89 * @param setName The name of the new node.
90 * @param setValue The value of the new node.
91 *
92 */
93
94 protected OsmNode(String setName, Object setValue)
95 {
96 this();
97
98 name = setName;
99 value = setValue;
100 }
101
102
103 /* Object value. */
104
105
106 /**
107 * Returns the value of this node.
108 *
109 * @return The <code>Object</code> held within this node.
110 *
111 */
112
113 public Object getNodeValue()
114 {
115 return value;
116 }
117
118 /**
119 * Returns the value of this node. If an object is not already assigned, then
120 * an instance of the specified class is created and stored.
121 *
122 * @param type <code>Class</code> of an object to created if one is not found.
123 *
124 * @return The <code>Object</code> held at the path.
125 *
126 * @exception OsmException The operation failed.
127 *
128 */
129
130 public Object getNodeValue(Class type) throws OsmException
131 {
132 // create the object if one does not exist
133 if (value == null)
134 {
135 try
136 {
137 value = type.newInstance();
138 }
139 catch (java.lang.InstantiationException exception)
140 {
141 throw new OsmException(OsmException.Code.ValueNotCreated, exception);
142 }
143 catch (java.lang.IllegalAccessException exception)
144 {
145 throw new OsmException(OsmException.Code.ValueNotCreated, exception);
146 }
147 }
148
149 return value;
150 }
151
152 /**
153 * Sets the value of this node.
154 *
155 * @param object The <code>Object</code> to place in this node.
156 *
157 */
158
159 public void setNodeValue(Object object)
160 {
161 value = object;
162 }
163
164
165 /* Attributes */
166
167
168 /**
169 * Returns the value of the specified attribute.
170 *
171 * @return The <code>Object</code> of the attibute.
172 *
173 */
174
175 public Object getAttribute(String name)
176 {
177 return attributes.get(name);
178 }
179
180 /**
181 * Returns the value of the specified attribute. If an object is not already set,
182 * then an instance of the specified class is created and stored.
183 *
184 * @param type <code>Class</code> of an object to created if one is not found.
185 *
186 * @return The <code>Object</code> held in the attribute specified.
187 *
188 * @exception OsmException The operation failed.
189 *
190 */
191
192 public Object getAttribute(Class type) throws OsmException
193 {
194 // create the object if one does not exist
195 if (value == null)
196 {
197 try
198 {
199 value = type.newInstance();
200 }
201 catch (java.lang.InstantiationException exception)
202 {
203 throw new OsmException(OsmException.Code.ValueNotCreated, exception);
204 }
205 catch (java.lang.IllegalAccessException exception)
206 {
207 throw new OsmException(OsmException.Code.ValueNotCreated, exception);
208 }
209 }
210
211 return value;
212 }
213
214 /**
215 * Returns the attributes of this node. Modifications to this <code>Map</code>
216 * do not affect the node's attributes.
217 *
218 * @return A <code>Map</code> of all attibutes.
219 *
220 */
221
222 public Map getAttributes()
223 {
224 return new HashMap(attributes);
225 }
226
227 /**
228 * Sets the value of the specified attribute.
229 *
230 * @param name The name of the attibute.
231 * @param value The <code>Object</code> to set as the attribute value.
232 *
233 */
234
235 public void setAttribute(String name, Object value)
236 {
237 attributes.put(name, value);
238 }
239
240 /**
241 * Returns whether this node has any attributes.
242 *
243 * @return True if this node has attributes.
244 *
245 */
246
247 public boolean hasAttributes()
248 {
249 return (attributes.size() > 0);
250 }
251
252
253 /* Accessors. */
254
255
256 /**
257 * Returns the name of this node.
258 *
259 * @return The name of this node.
260 *
261 */
262
263 public String getNodeName()
264 {
265 return name;
266 }
267
268 /**
269 * Returns a string representation of the node's position in the hierarchy.
270 *
271 * @return The position of this node in the hierarchy.
272 *
273 */
274
275 public String getNodePath()
276 {
277 LinkedList nodes = new LinkedList();
278
279 // traverse node tree upwards, and collect the nodes
280 OsmNode node = this;
281
282 do
283 {
284 nodes.addFirst(node);
285 node = node.getParentNode();
286 }
287 while (node != null);
288
289 // traverse back down tree, and create topic path
290 Iterator nodeIterator = nodes.iterator();
291 StringBuffer path = new StringBuffer();
292
293 // skip the root node
294 nodeIterator.next();
295
296 while (nodeIterator.hasNext())
297 {
298 // get the node at this level
299 node = (OsmNode)nodeIterator.next();
300
301 path.append('/');
302 path.append(node.getNodeName());
303 }
304
305 return path.toString();
306 }
307
308 /**
309 * Returns the OSM to which this node belongs. This is the OSM object used to
310 * create new nodes.
311 *
312 * @return The {@link Osm} of which this node belongs.
313 *
314 */
315
316 public Osm getOwnerOsm()
317 {
318 // traverse node tree upwards, and collect the nodes
319 OsmNode node = this;
320 OsmNode rootNode = this;
321
322 while (node != null)
323 {
324 node = node.getParentNode();
325
326 if (node != null)
327 {
328 rootNode = node;
329 }
330 }
331
332 // the root OsmNode is an instance of Osm
333
334 return (Osm)rootNode;
335 }
336
337
338 /* Node access. */
339
340
341 /**
342 * Returns a duplicate of this node. The duplicate node has no parent.
343 * If a shallow copy is performed, only the value name, value, and attributes are copied.
344 * If a deep copy is performed, a shallow copy of the node is made, then deep copies of
345 * each child of the node is made.
346 * Any objects stored as values or attributes are not copied, even if a deep copy is
347 * being performed.
348 *
349 * @param deep True to perform a deep copy, false to perform a shallow copy.
350 *
351 * @return The copied {@link OsmNode}.
352 *
353 */
354
355 public OsmNode cloneNode(boolean deep)
356 {
357 // make a shallow copy of this node
358 OsmNode copy = new OsmNode(this);
359
360 // perform a deep copy if requested
361 if (deep)
362 {
363 Iterator childIterator = children.iterator();
364
365 while (childIterator.hasNext())
366 {
367 // get child
368 OsmNode child = (OsmNode)childIterator.next();
369
370 // make deep copy of child
371 OsmNode childCopy = child.cloneNode(true);
372
373 // add copy as child of clone
374 copy.appendChild(childCopy);
375 }
376 }
377
378 return copy;
379 }
380
381 /**
382 * The parent of this node. All nodes, exception the root ({@link Osm})
383 * node may have a parent. If a node has just been created and not yet added to the tree, or if
384 * it has been removed from the tree, this is null.
385 *
386 * @return The parent of this node.
387 *
388 */
389
390 public OsmNode getParentNode()
391 {
392 return parent;
393 }
394
395 /**
396 * Returns whether this node has any children.
397 *
398 * @return True if this node has children.
399 *
400 */
401
402 public boolean hasChildNodes()
403 {
404 return (children.size() > 0);
405 }
406
407 /**
408 * The first child of this node. If there is no such node, this returns null.
409 *
410 * @return The first child node.
411 *
412 */
413
414 public OsmNode getFirstChild()
415 {
416 // make sure there are children
417 if (hasChildNodes())
418 {
419 return (OsmNode)children.get(0);
420 }
421
422 return null;
423 }
424
425 /**
426 * The last child of this node. If there is no such node, this returns null.
427 *
428 * @return The last child node.
429 *
430 */
431
432 public OsmNode getLastChild()
433 {
434 // make sure there are children
435 if (hasChildNodes())
436 {
437 return (OsmNode)children.get(children.size() - 1);
438 }
439
440 return null;
441 }
442
443 /**
444 * The node immediately preceding this node. If there is no such node,
445 * this returns null.
446 *
447 * @return The preceding sibling node.
448 * <code>null</code> if nothing found.
449 *
450 */
451
452 public OsmNode getPreviousSibling()
453 {
454 // check if first node
455 if (position == 0) return null;
456
457 // return previous node in list
458 return (OsmNode)parent.children.get(position - 1);
459 }
460
461 /**
462 * The node immediately following this node. If there is no such node,
463 * this returns null.
464 *
465 * @return The following sibling node.
466 *
467 */
468
469 public OsmNode getNextSibling()
470 {
471 // get next index
472 int next = (position + 1);
473
474 // check if last node
475 if (next == parent.children.size()) return null;
476
477 // return next node in list
478 return (OsmNode)parent.children.get(next);
479 }
480
481 /**
482 * Returns a <code>List</code> containing all the children of this node.
483 * Modifying this collection does not affect this node's children.
484 *
485 * @return A <code>List</code> of {@link OsmNode OsmNodes}.
486 *
487 */
488
489 public List getChildNodes()
490 {
491 return new ArrayList(children);
492 }
493
494 /**
495 * Removes all children of this node.
496 *
497 */
498
499 public void removeChildNodes()
500 {
501 children.clear();
502 }
503
504 /**
505 * Returns a <code>List</code> containing all the children of this node.
506 * This recursively collects all the children of each child node.
507 *
508 * @return A <code>List</code> of {@link OsmNode OsmNodes}.
509 *
510 */
511
512 public List getAllChildNodes()
513 {
514 ArrayList nodes = new ArrayList();
515
516 getAllChildNodes(children, nodes);
517
518 return nodes;
519 }
520
521 /**
522 * Helper method for #getAllChildNodes().
523 * This recursively collects all the children of each child node.
524 *
525 * @param children A <code>List</code> of children to examine.
526 * @param collectedNodes The collected children.
527 *
528 */
529
530 protected void getAllChildNodes(List children, List collectedNodes)
531 {
532 // collect child nodes
533 Iterator childIterator = children.iterator();
534
535 while (childIterator.hasNext())
536 {
537 OsmNode childNode = (OsmNode)childIterator.next();
538
539 // add this child to the results
540 collectedNodes.add(childNode);
541
542 // recursively examine this child
543 if (childNode.children.size() > 0)
544 {
545 getAllChildNodes(childNode.children, collectedNodes);
546 }
547 }
548 }
549
550
551 /* Node manipulation. */
552
553
554 /**
555 * Adds the node <code>newChild</code> to the end of the list of children of this node.
556 * If <code>newChild</code> is already in the tree, it is first removed.
557 *
558 * @param newChild The node to add.
559 *
560 * @return The node added.
561 *
562 */
563
564 public OsmNode appendChild(OsmNode newChild)
565 {
566 // remove child node from current position (if there)
567 children.remove(newChild);
568
569 // set parent
570 newChild.parent = this;
571
572 // index of child is current collection size
573 newChild.position = children.size();
574
575 // add child node
576 children.add(newChild);
577
578 return newChild;
579 }
580
581 /**
582 * Inserts the node <code>newChild</code> before the existing child node <code>refChild</code>.
583 * If <code>refChild</code> is null, <code>newChild</code> is inserted at the end of the list
584 * of children. If the <code>newChild</code> is already in the tree, it is first removed.
585 *
586 * @param newChild The node to insert.
587 * @param refChild The reference node.
588 *
589 * @return The node inserted.
590 *
591 */
592
593 public OsmNode insertBefore(OsmNode newChild, OsmNode refChild)
594 {
595 // remove child node from current position (if there)
596 children.remove(newChild);
597
598 // get position of reference node
599 int refPosition = refChild.position;
600
601 // set parent
602 newChild.parent = this;
603
604 // set position to that of refChild
605 newChild.position = refPosition;
606
607 // insert new node into that position
608 children.add(refPosition, newChild);
609
610 // resequence following siblings
611 int index;
612
613 for (index = (refPosition + 1); index < children.size(); index++)
614 {
615 OsmNode node = (OsmNode)children.get(index);
616 node.position = index;
617 }
618
619 return newChild;
620 }
621
622 /**
623 * Replaces the child node <code>oldChild</code> with <code>newChild</code>, and returns
624 * the <code>oldChild</code> node. If the <code>newChild</code> is already in the tree,
625 * it is first removed.
626 *
627 * @param newChild The node to replace.
628 * @param oldChild The node being replaced.
629 *
630 * @return The node replaced.
631 *
632 */
633
634 public OsmNode replaceChild(OsmNode newChild, OsmNode oldChild)
635 {
636 // remove new child node from current position (if there)
637 children.remove(newChild);
638
639 // set parent
640 newChild.parent = this;
641
642 // set the position to that of oldChild
643 newChild.position = oldChild.position;
644
645 // erase parent
646 oldChild.parent = null;
647
648 // replace the node at the position of oldChild
649 children.set(oldChild.position, newChild);
650
651 return newChild;
652 }
653
654 /**
655 * Removes the child node indicated by <code>oldChild</code> from the list of children,
656 * and returns it.
657 *
658 * @param oldChild The node to remove.
659 *
660 * @return The removed node.
661 *
662 */
663
664 public OsmNode removeChild(OsmNode oldChild)
665 {
666 // erase parent
667 oldChild.parent = null;
668
669 // remove the node at the position of oldChild
670 children.remove(oldChild.position);
671
672 // resequence following siblings
673 int index;
674
675 for (index = oldChild.position; index < children.size(); index++)
676 {
677 OsmNode node = (OsmNode)children.get(index);
678 node.position = index;
679 }
680
681 return oldChild;
682 }
683
684 /**
685 * Returns a string representation of the node.
686 *
687 */
688
689 public String toString()
690 {
691 if (parent == null)
692 return (super.toString() + "; name=" + name + "; value=" + value + "; parent=null; children=" + children.size());
693 else
694 return (super.toString() + "; name=" + name + "; value=" + value + "; parent=" + parent.name + "; children=" + children.size());
695 }
696 }
697