jmultimethod

multimethods for Java using annotations
git clone https://logand.com/git/jmultimethod.git/
Log | Files | Refs | LICENSE

commit 28656b6834f84b0b588f103aa9c9d7f22af2a510
parent 664692d255cef1169739e18232e86927ec703381
Author: Tomas Hlavaty <tom@logand.com>
Date:   Fri, 25 Dec 2015 22:35:58 +0100

import from tarball

Diffstat:
A.classpath | 6++++++
A.project | 17+++++++++++++++++
A.settings/org.eclipse.jdt.core.prefs | 12++++++++++++
Aindex.html | 599+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/jmultimethod/AsteroidTest.java | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/jmultimethod/CarTest.java | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/jmultimethod/DispatchTest.java | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/jmultimethod/Multi.java | 13+++++++++++++
Asrc/jmultimethod/Multimethod.java | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/jmultimethod/V.java | 13+++++++++++++
10 files changed, 1068 insertions(+), 0 deletions(-)

diff --git a/.classpath b/.classpath @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/.project b/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>jmultimethod</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Thu Jan 22 15:57:03 GMT 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/index.html b/index.html @@ -0,0 +1,599 @@ +<html> +<head> +<title>Multimethods and value dispatch with Java annotations in 140 LoC</title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<meta name="keywords" content="java, object oriented, multi-methods, +value dispatch, method"></meta> +<meta name="description" content="jmultimethod provides multimethods +and value dispatch for Java."></meta> +<style> +<!-- +.java { + border-left: 1pt dashed gray; + padding-left: 1em; +} +--> +</style> +</head> +<body> + +<h2>jmultimethod</h2> + +<p>22jan2009 <a href="http://logand.com/sw/jmultimethod/">http://logand.com/sw/jmultimethod/</a></p> + +<h3>Multimethods and value dispatch with Java annotations in 140 LoC</h3> + +<p>Quoting +from <a href="http://en.wikipedia.org/wiki/Multiple_dispatch">Wikipedia<a>:</p> + +<blockquote>Multiple dispatch or multimethods is the feature of some +object-oriented programming languages in which a function or method +can be dynamically dispatched based on the run time (dynamic) type of +more than one of its arguments.</blockquote> + +<p>The <a href="http://en.wikipedia.org/wiki/Visitor_pattern">Visitor +pattern</a> can be used in single-dispatch object-oriented +languages.</p> + +<p>Peter Norvig's presentation about +<a href="http://norvig.com/design-patterns/">Design Patterns in +Dynamic Languages</a> gives a brief insight into multimethods, +especially +slides <a href="http://norvig.com/design-patterns/img023.gif">The +Type/Operation Matrix</a> +and <a href="http://norvig.com/design-patterns/img024.gif">Multimethods</a>.</p> + +<p>Multimethods are huge step forward on the path towards the ultimate +extensibility and "clean" code structure.</p> + +<p>Java is a single-dispatch object-oriented languages and even though +Java does not allow for syntactic abstraction, Java annotations can be +used to implement multimethods. The following code requires Java +6.</p> + +<p>jmultimethod implements multiple displatch in a similar way I have +implemented <a href="http://logand.com/picoWiki/multimethods">multimethods +in PicoLisp</a> recently.</p> + +<h3>Idea</h3> + +<p>Java 6 annotations can be used to annotate methods and implement +multimethods and value dispatch. All this can be done at runtime +without the need for any special compilation or preprocessing and the +usage can still be reasonably user-friendly.</p> + +<p>We need to introduce two "simple" annotations to annotate:</p> + +<ul> + +<li>methods: What multimethod this method implements?</li> + +<li>parameters: What value should we dispatch on?</li> + +</ul> + +<p>We can then process the annotations and build a list of methods +that implement a particular multimethod. This list needs to be sorted +so that the most specific methods come first. "Most specific" means +that for each method parameter (from left to right), the parameter +type/value is more specialized (e.g. it is a subclass or it is matched +agains the specified value). Calling a multimethod means invoking the +most specific applicable method. "Applicable" means that the method +prototype matches the actual runtime arguments and "the most specific" +means that we can simply search through the sorted list and find the +first one which is applicable.</p> + +<p>Annotation processing can be wrapped up in a class which can then +be used in a user defined method that will simply invoke the +multimethod dispatch code with the actual runtime arguments.</p> + +<h3>Implementation</h3> + +<p>The interface <tt>Multi</tt> implements a runtime method annotation +used to mark multimethods:</p> + +<pre class="java"> +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Multi { + + public String value(); +} +</pre> + +<p>The interface <tt>V</tt> implements a runtime parameter annotation +used to specify dispatch values:</p> + +<pre class="java"> +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface V { + + public String value(); +} +</pre> + +The <tt>Multimethod</tt> code follows: + +<pre class="java"> +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 + "'."); + } + } +} +</pre> + +<p>To use multimethods, user code must:</p> + +<ol> + +<li>Annotate methods with the multimethod name, e.g. +<pre>@Multi("myMultimethod")</pre> 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 <tt>null</tt> value). Also, the method should be visible +(e.g. public) to the <tt>Multimethod</tt> class. +</li> + +<li>Process the annotations by creating the <tt>Multimethod</tt> +object, e.g. +<pre>protected Multimethod mm = new Multimethod("myMultimethod", getClass());</pre> +</li> + +<li>Define multimethod "entry point" method with parameters as general +as necessary. This method dispatches using the <tt>Multimethod</tt> +object created above, e.g. +<pre> +public void myMultimethod(Object X, Object Y) { + mm.invoke(this, X, Y); +} +</pre></li> + +<li>And then, the multimethod can be called as any normal Java method, +e.g. +<pre>myMultimethod(1, null);</pre></li> + +</ol> + +<p>Limitations:</p> + +<ul> + +<li>Value dispatch works only with values supported by Java +annotations, e.g. values of type <tt>String</tt>.</li> + +</ul> + +<h3>Asteroid Example</h3> + +<p>The following code is based on +the <a href="http://en.wikipedia.org/wiki/Multiple_dispatch">Multiple +dispatch example</a> from Wikipedia.</a> + +<pre class="java"> +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's 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(); + } +} +</pre> + +<p>The program output (partially edited to fit on the screen) is:</p> + +<pre> +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's 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 +</pre> + +<h3>Car Example</h3> + +<p>The following code is based on +the <a href="http://en.wikipedia.org/wiki/Visitor_pattern">Visitor +pattern example</a> from Wikipedia.</a> + +<pre class="java"> +public class CarTest { + + interface CarElement {} + + class Wheel implements CarElement { + private String name; + Wheel(String name) { + this.name = name; + } + String getName() { + return this.name; + } + } + + class Engine implements CarElement {} + + class Body implements CarElement {} + + class Car { + CarElement[] elements; + public CarElement [] getElements() { + return elements.clone(); + } + public Car() { + this.elements = new CarElement[] + { new Wheel("front left"), new Wheel("front right"), + new Wheel("back left") , new Wheel("back right"), + new Body(), new Engine()}; + } + } + + @Multi("visit") + public void visitP(Wheel wheel, @V("print") String mode) { + System.out.println("Visiting "+ wheel.getName() + " wheel"); + } + + @Multi("visit") + public void visitP(Engine engine, @V("print") String mode) { + System.out.println("Visiting engine"); + } + + @Multi("visit") + public void visitP(Body body, @V("print") String mode) { + System.out.println("Visiting body"); + } + + @Multi("visit") + public void visitP(Car car, @V("print") String mode) { + System.out.println("\nVisiting car"); + for(CarElement element : car.getElements()) { + visit(element, mode); + } + System.out.println("Visited car"); + } + + @Multi("visit") + public void visitD(Wheel wheel, @V("do") String mode) { + System.out.println("Kicking my "+ wheel.getName()); + } + + @Multi("visit") + public void visitD(Engine engine, @V("do") String mode) { + System.out.println("Starting my engine"); + } + + @Multi("visit") + public void visitD(Body body, @V("do") String mode) { + System.out.println("Moving my body"); + } + + @Multi("visit") + public void visitD(Car car, @V("do") String mode) { + System.out.println("\nStarting my car"); + for(CarElement element : car.getElements()) { + visit(element, mode); + } + System.out.println("Started car"); + } + + protected Multimethod mm = new Multimethod("visit", getClass()); + + public void visit(Object any, String mode) { + mm.invoke(this, any, mode); + } + + public void run() { + Car car = new Car(); + visit(car, "print"); + visit(car, "do"); + } + + static public void main(String[] args){ + CarTest t = new CarTest(); + t.run(); + } +} +</pre> + +<p>The program output is:</p> + +<pre> +Visiting car +Visiting front left wheel +Visiting front right wheel +Visiting back left wheel +Visiting back right wheel +Visiting body +Visiting engine +Visited car + +Starting my car +Kicking my front left +Kicking my front right +Kicking my back left +Kicking my back right +Moving my body +Starting my engine +Started car +</pre> + +<p>For this example, it would probably be even better to remove +the <tt>mode</tt> parameter and have two independent multimethods for +each mode as the parameter space is not sparse but completely covered +by specific methods.</p> + +<h3>Other ideas</h3> + +<p>One useful thing missing in this implementation is possibility of +calling next applicable method, +i.e. <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_call_n.htm#call-next-method">call-next-method</a> +or something like <tt>super()</tt> in the multimethod context. +However, the question is whether an implementation of such feature +would be simple and usable enough, due to lack of syntactic +abstraction and dynamic scope in Java.</p> + +<p>Other things like around, before and after methods or custom method +combinations as implemented in +the <a href="http://en.wikipedia.org/wiki/CLOS">Common Lisp Object +System</a> are out of scope here and are better addressed +by <a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming">Aspect-oriented +programming</a> frameworks which step away from standard Java.</p> + +<h3>More References</h3> + +<p>The ultimate object system to learn is +the <a href="http://en.wikipedia.org/wiki/CLOS">Common Lisp Object +System</a>. <a href="http://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Protocol">The +Art of the Metaobject Protocol</a> offers interesting reading about +it.</p> + +<p>A nice programming +language <a href="http://nice.sourceforge.net/">Nice</a> has some +interesting points +about <a href="http://nice.sourceforge.net/visitor.html">multimethods +and the Visitor pattern</a>.</p> + +<p><a href="http://gsd.di.uminho.pt/members/jop/mm4j/">mm4j</a> is +another minimalistic implementation of multimethods in Java. It +relies purely on Java reflection and does not have value dispatch so +the resulting implementation and usage is rather different from the +one described here.</p> + +<h3>Download</h3> + +<p>The latest jmultimethod tarball release can be +downloaded <a href="../jmultimethod.tar.gz">here</a>. + +<pre> +<a href="src/jmultimethod/AsteroidTest.java">AsteroidTest.java</a> +<a href="src/jmultimethod/CarTest.java">CarTest.java</a> +<a href="src/jmultimethod/Multi.java">Multi.java</a> +<a href="src/jmultimethod/Multimethod.java">Multimethod.java</a> +<a href="src/jmultimethod/V.java">V.java</a> +</pre> + +<h3>Licence</h3> + +<p>jmultimethod is open source software, made available under a BSD +license.</p> + +<pre> +Copyright (c) 2009, Tomas Hlavaty +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. Redistributions +in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of jmultimethod nor the names of its contributors may +be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +</pre> + +<h3>Feedback</h3> + +<p>Please send email to jmultimethod at logand dot com.</p> + +</body> +</html> diff --git a/src/jmultimethod/AsteroidTest.java b/src/jmultimethod/AsteroidTest.java @@ -0,0 +1,84 @@ +// http://en.wikipedia.org/wiki/Multiple_dispatch + +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's 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(); + } +} diff --git a/src/jmultimethod/CarTest.java b/src/jmultimethod/CarTest.java @@ -0,0 +1,100 @@ +// http://en.wikipedia.org/wiki/Visitor_pattern + +package jmultimethod; + +public class CarTest { + + interface CarElement {} + + class Wheel implements CarElement { + private String name; + Wheel(String name) { + this.name = name; + } + String getName() { + return this.name; + } + } + + class Engine implements CarElement {} + + class Body implements CarElement {} + + class Car { + CarElement[] elements; + public CarElement [] getElements() { + return elements.clone(); + } + public Car() { + this.elements = new CarElement[] + { new Wheel("front left"), new Wheel("front right"), + new Wheel("back left") , new Wheel("back right"), + new Body(), new Engine()}; + } + } + + @Multi("visit") + public void visitP(Wheel wheel, @V("print") String mode) { + System.out.println("Visiting "+ wheel.getName() + " wheel"); + } + + @Multi("visit") + public void visitP(Engine engine, @V("print") String mode) { + System.out.println("Visiting engine"); + } + + @Multi("visit") + public void visitP(Body body, @V("print") String mode) { + System.out.println("Visiting body"); + } + + @Multi("visit") + public void visitP(Car car, @V("print") String mode) { + System.out.println("\nVisiting car"); + for(CarElement element : car.getElements()) { + visit(element, mode); + } + System.out.println("Visited car"); + } + + @Multi("visit") + public void visitD(Wheel wheel, @V("do") String mode) { + System.out.println("Kicking my "+ wheel.getName()); + } + + @Multi("visit") + public void visitD(Engine engine, @V("do") String mode) { + System.out.println("Starting my engine"); + } + + @Multi("visit") + public void visitD(Body body, @V("do") String mode) { + System.out.println("Moving my body"); + } + + @Multi("visit") + public void visitD(Car car, @V("do") String mode) { + System.out.println("\nStarting my car"); + for(CarElement element : car.getElements()) { + visit(element, mode); + } + System.out.println("Started car"); + } + + protected Multimethod mm = new Multimethod("visit", getClass()); + + public void visit(Object any, String mode) { + mm.invoke(this, any, mode); + } + + public void run() { + Car car = new Car(); + visit(car, "print"); + visit(car, "do"); + } + + static public void main(String[] args){ + CarTest t = new CarTest(); + t.run(); + } +} diff --git a/src/jmultimethod/DispatchTest.java b/src/jmultimethod/DispatchTest.java @@ -0,0 +1,110 @@ +// FLAW: Java reasons about types of variables instead of values + +// for asteroid example see wikipedia visitor pattern + +public class Dispatch { + static class Asteroid { + String id; + public Asteroid(String id) {this.id = id;} + public String id() {return id;} + } + static class Spaceship { + String name; + public Spaceship(String name) {this.name = name;} + public String name() {return name;} + } + static class Static { + static void colide(Asteroid x, Asteroid y) { + System.out.println("AA colide " + x.id() + " with " + y.id()); + } + static void colide(Asteroid x, Spaceship y) { + System.out.println("AS colide " + x.id() + " with " + y.name()); + } + static void colide(Spaceship x, Asteroid y) { + System.out.println("SA colide " + x.name() + " with " + y.id()); + } + static void colide(Spaceship x, Spaceship y) { + System.out.println("SS colide " + x.name() + " with " + y.name()); + } + static void colide(Object x, Object y) { + System.out.println("OO colide " + x + " with " + y); + } + static void run() { + run1(); + run2(); + } + static void run1() { + System.out.println("Static: explicitly typed"); + Asteroid a1 = new Asteroid("A1"); + Asteroid a2 = new Asteroid("A2"); + Spaceship s1 = new Spaceship("S1"); + Spaceship s2 = new Spaceship("S2"); + colide(a1, a2); + colide(a1, s1); + colide(s1, a1); + colide(s1, s2); + } + static void run2() { + System.out.println("Static: superclass typed"); + // here is a flaw: the variable type should not be + // specified, it should be infered by the compiler instead + Object a1 = new Asteroid("A1"); + Object a2 = new Asteroid("A2"); + Object s1 = new Spaceship("S1"); + Object s2 = new Spaceship("S2"); + colide(a1, a2); + colide(a1, s1); + colide(s1, a1); + colide(s1, s2); + } + } + static class Dynamic { + void colide(Asteroid x, Asteroid y) { + System.out.println("AA colide " + x.id() + " with " + y.id()); + } + void colide(Asteroid x, Spaceship y) { + System.out.println("AS colide " + x.id() + " with " + y.name()); + } + void colide(Spaceship x, Asteroid y) { + System.out.println("SA colide " + x.name() + " with " + y.id()); + } + void colide(Spaceship x, Spaceship y) { + System.out.println("SS colide " + x.name() + " with " + y.name()); + } + void colide(Object x, Object y) { + System.out.println("OO colide " + x + " with " + y); + } + void run() { + run1(); + run2(); + } + void run1() { + System.out.println("Dynamic: explicitly typed"); + Asteroid a1 = new Asteroid("A1"); + Asteroid a2 = new Asteroid("A2"); + Spaceship s1 = new Spaceship("S1"); + Spaceship s2 = new Spaceship("S2"); + colide(a1, a2); + colide(a1, s1); + colide(s1, a1); + colide(s1, s2); + } + void run2() { + System.out.println("Dynamic: superclass typed"); + // here is the flaw again: dispatch is on variable type + // instead of value type + Object a1 = new Asteroid("A1"); + Object a2 = new Asteroid("A2"); + Object s1 = new Spaceship("S1"); + Object s2 = new Spaceship("S2"); + colide(a1, a2); + colide(a1, s1); + colide(s1, a1); + colide(s1, s2); + } + } + public static void main(String args[]) { + Static.run(); + new Dynamic().run(); + } +} diff --git a/src/jmultimethod/Multi.java b/src/jmultimethod/Multi.java @@ -0,0 +1,13 @@ +package jmultimethod; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Multi { + + public String value(); +} diff --git a/src/jmultimethod/Multimethod.java b/src/jmultimethod/Multimethod.java @@ -0,0 +1,114 @@ +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 + "'."); + } + } +} diff --git a/src/jmultimethod/V.java b/src/jmultimethod/V.java @@ -0,0 +1,13 @@ +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(); +}