View Javadoc

1   /*
2    $Id: Node.java 4201 2006-11-05 10:23:50Z paulk $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.util;
47  
48  import org.codehaus.groovy.runtime.InvokerHelper;
49  
50  import groovy.xml.QName;
51  
52  import java.io.PrintWriter;
53  import java.util.Collection;
54  import java.util.Collections;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Map;
58  
59  /***
60   * Represents an arbitrary tree node which can be used for structured metadata or any arbitrary XML-like tree.
61   * A node can have a name, a value and an optional Map of attributes.
62   * Typically the name is a String and a value is either a String or a List of other Nodes,
63   * though the types are extensible to provide a flexible structure, e.g. you could use a
64   * QName as the name which includes a namespace URI and a local name. Or a JMX ObjectName etc.
65   * 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" }}
66   * 
67   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
68   * @version $Revision: 4201 $
69   */
70  public class Node implements java.io.Serializable {
71  
72      private static final long serialVersionUID = 4121134753270542643L;
73      private Node parent;
74      private Object name;
75      private Map attributes;
76      private Object value;
77  
78      public Node(Node parent, Object name) {
79          this(parent, name, Collections.EMPTY_MAP, Collections.EMPTY_LIST);
80      }
81  
82      public Node(Node parent, Object name, Object value) {
83          this(parent, name, Collections.EMPTY_MAP, value);
84      }
85  
86      public Node(Node parent, Object name, Map attributes) {
87          this(parent, name, attributes, Collections.EMPTY_LIST);
88      }
89  
90      public Node(Node parent, Object name, Map attributes, Object value) {
91          this.parent = parent;
92          this.name = name;
93          this.attributes = attributes;
94          this.value = value;
95          
96          if (parent != null) {
97              Object parentValue = parent.value();
98              List parentList;
99              if (parentValue instanceof List) {
100                 parentList = (List) parentValue;
101             } else {
102                 parentList = new NodeList();
103                 parentList.add(parentValue);
104                 parent.setValue(parentList);
105             }
106             parentList.add(this);
107         }
108     }
109 
110     public String text() {
111         if (value instanceof String) {
112             return (String) value;
113         }
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                     }
125                     else {
126                         if (buffer == null) {
127                             buffer = new StringBuffer();
128                             buffer.append(previousText);
129                         }
130                         buffer.append(childText);
131                     }
132                 }
133             }
134             if (buffer != null) {
135                 return buffer.toString();
136             }
137             else {
138                 if (previousText != null) {
139                     return previousText;
140                 }
141             }
142         }
143         return "";
144     }
145 
146     
147     public Iterator iterator() {
148         return children().iterator();
149     }
150     
151     public List children() {
152         if (value == null) {
153             return Collections.EMPTY_LIST;
154         }
155         else if (value instanceof List) {
156             return (List) value;
157         }
158         else {
159             // we're probably just a String
160             return Collections.singletonList(value);
161         }
162     }
163 
164     public Map attributes() {
165         return attributes;
166     }
167 
168     public Object attribute(Object key) {
169         return (attributes != null) ? attributes.get(key) : null;
170     }
171     
172     public Object name() {
173         return name;
174     }
175 
176     public Object value() {
177         return value;
178     }
179 
180     public void setValue(Object value) {
181         this.value = value;
182     }
183 
184     public Node parent() {
185         return parent;
186     }
187 
188     /***
189      * Provides lookup of elements by non-namespaced name
190      * @param key the name (or shortcut key) of the node(s) of interest
191      * @return the nodes which match key
192      */
193     public Object get(String key) {
194         if (key != null && key.charAt(0) == '@') {
195             String attributeName = key.substring(1);
196             return attributes().get(attributeName);
197         }
198         if ("..".equals(key)) {
199             return parent();
200         }
201         if ("*".equals(key)) {
202             return children();
203         }
204         if ("**".equals(key)) {
205             return depthFirst();
206         }
207         // iterate through list looking for node with name 'key'
208         List answer = new NodeList();
209         for (Iterator iter = children().iterator(); iter.hasNext();) {
210             Object child = iter.next();
211             if (child instanceof Node) {
212                 Node childNode = (Node) child;
213                 Object childNodeName = childNode.name();
214                 if (childNodeName != null && childNodeName.equals(key)) {
215                     answer.add(childNode);
216                 }
217             }
218         }
219         return answer;
220     }
221     
222     /***
223      * Provides lookup of elements by QName.
224      *
225      * @param name the QName of interest
226      * @return the nodes matching name
227      */
228     public NodeList getAt(QName name) {
229         NodeList answer = new NodeList();
230         for (Iterator iter = children().iterator(); iter.hasNext();) {
231             Object child = iter.next();
232             if (child instanceof Node) {
233                 Node childNode = (Node) child;
234                 Object childNodeName = childNode.name();
235                 if (childNodeName != null && childNodeName.equals(name)) {
236                     answer.add(childNode);
237                 }
238             }
239         }
240         return answer;
241     }
242 
243     /***
244      * Provide a collection of all the nodes in the tree
245      * using a depth first traversal.
246      *
247      * @return the list of (depth-first) ordered nodes
248      */
249     public List depthFirst() {
250         List answer = new NodeList();
251         answer.add(this);
252         answer.addAll(depthFirstRest());
253         return answer;
254     }
255     
256     private List depthFirstRest() {
257         List answer = new NodeList();
258         for (Iterator iter = InvokerHelper.asIterator(value); iter.hasNext(); ) {
259             Object child = iter.next();
260             if (child instanceof Node) {
261                 Node childNode = (Node) child;
262                 List children = childNode.depthFirstRest();
263                 answer.add(childNode);
264                 answer.addAll(children);
265             }
266         }
267         return answer;
268     }
269 
270     /***
271      * Provide a collection of all the nodes in the tree
272      * using a breadth-first traversal.
273      *
274      * @return the list of (breadth-first) ordered nodes
275      */
276     public List breadthFirst() {
277         List answer = new NodeList();
278         answer.add(this);
279         answer.addAll(breadthFirstRest());
280         return answer;
281     }
282     
283     private List breadthFirstRest() {
284         List answer = new NodeList();
285         List nextLevelChildren = getDirectChildren();
286         while (!nextLevelChildren.isEmpty()) {
287             List working = new NodeList(nextLevelChildren);
288             nextLevelChildren = new NodeList();
289             for (Iterator iter = working.iterator(); iter.hasNext(); ) {
290                 Node childNode = (Node) iter.next();
291                 answer.add(childNode);
292                 List children = childNode.getDirectChildren();
293                 nextLevelChildren.addAll(children);
294             }
295         }
296         return answer;
297     }
298 
299     private List getDirectChildren() {
300         List answer = new NodeList();
301         for (Iterator iter = InvokerHelper.asIterator(value); iter.hasNext(); ) {
302             Object child = iter.next();
303             if (child instanceof Node) {
304                 Node childNode = (Node) child;
305                 answer.add(childNode);
306             }
307         }
308         return answer;
309     }
310 
311     public String toString() {
312         return name + "[attributes=" + attributes + "; value=" + value + "]";
313     }
314 
315     public void print(PrintWriter out) {
316         new NodePrinter(out).print(this);
317     }
318 }