/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2009 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.tree;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.Map;
import xtc.util.Pair;
/**
* The superclass of all node visitors.
*
* <p />Nodes may contain children that are lists of nodes. To
* simplify the processing of such lists with visitors, this class
* defines three helper methods that apply a visitor to a list of
* nodes:<ul>
*
* <li>{@link #iterate(Pair)} invokes <code>dispatch()</code> on each
* element of the list and ignores any results.</li>
*
* <li>{@link #map(Pair)} invokes <code>dispatch()</code> on each
* element of the list while also collecting the results in a new
* list.</li>
*
* <li>{@link #mapInPlace(Pair)} invokes <code>dispatch()</code> on
* each element of the list while also updating the list with the
* results.</li>
*
* </ul>
*
* Additonally, <code>Node</code> provides helper methods to
* dynamically test and cast lists of nodes through {@link
* Node#isList(Object)} and {@link Node#toList(Object)} respectively.
*
* @author Robert Grimm
* @version $Revision: 1.31 $
*/
public abstract class Visitor {
/** Key for the method lookup cache. */
static final class CacheKey {
/** The visitor. */
public Visitor visitor;
/** The object identifying the node. */
public Object node;
/**
* Create a new cache key.
*
* @param visitor The visitor.
* @param node The object identifying the node.
*/
public CacheKey(Visitor visitor, Object node) {
this.visitor = visitor;
this.node = node;
}
public int hashCode() {
return (37 * visitor.hashCode()) + node.hashCode();
}
public boolean equals(Object o) {
if (! (o instanceof CacheKey)) return false;
CacheKey other = (CacheKey)o;
if (! visitor.equals(other.visitor)) return false;
return node.equals(other.node);
}
}
// ========================================================================
/** The size of the method lookup cache. */
private static final int CACHE_SIZE = 300;
/** The capacity of the method lookup cache. */
private static final int CACHE_CAPACITY = 400;
/** The load factor of the method lookup cache. */
private static final float CACHE_LOAD = (float)0.75;
/** The method lookup cache. */
private static final LinkedHashMap<CacheKey, Method> cache;
/** The pre-allocated cache key for looking up methods. */
private static final CacheKey key;
/** The pre-allocated array for passing the argument to invoke(). */
private static final Object[] arguments;
/**
* The pre-allocated array for passing the type argument to
* getMethod().
*/
private static final Class<?>[] types;
static {
cache =
new LinkedHashMap<CacheKey, Method>(CACHE_CAPACITY, CACHE_LOAD, true) {
protected boolean removeEldestEntry(Map.Entry e) {
return size() > CACHE_SIZE;
}
};
key = new CacheKey(null, null);
arguments = new Object[] { null };
types = new Class<?>[] { null };
}
// ========================================================================
/** Create a new visitor. */
public Visitor() { /* Nothing to do. */ }
/**
* Get a hashcode for this visitor.
*
* @return The identity hashcode.
*/
public final int hashCode() {
return super.hashCode();
}
/**
* Determine whether this visitor equals the specified object.
*
* @param o The object to compare to.
* @return <code>true</code> if the specified object is this visitor.
*/
public final boolean equals(Object o) {
return this == o;
}
/**
* Visit the specified annotation. This method simply applies this
* visitor on the node referenced by the annotation, thus ignoring
* the annotation.
*
* @param a The annotation.
* @return The result of applying this visitor on the referenced node.
*/
public Object visit(Annotation a) {
return dispatch(a.node);
}
// ========================================================================
/**
* Dispatch this visitor on the specified node. This method
* determines the closest matching <code>visit()</code> method,
* invokes it on the specified node, and returns the result. If the
* specified node is <code>null</code> or the selected method
* returns <code>void</code>, this method returns <code>null</code>.
*
* @see #unableToVisit(Node)
*
* @param n The node.
* @return The result of dispatching this visitor on the specified
* node.
* @throws VisitorException Signals that no matching
* <code>visit()</code> method could be found.
* @throws VisitingException Signals an exceptional condition while
* applying the specified visitor on this node.
*/
public final Object dispatch(final Node n) {
// Get the trivial case out of the way.
if (null == n) return null;
// Check the method lookup cache.
Method method;
key.visitor = this;
if (n.isGeneric()) {
key.node = n.getName();
} else {
key.node = n.getClass();
}
method = cache.get(key);
if (null == method) {
// Determine the correct cache value and cache it.
method = findMethod(n);
cache.put(new CacheKey(this, key.node), method);
}
// Set up the argument.
arguments[0] = n;
// Invoke the method.
try {
return method.invoke(this, arguments);
} catch (IllegalAccessException x) {
throw new VisitorException("Unable to invoke " + method + " on " +
arguments[0]);
} catch (IllegalArgumentException x) {
throw new VisitorException("Internal error while visiting node " +
n + " with visitor " + this);
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
// Rethrow visiting and visitor exceptions.
if (cause instanceof VisitingException) {
throw (VisitingException)cause;
} else if (cause instanceof VisitorException) {
throw (VisitorException)cause;
}
// Throw the appropriate visiting exception.
throw new VisitingException("Error visiting node " + n + " with " +
"visitor " + this, cause);
} catch (NullPointerException x) {
throw new VisitorException("Internal error while visiting node " +
n + " with visitor " + this);
}
}
/**
* Determine the method for visiting the specified node with this
* visitor.
*
* @param n The node.
* @return The corresponding method.
*/
private Method findMethod(final Node n) {
Class<?> visitorT = getClass();
Method method = null;
if (n.isGeneric()) {
// Look for visit<n.getName()>(GNode).
types[0] = GNode.class;
try {
method = visitorT.getMethod("visit" + n.getName(), types);
} catch (NoSuchMethodException x) {
// Look for visit(GNode).
try {
method = visitorT.getMethod("visit", types);
} catch (NoSuchMethodException xx) {
// Look for visit(Node).
types[0] = Node.class;
try {
method = visitorT.getMethod("visit", types);
} catch (NoSuchMethodException xxx) {
// Ignore.
}
}
}
} else {
// Look for visit(<type>), starting with Type = n.getClass().
method = findMethod(visitorT, "visit", n.getClass());
}
// Look for unableToVisit(Node).
if (null == method) {
types[0] = Node.class;
try {
method = visitorT.getMethod("unableToVisit", types);
} catch (NoSuchMethodException x) {
throw new AssertionError("Unable to find unableToVisit(Node)");
}
}
// Override access control and return method.
method.setAccessible(true);
return method;
}
/**
* Find a method for the specified class with the specified name and
* parameter type. This method, in addition to looking for a method
* with the specified parameter type, also tries all interfaces
* implemented by the parameter type, then the superclass, then the
* interfaces implemented by the superclass, and so on.
*
* @param k The class.
* @param name The method name.
* @param paramT The parameter type.
* @return The method or <code>null</code> if no such method exists.
*/
private static Method findMethod(Class<?> k, String name, Class paramT) {
Method method = null;
do {
types[0] = paramT;
try {
method = k.getMethod(name, types);
} catch (NoSuchMethodException x) {
// Try the interfaces implemented by paramT.
Class<?>[] interfaces = paramT.getInterfaces();
for (int i=0; i<interfaces.length; i++) {
types[0] = interfaces[i];
try {
method = k.getMethod(name, types);
break;
} catch (NoSuchMethodException xx) {
// Ignore.
}
}
// Move on to the superclass.
paramT = paramT.getSuperclass();
}
} while ((null == method) && (Object.class != paramT));
return method;
}
/**
* Signal that this visitor has no <code>visit()</code> method for
* the specified node. The default implementation simply raises a
* visitor exception.
*
* @param node The node.
* @return The result of processing the node.
* @throws VisitorException Signals that no matching
* <code>visit()</code> method could be found.
*/
public Object unableToVisit(Node node) {
if (node.isGeneric()) {
throw new VisitorException("No method to visit generic node " +
node.getName() + " with visitor " + this);
} else {
throw new VisitorException("No method to visit node type " +
node.getClass() + " with visitor " + this);
}
}
// ========================================================================
/**
* Iterate this visitor over the specified list.
*
* @param list The list.
*/
public void iterate(Pair<? extends Node> list) {
while (Pair.EMPTY != list) {
dispatch(list.head());
list = list.tail();
}
}
/**
* Map this visitor over the specified list.
*
* @param list The list.
* @return The list of results.
*/
public <T> Pair<T> map(Pair<? extends Node> list) {
if (Pair.EMPTY == list) return Pair.empty();
final @SuppressWarnings("unchecked")
T v1 = (T)dispatch(list.head());
Pair<T> result = new Pair<T>(v1);
Pair<T> cursor = result;
while (Pair.EMPTY != list.tail()) {
list = list.tail();
final @SuppressWarnings("unchecked")
T v2 = (T)dispatch(list.head());
cursor.setTail(new Pair<T>(v2));
cursor = cursor.tail();
}
return result;
}
/**
* Map this visitor over the specified list while also updating the
* list.
*
* @param list The list.
* @return The updated list.
*/
public <T extends Node> Pair<T> mapInPlace(Pair<T> list) {
Pair<T> p = list;
while (Pair.EMPTY != p) {
final @SuppressWarnings("unchecked")
T v = (T)dispatch(p.head());
p.setHead(v);
p = p.tail();
}
return list;
}
}