Skip to content

Instantly share code, notes, and snippets.

@marcus-nl
Created August 15, 2014 15:32
Show Gist options
  • Save marcus-nl/6ba026980d404a9b0e4f to your computer and use it in GitHub Desktop.
Save marcus-nl/6ba026980d404a9b0e4f to your computer and use it in GitHub Desktop.
Dynamic Visitor Pattern
public abstract class DynamicVisitor {
private final Class<? extends DynamicVisitor> thisClass;
private final MethodMap methods;
protected DynamicVisitor() {
this.thisClass = getClass();
this.methods = new MethodMap();
for (Method m : thisClass.getMethods()) {
if ("visit".equals(m.getName())) {
methods.add(m);
}
}
}
public Object traverse(Object element) {
if (element == null) {
throw new IllegalArgumentException("Element is null");
}
Method m = findMethod(element);
if (m == null) {
return noSuchMethod(element);
}
try {
return m.invoke(this, element);
}
catch (IllegalArgumentException e) {
throw e;
}
catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
throw new RuntimeException(t.getMessage(), t);
}
}
private Method findMethod(Object element) {
Class<?> clazz = element.getClass();
return methods.find(clazz);
}
protected Object noSuchMethod(Object element) {
throw new IllegalArgumentException("No visit method for element " + element);
}
}
class MethodMap {
private final MethodMapNode root;
public MethodMap() {
this.root = new MethodMapNode(Object.class, null);
}
public void add(Method m) {
Class<?> pc = m.getParameterTypes()[0];
MethodMapNode node = getNode(pc);
node.setMethod(m);
}
private MethodMapNode getNode(Class<?> clazz) {
if (clazz.equals(Object.class)) {
return root;
}
MethodMapNode superNode = getNode(clazz.getSuperclass());
return superNode.getSub(clazz);
}
public Method find(Class<?> clazz) {
return root.find(clazz);
}
public void print(Printer printer) {
root.print(printer);
}
}
class MethodMapNode {
private final Class<?> clazz;
private final Map<Class<?>, MethodMapNode> subs;
private Method method;
public MethodMapNode(Class<?> clazz, Method method) {
this.clazz = clazz;
this.method = method;
this.subs = new HashMap<Class<?>, MethodMapNode>();
}
public MethodMapNode(Class<?> clazz) {
this(clazz, null);
}
public boolean isAssignableFrom(Class<?> c) {
return clazz.isAssignableFrom(c);
}
public Method getMethod() {
return method;
}
public void setMethod(Method m) {
if (method != null) {
throw new IllegalArgumentException("Duplicate method for parameter type " + clazz);
}
method = m;
}
public MethodMapNode getSub(Class<?> c) {
MethodMapNode sub = subs.get(c);
if (sub == null) {
sub = new MethodMapNode(c);
subs.put(c, sub);
}
return sub;
}
public Method find(Class<?> c) {
for (MethodMapNode sub : subs.values()) {
if (sub.isAssignableFrom(c)) {
return sub.find(c);
}
}
return method;
}
public void print(Printer printer) {
printer.print("Node[class=" + clazz + ", method=" + method + "]:");
printer.indent();
for (MethodMapNode sub : subs.values()) {
sub.print(printer);
}
printer.outdent();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment