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, Collections.EMPTY_LIST);
080 }
081
082 public Node(Node parent, Object name, Object value) {
083 this(parent, name, Collections.EMPTY_MAP, value);
084 }
085
086 public Node(Node parent, Object name, Map attributes) {
087 this(parent, name, attributes, Collections.EMPTY_LIST);
088 }
089
090 public Node(Node parent, Object name, Map attributes, Object value) {
091 this.parent = parent;
092 this.name = name;
093 this.attributes = attributes;
094 this.value = value;
095
096 if (parent != null) {
097 Object parentValue = parent.value();
098 List parentList;
099 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 }