/* * xtc - The eXTensible Compiler * Copyright (C) 2004 Robert Grimm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package xtc.tree; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.util.LinkedHashMap; import java.util.Map; /** * A node visitor. In contrast to the basic visitor pattern (which * only works for a fixed set of object types), the node visitor is * extensible. As described in <a * href="http://www.javaworld.com/javaworld/javatips/jw-javatip98.html">this * JavaWorld tip</a>, the implementation relies on Java reflection to * dispatch to the appropriate <code>visit()</code> method. Note that * the node visitor does not recognize method names that append the * object type to the word "visit", rather it relies on the argument * type alone to identify the appropriate <code>visit()</code> method. * * <p>Note that to improve performance of dynamic dispatch, this class * uses a static method lookup cache. As a result, dispatch is not * thread-safe. * * @author Robert Grimm * @version $Revision: 1.1 $ */ public abstract class Visitor { /** Key for the method lookup cache. */ static class CacheKey { /** The visitor. */ public Visitor visitor; /** The class of the node. */ public Class nodeT; /** * Create a new cache key. * * @param visitor The visitor. * @param nodeT The class of the node. */ public CacheKey(Visitor visitor, Class nodeT) { this.visitor = visitor; this.nodeT = nodeT; } public int hashCode() { return (37 * System.identityHashCode(visitor) + System.identityHashCode(nodeT)); } public boolean equals(Object o) { if (! (o instanceof CacheKey)) return false; CacheKey other = (CacheKey)o; if (visitor != other.visitor) return false; return (nodeT == other.nodeT); } } /** Flag for whether to use the method lookup cache. */ private static final boolean USE_CACHE = true; /** 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 theCache; /** The pre-allocated cache key for looking up methods. */ private static final CacheKey theKey; /** The pre-allocated array for passing the node to invoke(). */ private static final Object[] theNode; /** The pre-allocated array for passing the node type to getMethod(). */ private static final Class[] theNodeType; static { if (USE_CACHE) { theCache = new LinkedHashMap(CACHE_CAPACITY, CACHE_LOAD, true) { protected boolean removeEldestEntry(Map.Entry e) { return size() > CACHE_SIZE; } }; theKey = new CacheKey(null, null); } else { theCache = null; theKey = null; } theNode = new Object[] { null }; theNodeType = new Class[] { null }; } /** Create a new visitor. */ public Visitor() { } /** * Find the closest matching <code>visit()</code> method for the * specified node and invoke it. Optionally, a <code>visit()</code> * method may return a value, thus making it possible, for example, * to replace nodes or to construct entirely new trees. If the * <code>visit()</code> method does not return a value, this method * returns <code>null</code>. * * @param node The node. * @return The (optional) return value. * @throws VisitorException * Signals that this visitor does not have a matching * <code>visit()</code> method. * @throws VisitingException * Signals an exceptional condition while visiting the specified * node. */ public Object dispatch(Node node) { Class nodeT = node.getClass(); Method m; if (USE_CACHE) { theKey.visitor = this; theKey.nodeT = nodeT; m = (Method)theCache.get(theKey); if (null == m) { m = getMethod(nodeT); theCache.put(new CacheKey(this, nodeT), m); } } else { m = getMethod(nodeT); } theNode[0] = node; try { return m.invoke(this, theNode); } catch (IllegalAccessException x) { // Shouldn't happen b/c m is public. throw new VisitingException("Exceptional condition while visiting node " + node + " with visitor " + this, x); } catch (IllegalArgumentException x) { // Shouldn't happen b/c this must be an instance of its own class. throw new VisitingException("Exceptional condition while visiting node " + node + " with visitor " + this, x); } catch (ClassCastException x) { throw new VisitingException("Invalid result while from visiting node " + node + " with visitor " + this, x); } catch (InvocationTargetException x) { Throwable cause = x.getCause(); if (cause instanceof VisitingException) { // Rethrow. throw (VisitingException)cause; } else if (cause instanceof VisitorException) { // Rethrow. throw (VisitorException)cause; } else if (null != cause) { throw new VisitingException("Exceptional condition while visiting node " + node + " with visitor " + this, cause); } else { throw new VisitingException("Exceptional condition while visiting node " + node + " with visitor " + this, x); } } } /** * Find the closest matching <code>visit()</code> method for the * specified type of node. The implementation of this method walks * the class chain until it finds an appropriate method. * * @param node The node's class (which must be a subtype of Node). * @throws VisitorException * Signals that this visitor does not have a matching * <code>visit()</code> method. */ private Method getMethod(Class nodeT) { Class visiT = getClass(); Class c = nodeT; Method m = null; do { theNodeType[0] = c; try { m = visiT.getMethod("visit", theNodeType); } catch (NoSuchMethodException x) { // Try the interfaces implemented by c. Class[] interfaces = c.getInterfaces(); for (int i=0; i<interfaces.length; i++) { theNodeType[0] = interfaces[i]; try { m = visiT.getMethod("visit", theNodeType); } catch (NoSuchMethodException xx) { // Ignore. } if (null != m) { break; } } if (null == m) { c = c.getSuperclass(); } } } while ((null == m) && (Object.class != c)); if (null == m) { throw new VisitorException("No method to visit nodes of type " + nodeT.toString() + " in visitor " + this.toString()); } else { return m; } } }