Created
August 15, 2014 15:32
-
-
Save marcus-nl/6ba026980d404a9b0e4f to your computer and use it in GitHub Desktop.
Dynamic Visitor Pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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