/* * Copyright 2003-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package groovy.util; import groovy.lang.Closure; import groovy.lang.DelegatingMetaClass; import groovy.lang.GroovySystem; import groovy.lang.MetaClass; import groovy.xml.QName; import org.codehaus.groovy.runtime.InvokerHelper; import java.io.PrintWriter; import java.io.Serializable; import java.util.*; /** * Represents an arbitrary tree node which can be used for structured metadata or any arbitrary XML-like tree. * A node can have a name, a value and an optional Map of attributes. * Typically the name is a String and a value is either a String or a List of other Nodes, * though the types are extensible to provide a flexible structure, e.g. you could use a * QName as the name which includes a namespace URI and a local name. Or a JMX ObjectName etc. * So this class can represent metadata like <code>{foo a=1 b="abc"}</code> or nested * metadata like <code>{foo a=1 b="123" { bar x=12 text="hello" }}</code> * * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> * @author Paul King * @version $Revision$ */ public class Node implements Serializable { static { // wrap the standard MetaClass with the delegate setMetaClass(GroovySystem.getMetaClassRegistry().getMetaClass(Node.class), Node.class); } private static final long serialVersionUID = 4121134753270542643L; private Node parent; private Object name; private Map attributes; private Object value; /** * Creates a new Node named <code>name</code> and if a parent is supplied, adds * the newly created node as a child of the parent. * * @param parent the parent node or null if no parent * @param name the name of the node */ public Node(Node parent, Object name) { this(parent, name, new NodeList()); } /** * Creates a new Node named <code>name</code> with value <code>value</code> and * if a parent is supplied, adds the newly created node as a child of the parent. * * @param parent the parent node or null if no parent * @param name the name of the node * @param value the Node value, e.g. some text but in general any Object */ public Node(Node parent, Object name, Object value) { this(parent, name, new HashMap(), value); } /** * Creates a new Node named <code>name</code> with * attributes specified in the <code>attributes</code> Map. If a parent is supplied, * the newly created node is added as a child of the parent. * * @param parent the parent node or null if no parent * @param name the name of the node * @param attributes a Map of name-value pairs */ public Node(Node parent, Object name, Map attributes) { this(parent, name, attributes, new NodeList()); } /** * Creates a new Node named <code>name</code> with value <code>value</code> and * with attributes specified in the <code>attributes</code> Map. If a parent is supplied, * the newly created node is added as a child of the parent. * * @param parent the parent node or null if no parent * @param name the name of the node * @param attributes a Map of name-value pairs * @param value the Node value, e.g. some text but in general any Object */ public Node(Node parent, Object name, Map attributes, Object value) { this.parent = parent; this.name = name; this.attributes = attributes; this.value = value; if (parent != null) { getParentList(parent).add(this); } } private List getParentList(Node parent) { Object parentValue = parent.value(); List parentList; if (parentValue instanceof List) { parentList = (List) parentValue; } else { parentList = new NodeList(); parentList.add(parentValue); parent.setValue(parentList); } return parentList; } public boolean append(Node child) { child.parent = this; return getParentList(this).add(child); } public boolean remove(Node child) { child.parent = null; return getParentList(this).remove(child); } public Node appendNode(Object name, Map attributes) { return new Node(this, name, attributes); } public Node appendNode(Object name) { return new Node(this, name); } public Node appendNode(Object name, Object value) { return new Node(this, name, value); } public Node appendNode(Object name, Map attributes, Object value) { return new Node(this, name, attributes, value); } public Node replaceNode(Closure c) { getParentList(parent).remove(this); NodeBuilder b = new NodeBuilder(); Node newNode = (Node) b.invokeMethod("dummyNode", c); List<Node> children = newNode.children(); Node result = this; for (Node child : children) { result = parent.appendNode(child.name(), child.attributes(), child.value()); } return result; } public void plus(Closure c) { List<Node> list = this.parent().children(); int afterIndex = list.indexOf(this); List<Node> leftOvers = new ArrayList<Node>(list.subList(afterIndex + 1, list.size())); list.subList(afterIndex + 1, list.size()).clear(); NodeBuilder b = new NodeBuilder(); Node newNode = (Node) b.invokeMethod("dummyNode", c); List<Node> children = newNode.children(); for (Node child : children) { parent.appendNode(child.name(), child.attributes(), child.value()); } for (Node child : leftOvers) { this.parent().children().add(child); } } protected static void setMetaClass(final MetaClass metaClass, Class nodeClass) { final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) { @Override public Object getAttribute(final Object object, final String attribute) { Node n = (Node) object; return n.get("@" + attribute); } @Override public void setAttribute(final Object object, final String attribute, final Object newValue) { Node n = (Node) object; n.attributes().put(attribute, newValue); } @Override public Object getProperty(Object object, String property) { if (object instanceof Node) { Node n = (Node) object; return n.get(property); } return super.getProperty(object, property); } @Override public void setProperty(Object object, String property, Object newValue) { if (property.startsWith("@")) { String attribute = property.substring(1); Node n = (Node) object; n.attributes().put(attribute, newValue); return; } delegate.setProperty(object, property, newValue); } }; GroovySystem.getMetaClassRegistry().setMetaClass(nodeClass, newMetaClass); } public String text() { if (value instanceof String) { return (String) value; } else if (value instanceof Collection) { Collection coll = (Collection) value; String previousText = null; StringBuffer buffer = null; for (Iterator iter = coll.iterator(); iter.hasNext();) { Object child = iter.next(); if (child instanceof String) { String childText = (String) child; if (previousText == null) { previousText = childText; } else { if (buffer == null) { buffer = new StringBuffer(); buffer.append(previousText); } buffer.append(childText); } } } if (buffer != null) { return buffer.toString(); } else { if (previousText != null) { return previousText; } } } return ""; } public Iterator iterator() { return children().iterator(); } public List children() { if (value == null) { return new NodeList(); } if (value instanceof List) { return (List) value; } // we're probably just a String List result = new NodeList(); result.add(value); return result; } public Map attributes() { return attributes; } public Object attribute(Object key) { return (attributes != null) ? attributes.get(key) : null; } public Object name() { return name; } public Object value() { return value; } public void setValue(Object value) { this.value = value; } public Node parent() { return parent; } /** * Provides lookup of elements by non-namespaced name * * @param key the name (or shortcut key) of the node(s) of interest * @return the nodes which match key */ public Object get(String key) { if (key != null && key.charAt(0) == '@') { String attributeName = key.substring(1); return attributes().get(attributeName); } if ("..".equals(key)) { return parent(); } if ("*".equals(key)) { return children(); } if ("**".equals(key)) { return depthFirst(); } return getByName(key); } /** * Provides lookup of elements by QName. * * @param name the QName of interest * @return the nodes matching name */ public NodeList getAt(QName name) { NodeList answer = new NodeList(); for (Iterator iter = children().iterator(); iter.hasNext();) { Object child = iter.next(); if (child instanceof Node) { Node childNode = (Node) child; Object childNodeName = childNode.name(); if (name.matches(childNodeName)) { answer.add(childNode); } } } return answer; } /** * Provides lookup of elements by name. * * @param name the name of interest * @return the nodes matching name */ private NodeList getByName(String name) { NodeList answer = new NodeList(); for (Iterator iter = children().iterator(); iter.hasNext();) { Object child = iter.next(); if (child instanceof Node) { Node childNode = (Node) child; Object childNodeName = childNode.name(); if (childNodeName instanceof QName) { QName qn = (QName) childNodeName; if (qn.matches(name)) { answer.add(childNode); } } else if (name.equals(childNodeName)) { answer.add(childNode); } } } return answer; } /** * Provide a collection of all the nodes in the tree * using a depth first traversal. * * @return the list of (depth-first) ordered nodes */ public List depthFirst() { List answer = new NodeList(); answer.add(this); answer.addAll(depthFirstRest()); return answer; } private List depthFirstRest() { List answer = new NodeList(); for (Iterator iter = InvokerHelper.asIterator(value); iter.hasNext();) { Object child = iter.next(); if (child instanceof Node) { Node childNode = (Node) child; List children = childNode.depthFirstRest(); answer.add(childNode); answer.addAll(children); } } return answer; } /** * Provide a collection of all the nodes in the tree * using a breadth-first traversal. * * @return the list of (breadth-first) ordered nodes */ public List breadthFirst() { List answer = new NodeList(); answer.add(this); answer.addAll(breadthFirstRest()); return answer; } private List breadthFirstRest() { List answer = new NodeList(); List nextLevelChildren = getDirectChildren(); while (!nextLevelChildren.isEmpty()) { List working = new NodeList(nextLevelChildren); nextLevelChildren = new NodeList(); for (Iterator iter = working.iterator(); iter.hasNext();) { Node childNode = (Node) iter.next(); answer.add(childNode); List children = childNode.getDirectChildren(); nextLevelChildren.addAll(children); } } return answer; } private List getDirectChildren() { List answer = new NodeList(); for (Iterator iter = InvokerHelper.asIterator(value); iter.hasNext();) { Object child = iter.next(); if (child instanceof Node) { Node childNode = (Node) child; answer.add(childNode); } } return answer; } public String toString() { return name + "[attributes=" + attributes + "; value=" + value + "]"; } public void print(PrintWriter out) { new NodePrinter(out).print(this); } }