When searching for the correct method, Java does not consider the type of execution time of the method arguments. Java just talks about variable types instead of values.
Decision:
Java 6 annotations can be used to annotate methods and implement multimethods and send values. All this can be done at runtime without the need for any special compilation or preprocessing, and use can still be reasonably convenient.
We need to introduce two “simple” annotations for annotation:
Methods : which method uses this method?
Parameters : What value should we send?
Then we can process annotations and create a list of methods that implement a specific multimethod. This list needs to be sorted so that the most specific methods are applied first. “Most specific” means that for each method parameter (from left to right) the type / value of the parameter is more specialized (for example, it is a subclass or it matches the specified value). Calling a multimethod means calling the most specific applicable method. “Applicable” means that the prototype of the method matches the actual runtime arguments, and “most specific” means that we can just search the sorted list and find the first one that is applicable.
Annotation processing can be completed in a class, which can then be used in a user-defined method that simply invokes a multi-point code submission code with actual runtime arguments.
Implementation
The Multi interface implements the run-time method annotation used to indicate multimethods:
package jmultimethod; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Retention; import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Multi { public String value(); }
Interface V implements a run-time parameter annotation used to specify send values:
package jmultimethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface V { public String value(); }
The following is the Multimethod code:
package jmultimethod; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; public class Multimethod { protected String name; protected final ArrayList<Method> methods = new ArrayList<Method>(); 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<Method> { @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 + "'."); } } }
To use multimethods, the user code must:
Explain methods with a multimethod name, for example
@Multi("myMultimethod")
The name of annotated methods can be anything Java is happy with. Most likely, this will differ from the name of the multimethod, because some methods may have a prototype similar enough to cause name conflicts for the Java compiler (and, possibly, because the compiler may have problems with a null value). In addition, this method must be visible (e.g., public) to the Multimethod class.
Process annotations by creating a Multimethod object, for example
protected Multimethod mm = new Multimethod("myMultimethod", getClass());
Define an entry point method using several methods with the required parameters. This method is dispatched using the Multimethod object created above, for example.
public void myMultimethod(Object X, Object Y) { mm.invoke(this, X, Y); }
And then, the multimethod can be called like any normal Java method, for example
myMultimethod (1, null);
Limitations:
Passing values only works with values supported by Java annotations, for example. values of type String.
The following code is an example of multiple sending
package jmultimethod; public class AsteroidTest { class Asteroid {} class Spaceship {} @Multi("collide") public void collideOO(Object X, Object Y) { log("?? Bang, what happened? ", X, Y); } @Multi("collide") public void collideAA(Asteroid X, Asteroid Y) { log("AA Look at the beautiful fireworks! ", X, Y); } @Multi("collide") public void collideAS(Asteroid X, Spaceship Y) { log("AS Is it fatal? ", X, Y); } @Multi("collide") public void collideSA(Spaceship X, Asteroid Y) { log("SA Is it fatal? ", X, Y); } @Multi("collide") public void collideSS(Spaceship X, Spaceship Y) { log("SS Who fault was it? ", X, Y); } @Multi("collide") public void collide1S(String X, Spaceship Y) { log("1S any string? ", X, Y); } @Multi("collide") public void collide2S(@V("hi") String X, Spaceship Y) { log("2S 'hi' value? ", X, Y); } protected Multimethod mm = new Multimethod("collide", getClass()); public void collide(Object X, Object Y) { mm.invoke(this, X, Y); } public void run() { Object A = new Asteroid(); Object S = new Spaceship(); collide(A, A); collide(A, S); collide(S, A); collide(S, S); collide(A, 1); collide(2, A); collide(S, 3); collide(4, S); collide(5, null); collide(null, null); collide("hi", S); collide("hello", S); } public void log(Object... args) { for(Object o: args) { if(o instanceof String) { System.out.print(" " + (String) o); } else { System.out.print(" " + o); } } System.out.println(); } public static void main(String[] args) throws Exception { AsteroidTest t = new AsteroidTest(); t.run(); } }
Program output (partially edited for installation on the screen):
AA Look at the beautiful fireworks! Asteroid@1f24bbbf Asteroid@1f24bbbf AS Is it fatal? Asteroid@1f24bbbf Spaceship@24a20892 SA Is it fatal? Spaceship@24a20892 Asteroid@1f24bbbf SS Who fault was it? Spaceship@24a20892 Spaceship@24a20892 ?? Bang, what happened? Asteroid@1f24bbbf 1 ?? Bang, what happened? 2 Asteroid@1f24bbbf ?? Bang, what happened? Spaceship@24a20892 3 ?? Bang, what happened? 4 Spaceship@24a20892 ?? Bang, what happened? 5 null ?? Bang, what happened? null null 2S 'hi' value? hi Spaceship@24a20892 1S any string? hello Spaceship@24a20892