commit 28656b6834f84b0b588f103aa9c9d7f22af2a510
parent 664692d255cef1169739e18232e86927ec703381
Author: Tomas Hlavaty <tom@logand.com>
Date: Fri, 25 Dec 2015 22:35:58 +0100
import from tarball
Diffstat:
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();
+}