Skip to content

Instantly share code, notes, and snippets.

@fogus
Created January 23, 2009 17:14
Show Gist options
  • Save fogus/51089 to your computer and use it in GitHub Desktop.
Save fogus/51089 to your computer and use it in GitHub Desktop.
// 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