package edu.neu.ccs.demeter.dj; import java.util.*; import java.lang.reflect.*; import edu.neu.ccs.demeter.aplib.EdgeI; public abstract class Visitor { /** This method is called when a traversal begins, before any nodes are visited. The default behavior is to do nothing. */ public void start() { } /** This method is called when a traversal ends, after all nodes have been visited. The default behavior is to do nothing. */ public void finish() { } /** This method is called on the first visitor (i.e. v[0]) after finish() has been called on all visitors in a traversal, and its return value is returned as the return value of the traversal. The default behavior is to do return null. */ public Object getReturnValue() { return null; } /** This method is called when a traversal is about to traverse obj in the object graph with cl as the token. Note that the same object might be passed to this method multiple times, once per class in its inheritance hierarchy. The default behavior is to call {@link #invokeMethod(String, Object, Class) invokeMethod}("before", obj, cl). */ public void before(Object obj, Class cl) { invokeMethod("before", obj, cl); } /** This method is called when a traversal has finished traversing obj in the object graph with cl as the token. Note that the same object might be passed to this method multiple times, once per class in its inheritance hierarchy. The default behavior is to call {@link #invokeMethod(String, Object, Class) invokeMethod}("after", obj, cl). */ public void after(Object obj, Class cl) { invokeMethod("after", obj, cl); } /** This method is called when a traversal is about to traverse an edge in the object graph from source to target corresponding to the class graph edge edge from sourceClass to targetClass. The default behavior is to call {@link #invokeMethods(String, Object, Class, EdgeI, Object, Class) invokeMethods}("before", source, sourceClass, edge, target, targetClass). */ public void before(Object source, Class sourceClass, EdgeI edge, Object target, Class targetClass) { invokeMethods("before", source, sourceClass, edge, target, targetClass); } /** This method is called when a traversal has finished traversing an edge in the object graph from source to target corresponding to the class graph edge edge from sourceClass to targetClass. The default behavior is to call {@link #invokeMethods(String, Object, Class, EdgeI, Object, Class) invokeMethods}("before", source, sourceClass, edge, target, targetClass). */ public void after(Object source, Class sourceClass, EdgeI edge, Object target, Class targetClass) { invokeMethods("after", source, sourceClass, edge, target, targetClass); } /** Invoke the methods on this class whose names start with name and whose signatures correspond to the class graph edge edge, passing source and target (and the edge label, if it's a construction edge) as arguments.

For a construction edge, the following signatures are matched, in order:

  1. n_l(S, T)
  2. n_l(S, Object)
  3. n_l(Object, T)
  4. n_l(Object, Object)
  5. n(S, String, T)
  6. n(S, String, Object)
  7. n(Object, String, T)
  8. n(Object, String, Object)
where n is the value of name, S is the source type of the edge, l is the label of the edge, and T is the target type of the edge. For example, if an edge ->Employee,salary,Currency is traversed, then first the visitor method before_salary(Employee,Currency) is invoked, if it exists, followed by before_salary(Employee,Object), etc. */ public void invokeMethods(String name, Object source, Class sourceClass, EdgeI edge, Object target, Class targetClass) { if (edge.isConstructionEdge()) { // FIXME: repetition edge String label = edge.getLabel(); String longname = name + "_" + label; // FIXME: should it call all of these, or just the first match? // And is this the right order? invokeMethod(longname, source, sourceClass, target, targetClass); invokeMethod(longname, source, sourceClass, target, Object.class); invokeMethod(longname, source, Object.class, target, targetClass); invokeMethod(longname, source, Object.class, target, Object.class); invokeMethod(name, source, sourceClass, label, target, targetClass); invokeMethod(name, source, sourceClass, label, target, Object.class); invokeMethod(name, source, Object.class, label, target, targetClass); invokeMethod(name, source, Object.class, label, target, Object.class); } else { // FIXME: alternation or inheritance edge } } /** Invoke the method on this class named name with one parameter of type cl, passing obj as the argument. */ public void invokeMethod(String name, Object obj, Class cl) { invokeMethod(name, new Object[] { obj }, new Class[] { cl }); } /** Invoke the method on this class named name with two parameters of type cl1 and cl2, passing obj1 and obj2 as the arguments. */ void invokeMethod(String name, Object obj1, Class cl1, Object obj2, Class cl2) { invokeMethod(name, new Object[] { obj1, obj2 }, new Class[] { cl1, cl2 }); } /** Invoke the method on this class named name with three parameters of type cl1, String, and cl2, passing obj1, str, and obj2 as the arguments. */ void invokeMethod(String name, Object obj1, Class cl1, String str, Object obj2, Class cl2) { invokeMethod(name, new Object[] { obj1, str, obj2 }, new Class[] { cl1, String.class, cl2 }); } /** Invoke the method on this class named name with parameter types paramTypes, passing args as the arguments. */ public void invokeMethod(String name, Object args[], Class paramTypes[]) { try { Method meth = getMethod(name, paramTypes); if (meth != null) { meth.setAccessible(true); meth.invoke(this, args); } } catch (SecurityException e) { // print something? return; } catch (IllegalAccessException e) { throw new RuntimeException("\n" + e); } catch (InvocationTargetException e) { throw new VisitorMethodException(e.getTargetException()); } } /** Return the method on this class named name with parameter types paramTypes, or null if there is no such method. */ public Method getMethod(final String name, final Class paramTypes[]) { // Memoize, using the visitor class + method name + param types as the key. final Class visitorClass = getClass(); // it's easier to do equals and hashCode on Lists than arrays. final List paramTypesList = Arrays.asList(paramTypes); class Signature { public boolean equals(Object x) { if (!(x instanceof Signature)) return false; Signature s = (Signature) x; return s.getVisitorClass().equals(getVisitorClass()) && s.getName().equals(getName()) && s.getParamTypes().equals(getParamTypes()); } public int hashCode() { return getVisitorClass().hashCode() + getName().hashCode() + getParamTypes().hashCode(); } Class getVisitorClass() { return visitorClass; } String getName() { return name; } List getParamTypes() { return paramTypesList; } public String toString() { String paramTypesString = null; Iterator it = getParamTypes().iterator(); while (it.hasNext()) { String name = ((Class) it.next()).getName(); if (paramTypesString == null) paramTypesString = name; else paramTypesString += ", " + name; } return getVisitorClass().getName() + "." + getName() + "(" + paramTypesString + ")"; } } Signature key = new Signature(); if (methods.containsKey(key)) return (Method) methods.get(key); Method method = getUnmemoizedMethod(name, paramTypes); methods.put(key, method); return method; } private static Map methods = new HashMap(); /** Return the method on this class named name with parameter types paramTypes, or null if there is no such method. */ protected Method getUnmemoizedMethod(String name, Class paramTypes[]) { // java.lang.Class.getMethod() doesn't do what we need - // it only returns public methods! Bogus! for (Class vcl = getClass(); vcl != null; vcl = vcl.getSuperclass()) { // FIXME: This is expensive, because it has to fill in the stack // trace each time it throws an exception. The alternative is // to use getDeclaredMethods and search through the list, but // that could be even more expensive if there are a lot of // methods. Replace with hasDeclaredMethod if that's ever added // to Java. try { return vcl.getDeclaredMethod(name, paramTypes); } catch (NoSuchMethodException e) { } } return null; } }