Created
January 23, 2009 17:14
-
-
Save fogus/51089 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// Totally stolen from http://logand.com/sw/jmultimethod/ | |
// Multi.java | |
@Target(ElementType.METHOD) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Multi { | |
public String value(); | |
} | |
// V.java | |
@Target(ElementType.PARAMETER) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface V { | |
public String value(); | |
} | |
// Multimethod.java | |
public class Multimethod { | |
protected String name; | |
protected final ArrayList methods = new ArrayList(); | |
protected final MethodComparator methodComparator = new MethodComparator(); | |
public Multimethod(String name, Class... classes) { | |
this.name = name; | |
for(Class c: classes) { | |
add(c); | |
} | |
} | |
public void add(Class c) { | |
for(Method m: c.getMethods()) { | |
for(Annotation ma: m.getAnnotations()) { | |
if(ma instanceof Multi) { | |
Multi g = (Multi) ma; | |
if(this.name.equals(g.value())) { | |
methods.add(m); | |
} | |
} | |
} | |
} | |
sort(); | |
} | |
protected void sort() { | |
Method[] a = new Method[methods.size()]; | |
methods.toArray(a); | |
Arrays.sort(a, methodComparator); | |
methods.clear(); | |
for(Method m: a) { | |
methods.add(m); | |
} | |
} | |
protected class MethodComparator implements Comparator { | |
@Override | |
public int compare(Method l, Method r) { | |
// most specific methods first | |
Class[] lc = l.getParameterTypes(); | |
Class[] rc = r.getParameterTypes(); | |
for(int i = 0; i < lc.length; i++) { | |
String lv = value(l, i); | |
String rv = value(r, i); | |
if(lv == null) { | |
if(rv != null) { | |
return 1; | |
} | |
} | |
if(lc[i].isAssignableFrom(rc[i])) { | |
return 1; | |
} | |
} | |
return -1; | |
} | |
} | |
protected String value(Method method, int arg) { | |
Annotation[] a = method.getParameterAnnotations()[arg]; | |
for(Annotation p: a) { | |
if(p instanceof V) { | |
V v = (V) p; | |
return v.value(); | |
} | |
} | |
return null; | |
} | |
protected boolean isApplicable(Method method, Object... args) { | |
Class[] c = method.getParameterTypes(); | |
for(int i = 0; i < c.length; i++) { | |
// must be instanceof and equal to annotated value if present | |
if(c[i].isInstance(args[i])) { | |
String v = value(method, i); | |
if(v != null && !v.equals(args[i])) { | |
return false; | |
} | |
} else { | |
if(args[i] != null || !Object.class.equals(c[i])) { | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
public Object invoke(Object self, Object... args) { | |
Method m = null; // first applicable method (most specific) | |
for(Method method: methods) { | |
if(isApplicable(method, args)) { | |
m = method; | |
break; | |
} | |
} | |
if(m == null) { | |
throw new RuntimeException("No applicable method '" + name + "'."); | |
} | |
try { | |
return m.invoke(self, args); | |
} catch (Exception e) { | |
throw new RuntimeException("Method invocation failed '" + name + "'."); | |
} | |
} | |
} | |
/* | |
1. Annotate methods with the multimethod name, e.g. | |
@Multi("myMultimethod") | |
The name of the annotated methods can be anything Java is happy with. It is most likely going to be different from the multimethod name because some methods can have prototype similar enough to cause name clashes for the Java compiler (and maybe because the compiler could have problems with the null value). Also, the method should be visible (e.g. public) to the Multimethod class. | |
2. Process the annotations by creating the Multimethod object, e.g. | |
protected Multimethod mm = new Multimethod("myMultimethod", getClass()); | |
3. Define multimethod "entry point" method with parameters as general as necessary. This method dispatches using the Multimethod object created above, e.g. | |
public void myMultimethod(Object X, Object Y) { | |
mm.invoke(this, X, Y); | |
} | |
4. And then, the multimethod can be called as any normal Java method, e.g. | |
myMultimethod(1, null); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment