/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2007 Robert Grimm
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.tree;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import xtc.util.Pair;
import xtc.util.Utilities;
/**
* A node in an abstract syntax tree.
*
* <p />Subclasses may optionally support two features. First, a
* subclass may support <strong>generic tree traversal</strong>. Such
* a class must override {@link #hasTraversal()} to return
* <code>true</code> and must provide meaningful implementations for
* {@link #size()}, {@link #get(int)}, and {@link #set(int,Object)}.
* Second, a subclass may support a <strong>variable number of
* children</strong>. Such a class must override {@link
* #hasVariable()} to return <code>true</code> and must provide
* meaningful implementations for {@link #add(Object)}, {@link
* #add(int,Object)}, and {@link #remove(int)}.
*
* @author Robert Grimm
* @version $Revision: 1.55 $
*/
public abstract class Node implements Iterable<Object>, Locatable {
/** The properties. */
Map<String, Object> properties;
/** The optional source location. */
Location location;
// ========================================================================
/** Create a new node. */
public Node() { /* Nothing to do. */ }
/**
* Create a new node.
*
* @param location The source location for the new node.
*/
public Node(Location location) {
this.location = location;
}
// ========================================================================
/**
* Determine whether this node is a token. User-specified classes
* must not override this method.
*
* @see Token
*
* @return <code>true</code> if this node is a token.
*/
public boolean isToken() {
return false;
}
/**
* Get this node as a token. User-specified classes must not
* override this method.
*
* @return This node as a token.
* @throws ClassCastException Signals that this node is not a token.
*/
public Token toToken() {
throw new ClassCastException("Not a token");
}
/**
* Treat this node as a token and get its text. This method strips
* away any annotations, treats the resulting node as a token, and
* returns its text. User-specified classes must not override this
* method.
*
* @see #strip()
*
* @return The token's text.
* @throws ClassCastException Signals that this node is not a token.
*/
public String getTokenText() {
throw new ClassCastException("Not a token");
}
/**
* Determine whether this node is an annotation. User-specified
* classes must not override this method.
*
* @see Annotation
*
* @return <code>true</code> if this node is an annotation.
*/
public boolean isAnnotation() {
return false;
}
/**
* Get this node as an annotation. User-specified classes must not
* override this method.
*
* @return This node as an annotation.
* @throws ClassCastException Signals that this node is not an
* annotation.
*/
public Annotation toAnnotation() {
throw new ClassCastException("Not an annotation");
}
/**
* Determine whether this node is a generic node. User-specified
* classes must not override this method.
*
* @see GNode
*
* @return <code>true</code> if this node is a generic node.
*/
public boolean isGeneric() {
return false;
}
// ========================================================================
/**
* Get the name of this node. For strongly typed nodes, the name is
* implicitly specified by the node's class. For generic nodes, the
* name is the generic node's explicit name. The default
* implementation returns the node's class name.
*
* <p />User-specified classes must not override this method.
*
* @return The name.
*/
public String getName() {
return getClass().getName();
}
/**
* Determine whether this node's name is the same as the specified
* name.
*
* <p />User-specified classes must not override this method.
*
* @param name The name.
* @return <code>true</code> if this node's name equals the
* specified name.
*/
public boolean hasName(final String name) {
return getClass().getName().equals(name);
}
// ========================================================================
/**
* Set the value of a property.
*
* @param name The property name.
* @param value The new property value.
* @return The property's old value or <code>null</code> if the
* property didn't have a value.
*/
public Object setProperty(String name, Object value) {
if (null == properties) {
properties = new HashMap<String, Object>();
}
return properties.put(name, value);
}
/**
* Test if this node has a property.
*
* @param name The property name.
* @return <code>true</code> if this node has a property with the
* specified name.
*/
public boolean hasProperty(String name) {
if (null == properties) {
return false;
} else {
return properties.containsKey(name);
}
}
/**
* Get a property value.
*
* @param name The property name.
* @return The property's value or <code>null</code> if the
* property doesn't have a value.
*/
public Object getProperty(String name) {
if (null == properties) {
return null;
} else {
return properties.get(name);
}
}
/**
* Get a property value as a boolean. If this node does not have a
* property with the specified name, this method returns
* <code>false</code>.
*
* @param name The property name.
* @return The property's value as a boolean.
*/
public boolean getBooleanProperty(String name) {
if (null == properties) {
return false;
} else {
Object o = properties.get(name);
if (null == o) {
return false;
} else {
return (Boolean)o;
}
}
}
/**
* Get a property value as a string.
*
* @param name The property name.
* @return The property's value as a string.
*/
public String getStringProperty(String name) {
if (null == properties) {
return null;
} else {
return (String)properties.get(name);
}
}
/**
* Remove a property.
*
* @param name The property name.
* @return The property's old value or <code>null</code> if the
* property didn't have a value.
*/
public Object removeProperty(String name) {
if (null == properties) {
return null;
} else {
return properties.remove(name);
}
}
/**
* Get the set of property names.
*
* @return The set of property names.
*/
public Set<String> properties() {
if (null == properties) {
return Collections.emptySet();
} else {
return properties.keySet();
}
}
// ========================================================================
public boolean hasLocation() {
return null != location;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public void setLocation(Locatable locatable) {
if (locatable.hasLocation()) this.location = locatable.getLocation();
}
// ========================================================================
/**
* Determine whether this node supports generic traversal of its
* children. The default implementation returns <code>false</code>.
*
* @return <code>true</code> if this node supports generic traversal
* of its children.
*/
public boolean hasTraversal() {
return false;
}
/**
* Determine whether this node has no children.
*
* @return <code>true</code> if this node has no children.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public boolean isEmpty() {
return 0 == size();
}
/**
* Get an iterator over this node's children.
*
* <p />Note that instance tests on the iterator's objects may not
* behave as expected. Notably, any node may be wrapped in
* annotations. Furthermore, any string may be wrapped in a token
* (and, recursively, in annotations).
*
* @see Token#test(Object)
* @see Token#cast(Object)
* @see GNode#test(Object)
* @see GNode#cast(Object)
*
* @return An iterator over the children.
*/
public Iterator<Object> iterator() {
final int size = size();
return new Iterator<Object>() {
int cursor = 0;
public boolean hasNext() {
return cursor < size;
}
public Object next() {
if (cursor < size) {
return get(cursor++);
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException("Down with Iterator.remove()");
}};
}
/**
* Get the number of children. The default implementation signals
* an unsupported operation exception.
*
* @return The number of children.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public int size() {
throw new UnsupportedOperationException();
}
/**
* Get the child at the specified index. The default implementation
* signals an unsupported operation exception.
*
* <p />Note that instance tests on the returned object may not
* behave as expected. Notably, any node may be wrapped in
* annotations. Furthermore, any string may be wrapped in a token
* (and, recursively, in annotations).
*
* @see Token#test(Object)
* @see Token#cast(Object)
* @see GNode#test(Object)
* @see GNode#cast(Object)
*
* @param index The index.
* @return The child at that positioin.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public Object get(int index) {
throw new UnsupportedOperationException();
}
/**
* Get the boolean child at the specified index.
*
* @param index The index.
* @return The child at that position as a boolean.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the child is not a
* boolean.
* @throws UnsupportedOperationException Signals that this node does
* not support generic traversal.
*/
public boolean getBoolean(int index) {
return (Boolean)get(index);
}
/**
* Get the string child at the specified index. If the child at the
* specified index is a string, this method returns it. Otherwise,
* it casts the child to a node, strips any annotations, and returns
* the text of the annotated token.
*
* @param index The index.
* @return The child at that position as a string.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the child is not a string
* nor an annotated token.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public String getString(int index) {
Object o = get(index);
if (null == o) {
return null;
} else if (o instanceof String) {
return (String)o;
} else {
return ((Node)o).getTokenText();
}
}
/**
* Get the node child at the specified index.
*
* @param index The index.
* @return The child at that position as a node.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the child is not a node.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public Node getNode(int index) {
return (Node)get(index);
}
/**
* Get the generic node child at the specified index. If the
* specified child has any {@link Annotation annotations}, they are
* {@link Node#strip() stripped} before returning the child as a
* generic node.
*
* @param index The index.
* @return The child at that position as a stripped generic node.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the child is not a node.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public GNode getGeneric(int index) {
Object o = get(index);
return (null == o) ? null : (GNode)((Node)o).strip();
}
/**
* Get the list child at the specified index.
*
* @param index The index.
* @return The list child at that position as a list.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the child is not a list.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
@SuppressWarnings("unchecked")
public <T> Pair<T> getList(int index) {
return (Pair<T>)get(index);
}
/**
* Set the child at the specified index to the specified value. The
* default implementation signals an unsupported operation
* exception.
*
* @param index The index.
* @param value The new value.
* @return The old value.
* @throws IllegalStateException Signals that this node is
* immutable.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws ClassCastException Signals that the value is not of the
* necessary type.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public Object set(int index, Object value) {
throw new UnsupportedOperationException();
}
/**
* Determine the index of the specified object.
*
* @param o The object.
* @return The first index of the child equal to the specified
* object or -1 if this node does not have the object as a child.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public int indexOf(Object o) {
final int size = size();
for (int i=0; i<size; i++) {
Object child = get(i);
if (null == o ? null == child : o.equals(child)) return i;
}
return -1;
}
/**
* Determine the last index of the specified object.
*
* @param o The object.
* @return The last index of the child equal to the specified object
* or -1 if this node does not have the object as a child.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public int lastIndexOf(Object o) {
for (int i=size()-1; i>=0; i--) {
Object child = get(i);
if (null == o ? null == child : o.equals(child)) return i;
}
return -1;
}
/**
* Determine whether this node has the specified object as a child.
*
* @param o The object.
* @return <code>true</code> if this node has the specified object
* as a child.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public boolean contains(Object o) {
return -1 != indexOf(o);
}
// =======================================================================
/**
* Add all of this node's children to the specified collection.
*
* @param c The collection.
* @throws UnsupportedOperationException Signals that this node
* does not support generic traversal.
*/
public void addAllTo(Collection<Object> c) {
final int size = size();
for (int i=0; i<size; i++) {
c.add(get(i));
}
}
// =======================================================================
/**
* Determine whether this node supports a variable number of
* children. Any variable-sized node should also support generic
* traversal. The default implementation returns
* <code>false</code>.
*
* @see #hasTraversal()
*
* @return <code>true</code> if this node supports a variable number
* of children.
*/
public boolean hasVariable() {
return false;
}
/**
* Add the specified object as a child. The default implementation
* signals an unsupported operation exception.
*
* @param o The object.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node add(Object o) {
throw new UnsupportedOperationException();
}
/**
* Add the specified node as a child. For nodes that are not
* annotations supporting a variable number of children, this method
* is semantically equivalent to {@link #add(Object)}. For
* annotations supporting a variable number of children, this method
* adds the annotated node. Any previously added children precede
* that node and any children added after the call to this method
* succeed that node.
*
* @param node The node.
* @return This node.
* @throws IllegalStateException Signals that this method has
* already been invoked on an annotation supporting a variable
* number of children.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node addNode(Node node) {
return add(node);
}
/**
* Add the specified object as a child at the specified index. The
* default implementation signals an unsupported operation
* exception.
*
* @param index The index.
* @param o The object.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node add(int index, Object o) {
throw new UnsupportedOperationException();
}
/**
* Add all values in the list starting with the specified pair as
* children.
*
* @param p The pair.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node addAll(Pair<?> p) {
while (Pair.empty() != p) {
add(p.head());
p = p.tail();
}
return this;
}
/**
* Add all values in the list starting with the specified pair as
* children at the specified index.
*
* @param index The index.
* @param p The pair.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node addAll(int index, Pair<?> p) {
while (Pair.empty() != p) {
add(index++, p.head());
p = p.tail();
}
return this;
}
/**
* Add all values in the specified collection as children.
*
* @param c The collection.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node addAll(Collection<?> c) {
for (Object o : c) add(o);
return this;
}
/**
* Add all values in the specified collection as children at the
* specified index.
*
* @param index The index.
* @param c The collection.
* @return This node.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Node addAll(int index, Collection<?> c) {
for (Object o : c) add(index++, o);
return this;
}
/**
* Remove the child at the specified index. The default
* implementation signals an unsupported operation exception.
*
* @param index The index.
* @return The removed child.
* @throws IndexOutOfBoundsException Signals that the index is out
* of range.
* @throws UnsupportedOperationException Signals that this node does
* not support a variable number of children.
*/
public Object remove(int index) {
throw new UnsupportedOperationException();
}
// ========================================================================
/**
* Strip any annotations. This method removes any annotations
* starting with this node. The default implementation returns this
* node.
*
* @see Annotation
*
* @return The node without annotations.
*/
public Node strip() {
return this;
}
// ========================================================================
/**
* Write a human readable representation to the specified
* appendable. If this node supports generic traversal, the default
* implementation writes this node in algebraic term-format;
* otherwise, it writes the string returned by {@link
* Object#toString()}.
*
* @param out The appendable.
* @throws IOException Signals an I/O error.
*/
public void write(Appendable out) throws IOException {
if (! hasTraversal()) {
out.append(super.toString());
} else {
out.append(getName());
out.append('(');
boolean first = true;
for (Object o : this) {
if (first) {
first = false;
} else {
out.append(", ");
}
if (null == o) {
out.append("null");
} else if (o instanceof String) {
out.append('"');
Utilities.escape((String)o, out, Utilities.JAVA_ESCAPES);
out.append('"');
} else if (o instanceof Node) {
((Node)o).write(out);
} else {
out.append(o.toString());
}
}
out.append(')');
}
}
/**
* Return a human readable representation of this node. The default
* implementation creates a new string builder, writes this node to
* the builder, and then returns the corresponding string.
* Subclasses should typically override {@link #write(Appendable)}.
*
* @return A human readable representation.
*/
public String toString() {
StringBuilder buf = new StringBuilder();
try {
write(buf);
} catch (IOException x) {
assert false;
}
return buf.toString();
}
// ========================================================================
/**
* Determine whether the specified object is a list of nodes.
*
* @param o The object.
* @return <code>true</code> if the specified object is a list of
* nodes.
*/
public static final boolean isList(Object o) {
if (! (o instanceof Pair)) return false;
if (Pair.EMPTY == o) return true;
return ((Pair)o).head() instanceof Node;
}
/**
* Convert the specified object to a list of nodes.
*
* @param o The object, which must be a list of nodes.
* @return The object as a list of nodes.
* @throws ClassCastException Signals that the object is not a list
* of nodes.
*/
@SuppressWarnings("unchecked")
public static final Pair<Node> toList(Object o) {
if (isList(o)) {
return (Pair<Node>)o;
} else {
throw new ClassCastException("Not a list of nodes " + o);
}
}
}