Skip to content

GSD

Sections
Personal tools
You are here: Home » Members » jop's Home » mm4j - Simple multi-methods for Java5

mm4j - Simple multi-methods for Java

Document Actions

Provides dynamic dispatch on all method parameters, also known as multi-methods. This is useful, for instance, when implementing event-driven code or structuring a program explicitly as a state-machine. It is likely that we'll have something like this natively in future versions of Java supported by the new invokedynamic opcode.

The latest version of the single implementation file MultiMethod.java and of this document are available at the mm4j homepage. Check the change log for the latest news.

This implementation builds on reflection capabilities in the Java language. First, an exact match is attempted. If it does not exist, super-classes of parameter values are also tested, starting from right to left. Currently, interfaces are not considered, only super-classes. Matched methods are cached and reused thus avoiding that the recursive matching procedure is called repeatedly. This results in a fairly efficient implementation which on the average adds only a single map lookup to the cost of normal method invocation.

Super-classes of right-most parameters are tested first, thus naturally accommodating the native dynamic dispatch on this as the left-most parameter. To better understand searching order, consider the following sample program:

 import java.lang.reflect.*;
 import mm4j.*;
 
 public class Test {
   public void m(String a, Integer b) {
     System.out.println("m(String,Integer) with "+
       a.getClass().getName()+" "+b.getClass().getName());
   }
   public void m(Object a, Integer b) {
     System.out.println("m(Object,Integer) with "+
       a.getClass().getName()+" "+b.getClass().getName());
   }
   public void m(String a, Object b) {
     System.out.println("m(String,Object) with "+
       a.getClass().getName()+" "+b.getClass().getName());
   }
   public void m(Object a, Object b) {
     System.out.println("m(Object,Object) with "+
       a.getClass().getName()+" "+b.getClass().getName());
   }
 
   public static void main(String[] args) {
     try {
       Test t=new Test();
       MultiMethod mm=MultiMethod.getMultiMethod(t.getClass(), "m");
       
       mm.invoke(t, "a", 2);
       mm.invoke(t, "a", "b");
       mm.invoke(t, 1, "b");
       mm.invoke(t, 1,2);
 
       Method m=mm.resolve(String.class, Object.class);
       m.invoke(t, "a", 1);
       m=mm.resolve(String.class, String.class);
       m.invoke(t, "a", 1);
     } catch(Exception e) {e.printStackTrace();}
   }
 };
Notice that it uses the auto-boxing in Java5 for integers. The sample program should produce the following output:
 m(String,Integer) with java.lang.String java.lang.Integer
 m(String,Object) with java.lang.String java.lang.String
 m(Object,Object) with java.lang.Integer java.lang.String
 m(Object,Object) with java.lang.Integer java.lang.Integer
 m(String,Object) with java.lang.String java.lang.Integer
 m(String,Object) with java.lang.String java.lang.Integer
Contrast the result of the second invocation, which has (String, String) parameters and matches (String, Object), with the result of the fourth invocation, which has (Integer, Integer) parameters but matches (Object, Object) and not (Object, Integer).

A particularly interesting idiom is achieved by using a Java5 variable parameter list to wrap dynamic invocation and exception handling as follows:

 import java.lang.reflect.*;
 import java.io.*;
 import mm4j.*;

 interface Idiom {
   public void m(Object...) throws NoSuchMethodException, IOException;
 }
 
 public class IdiomImpl implements Idiom {
   // Alternatives
   public void m(Integer a) {  }
   public void m(String a, Object b) throws IOException {  }
   public void m(Integer a, String b, Object c) {  }
 
   // Wrapper
   public void m(Object... args) throws NoSuchMethodException, IOException {
     try {
       mm.invoke(this, args);
     } catch(InvocationTargetException e) {
       Throwable target=e.getTargetException();
       if (target instanceof IOException)
         throw (IOException)target;
       else if (target instanceof RuntimeException)
         throw (RuntimeException)target;
     
       target.printStackTrace();
     } catch(IllegalAccessException e) {
       e.printStackTrace();
     }
   }
   private static MultiMethod mm=MultiMethod.getMultiMethod(IdiomImpl.class, "m");
 
   // Sample usage
   public static void main(String[] args) {
     try {
       Idiom t=new IdiomImpl();
 
       t.m(3);
       t.m("a", "b");
       t.m(1, "b", 3f);
     } catch(Exception e) {e.printStackTrace();}
   }
 };
Most of the effort is related to unwrapping InvocationTargetExceptions to each of the possibilities. The resulting client code, in main, is however very user friendly.
  mm4j.MultiMethod - Simple multi-methods for Java
  Copyright (C) 2005  Jose Orlando Pereira <jop@di.uminho.pt>
  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 the University of Minho 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.


Changes:
  • 20051125 - Bug fix for null pointer exceptions in weak hash maps.
  • 20051115 - Initial public release.

Method Summary
 Class getDeclaringClass()
          Returns the Class object representing the class or interface that declares the method family represented by this MultiMethod object.
static MultiMethod getMultiMethod(Class claz, String name)
          Lookup a dynamic method in a specific class.
 String getName()
          Returns the name of the target methods as a String.
 Object invoke(Object obj, Object... args)
          Invokes the method represented by this MultiMethod object that better fits the specified parameters on the specified object.
 java.lang.reflect.Method resolve(Class... types)
          Resolves the method represented by this MultiMethod object that better fits the specified parameters types.
 String toString()
           
 
Methods inherited from class Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Method Detail

getMultiMethod

public static MultiMethod getMultiMethod(Class claz,
                                         String name)
Lookup a dynamic method in a specific class. This method can be used for invocations on instances of any derived class. No validation is performed at this time and therefore there is no guarantee that invocations will succeed, not even that there is a method with the same name.

Parameters:
claz - a class, or superclass, of the target objects
name - the name of the target method
Returns:
a dynamic method object

invoke

public final Object invoke(Object obj,
                           Object... args)
                    throws IllegalAccessException,
                           NoSuchMethodException,
                           java.lang.reflect.InvocationTargetException
Invokes the method represented by this MultiMethod object that better fits the specified parameters on the specified object. The same effect can be achieved by first resolving the method and then using invoke. Note that this method cannot be used to invoke target methods with primitive or null arguments.

Parameters:
obj - the target object instance
args - the argument values
Returns:
the return value of the invoked method
Throws:
NoSuchMethodException - no matching method found
IllegalAccessException - matching method exists but cannot be accessed
java.lang.reflect.InvocationTargetException - nested exception by target

resolve

public final java.lang.reflect.Method resolve(Class... types)
                                       throws NoSuchMethodException
Resolves the method represented by this MultiMethod object that better fits the specified parameters types. The result can be used repeatedly to avoid the overhead of matching. This method is also able to resolve methods declared with primitive arguments. The resulting method can also be invoked with null arguments.

Parameters:
types - the classes of the argument of the method to be searched
Returns:
a specific Method object
Throws:
NoSuchMethodException - no matching method found

getDeclaringClass

public Class getDeclaringClass()
Returns the Class object representing the class or interface that declares the method family represented by this MultiMethod object.

Returns:
the declaring class

getName

public String getName()
Returns the name of the target methods as a String.

Returns:
the name of the method

toString

public String toString()
Overrides:
toString in class Object

Created by jop
Last modified 2005-11-25 11:04 AM
 

Powered by Plone

This site conforms to the following standards: