001: /*
002: $Id: Node.java 4201 2006-11-05 10:23:50Z paulk $
003:
004: Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005:
006: Redistribution and use of this software and associated documentation
007: ("Software"), with or without modification, are permitted provided
008: that the following conditions are met:
009:
010: 1. Redistributions of source code must retain copyright
011: statements and notices. Redistributions must also contain a
012: copy of this document.
013:
014: 2. Redistributions in binary form must reproduce the
015: above copyright notice, this list of conditions and the
016: following disclaimer in the documentation and/or other
017: materials provided with the distribution.
018:
019: 3. The name "groovy" must not be used to endorse or promote
020: products derived from this Software without prior written
021: permission of The Codehaus. For written permission,
022: please contact info@codehaus.org.
023:
024: 4. Products derived from this Software may not be called "groovy"
025: nor may "groovy" appear in their names without prior written
026: permission of The Codehaus. "groovy" is a registered
027: trademark of The Codehaus.
028:
029: 5. Due credit should be given to The Codehaus -
030: http://groovy.codehaus.org/
031:
032: THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033: ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034: NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036: THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043: OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: */
046: package groovy.util;
047:
048: import org.codehaus.groovy.runtime.InvokerHelper;
049:
050: import groovy.xml.QName;
051:
052: import java.io.PrintWriter;
053: import java.util.Collection;
054: import java.util.Collections;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.Map;
058:
059: /**
060: * Represents an arbitrary tree node which can be used for structured metadata or any arbitrary XML-like tree.
061: * A node can have a name, a value and an optional Map of attributes.
062: * Typically the name is a String and a value is either a String or a List of other Nodes,
063: * though the types are extensible to provide a flexible structure, e.g. you could use a
064: * QName as the name which includes a namespace URI and a local name. Or a JMX ObjectName etc.
065: * So this class can represent metadata like {foo a=1 b="abc"} or nested metadata like {foo a=1 b="123" { bar x=12 text="hello" }}
066: *
067: * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
068: * @version $Revision: 4201 $
069: */
070: public class Node implements java.io.Serializable {
071:
072: private static final long serialVersionUID = 4121134753270542643L;
073: private Node parent;
074: private Object name;
075: private Map attributes;
076: private Object value;
077:
078: public Node(Node parent, Object name) {
079: this (parent, name, Collections.EMPTY_MAP,
080: Collections.EMPTY_LIST);
081: }
082:
083: public Node(Node parent, Object name, Object value) {
084: this (parent, name, Collections.EMPTY_MAP, value);
085: }
086:
087: public Node(Node parent, Object name, Map attributes) {
088: this (parent, name, attributes, Collections.EMPTY_LIST);
089: }
090:
091: public Node(Node parent, Object name, Map attributes, Object value) {
092: this .parent = parent;
093: this .name = name;
094: this .attributes = attributes;
095: this .value = value;
096:
097: if (parent != null) {
098: Object parentValue = parent.value();
099: List parentList;
100: if (parentValue instanceof List) {
101: parentList = (List) parentValue;
102: } else {
103: parentList = new NodeList();
104: parentList.add(parentValue);
105: parent.setValue(parentList);
106: }
107: parentList.add(this );
108: }
109: }
110:
111: public String text() {
112: if (value instanceof String) {
113: return (String) value;
114: } else if (value instanceof Collection) {
115: Collection coll = (Collection) value;
116: String previousText = null;
117: StringBuffer buffer = null;
118: for (Iterator iter = coll.iterator(); iter.hasNext();) {
119: Object child = iter.next();
120: if (child instanceof String) {
121: String childText = (String) child;
122: if (previousText == null) {
123: previousText = childText;
124: } else {
125: if (buffer == null) {
126: buffer = new StringBuffer();
127: buffer.append(previousText);
128: }
129: buffer.append(childText);
130: }
131: }
132: }
133: if (buffer != null) {
134: return buffer.toString();
135: } else {
136: if (previousText != null) {
137: return previousText;
138: }
139: }
140: }
141: return "";
142: }
143:
144: public Iterator iterator() {
145: return children().iterator();
146: }
147:
148: public List children() {
149: if (value == null) {
150: return Collections.EMPTY_LIST;
151: } else if (value instanceof List) {
152: return (List) value;
153: } else {
154: // we're probably just a String
155: return Collections.singletonList(value);
156: }
157: }
158:
159: public Map attributes() {
160: return attributes;
161: }
162:
163: public Object attribute(Object key) {
164: return (attributes != null) ? attributes.get(key) : null;
165: }
166:
167: public Object name() {
168: return name;
169: }
170:
171: public Object value() {
172: return value;
173: }
174:
175: public void setValue(Object value) {
176: this .value = value;
177: }
178:
179: public Node parent() {
180: return parent;
181: }
182:
183: /**
184: * Provides lookup of elements by non-namespaced name
185: * @param key the name (or shortcut key) of the node(s) of interest
186: * @return the nodes which match key
187: */
188: public Object get(String key) {
189: if (key != null && key.charAt(0) == '@') {
190: String attributeName = key.substring(1);
191: return attributes().get(attributeName);
192: }
193: if ("..".equals(key)) {
194: return parent();
195: }
196: if ("*".equals(key)) {
197: return children();
198: }
199: if ("**".equals(key)) {
200: return depthFirst();
201: }
202: // iterate through list looking for node with name 'key'
203: List answer = new NodeList();
204: for (Iterator iter = children().iterator(); iter.hasNext();) {
205: Object child = iter.next();
206: if (child instanceof Node) {
207: Node childNode = (Node) child;
208: Object childNodeName = childNode.name();
209: if (childNodeName != null && childNodeName.equals(key)) {
210: answer.add(childNode);
211: }
212: }
213: }
214: return answer;
215: }
216:
217: /**
218: * Provides lookup of elements by QName.
219: *
220: * @param name the QName of interest
221: * @return the nodes matching name
222: */
223: public NodeList getAt(QName name) {
224: NodeList answer = new NodeList();
225: for (Iterator iter = children().iterator(); iter.hasNext();) {
226: Object child = iter.next();
227: if (child instanceof Node) {
228: Node childNode = (Node) child;
229: Object childNodeName = childNode.name();
230: if (childNodeName != null && childNodeName.equals(name)) {
231: answer.add(childNode);
232: }
233: }
234: }
235: return answer;
236: }
237:
238: /**
239: * Provide a collection of all the nodes in the tree
240: * using a depth first traversal.
241: *
242: * @return the list of (depth-first) ordered nodes
243: */
244: public List depthFirst() {
245: List answer = new NodeList();
246: answer.add(this );
247: answer.addAll(depthFirstRest());
248: return answer;
249: }
250:
251: private List depthFirstRest() {
252: List answer = new NodeList();
253: for (Iterator iter = InvokerHelper.asIterator(value); iter
254: .hasNext();) {
255: Object child = iter.next();
256: if (child instanceof Node) {
257: Node childNode = (Node) child;
258: List children = childNode.depthFirstRest();
259: answer.add(childNode);
260: answer.addAll(children);
261: }
262: }
263: return answer;
264: }
265:
266: /**
267: * Provide a collection of all the nodes in the tree
268: * using a breadth-first traversal.
269: *
270: * @return the list of (breadth-first) ordered nodes
271: */
272: public List breadthFirst() {
273: List answer = new NodeList();
274: answer.add(this );
275: answer.addAll(breadthFirstRest());
276: return answer;
277: }
278:
279: private List breadthFirstRest() {
280: List answer = new NodeList();
281: List nextLevelChildren = getDirectChildren();
282: while (!nextLevelChildren.isEmpty()) {
283: List working = new NodeList(nextLevelChildren);
284: nextLevelChildren = new NodeList();
285: for (Iterator iter = working.iterator(); iter.hasNext();) {
286: Node childNode = (Node) iter.next();
287: answer.add(childNode);
288: List children = childNode.getDirectChildren();
289: nextLevelChildren.addAll(children);
290: }
291: }
292: return answer;
293: }
294:
295: private List getDirectChildren() {
296: List answer = new NodeList();
297: for (Iterator iter = InvokerHelper.asIterator(value); iter
298: .hasNext();) {
299: Object child = iter.next();
300: if (child instanceof Node) {
301: Node childNode = (Node) child;
302: answer.add(childNode);
303: }
304: }
305: return answer;
306: }
307:
308: public String toString() {
309: return name + "[attributes=" + attributes + "; value=" + value
310: + "]";
311: }
312:
313: public void print(PrintWriter out) {
314: new NodePrinter(out).print(this);
315: }
316: }
|