1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
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
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 }