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 }