Java Selector

pierreth 0 Tallied Votes 114 Views Share

This class implements a mechanism to encapsulate a selector. A
selector is method signature descriptor. It is an easy way to create what may be called function pointers like in C language. I took my inspiration from Objective-C.

// File name: Message.java

/*

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 NeoData Inc. 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 REGENTS 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.

*/

package util;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>This class implements a mechanism to encapsulate a selector. A 
 * selector is like a method signature but the return type is
 * optional. This enables to dynamically call the method on an 
 * instance or a class that match the selector.</p>
 * 
 * <p>The idea is borrowed from the Objective-C language. In this language,
 * we can encapsulate a message inside an object called a selector and
 * later on use the selector to dispatch the message on some object.</p>
 * 
 * <p>Here is an example:</p>
 * 
 * <code>
 * Class A { <br/>
 *     public void foo(String param) { <br/>
 *         // some implementation... <br/>
 *     } <br/>
 * } <br/>
 * <br/>
 * // Let's create a Selector:<br/>
 * Selector foo = Selector.newSelector("foo", String.class);<br/>
 * <br/>
 * // Let's create an A:<br/>
 * A a = new A();<br/>
 * <br/>
 * // and call foo from 'a':<br/>
 * foo.invoke(a, "Some string...");<br/>
 * </code>
 * 
 * <br/><hr/>
 * 
 * <p>This is an Java alternative to the missing C function pointers.</p>
 * 
 * <p>Note that the methods newSelector and invoke are overloaded as a
 * conveniance.</p>
 * 
 * <p>Instances of the class are immutable.</p>
 * 
 * @author  Pierre Thibault
 * @version 2005-04-09
 * @since   2004-04-08
 */
public final class Selector {

    /**
     * Set that countains all the selectors created to avoid duplicates.
     */
    private static final Map SELECTOR_MAP = new HashMap();
    
    /**
     * Name of the method.
     */
    private String name;
	
    /**
     * Parameter types of the method.
     */
    private Class[] parameterTypes;
	
    /**
     * Return type of the method. May be null.
     */
    private Class returnType;
		
    private int hashCode;
    
    /**
     * Local version of NoSuchMethodException so we don't have to declare
     * throws clauses.
     *
     */
    public static class NoSuchMethodException extends RuntimeException {

        /**
         * Default constructor.
         *
         */
        public NoSuchMethodException() {
        }

        /**
         * Constructor with humain readable string.
         * @param string The humain readable error.
         */
        public NoSuchMethodException(String string) {
            super(string);
        }
    }
    
	/**
     * Creates a selector for a method with no argument.
     * 
	 * @param name Method name.
	 * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
	 */
	public static Selector newSelector(String name) {
		return Selector.newSelectorRT(null, name, (Class[]) null);
	}
	
    /**
     * Creates a selector for a method with one argument.
     * 
     * @param name Method name.
     * @param param1 Type of the parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class param1) {
		return Selector.newSelectorRT(null, name, new Class[] { param1 });
	}
	
    /**
     * Creates a selector for a method with two argument.
     * 
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class param1, 
            Class param2) {
		return Selector.newSelectorRT(null, name, 
                new Class[] { param1, param2 });
	}
	
    /**
     * Creates a selector for a method with three argument.
     * 
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the last parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class param1, 
            Class param2, Class param3) {
		return Selector.newSelectorRT(null, name, 
                new Class[] { param1, param2, param3 });
	}
	
    /**
     * Creates a selector for a method with four argument.
     * 
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the third parameter.
     * @param param4 Type of the last parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class param1, 
            Class param2, Class param3, Class param4) {
		return Selector.newSelectorRT(null, name, 
                new Class[] { param1, param2, param3, param4 });
	}
	
    /**
     * Creates a selector for a method with five argument.
     * 
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the third parameter.
     * @param param4 Type of the fourth parameter.
     * @param param5 Type of the last parameter.
     * @param name Method name.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class param1, 
            Class param2, Class param3, Class param4, Class param5) {
		return Selector.newSelectorRT(null, name, 
                new Class[] { param1, param2, param3, param4, param5 });
	}
	
    /**
     * Creates a selector for a method with a return type but no argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name) {
		return Selector.newSelectorRT(returnType, name, (Class[]) null);
	}
	
    /**
     * Creates a selector for a method with a return type and a argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class param1) {
		return Selector.newSelectorRT(returnType, name, 
                new Class[] { param1 });
	}
	
    /**
     * Creates a selector for a method with a return type but no argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class param1, Class param2) {
		return Selector.newSelectorRT(returnType, name, new Class[] { param1, 
                param2 });
	}
	
    /**
     * Creates a selector for a method with a return type but no argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the third parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class param1, Class param2, Class param3) {
		return Selector.newSelectorRT(returnType, name, new Class[] { param1, 
                param2, param3 });
	}
	
    /**
     * Creates a selector for a method with a return type but no argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the third parameter.
     * @param param4 Type of the fourth parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class param1, Class param2, Class param3, Class param4) {
		return Selector.newSelectorRT(returnType, name, 
                new Class[] { param1, param2, param3, param4 });
	}
	
    /**
     * Creates a selector for a method with a return type but no argument.
     * 
     * @param returnType return type of the method.
     * @param name Method name.
     * @param param1 Type of the first parameter.
     * @param param2 Type of the second parameter.
     * @param param3 Type of the third parameter.
     * @param param4 Type of the fourth parameter.
     * @param param5 Type of the fiveth parameter.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class param1, Class param2, Class param3, Class param4, 
            Class param5) {
		return Selector.newSelectorRT(returnType, name, 
                new Class[] { param1, param2, param3, param4, param5 });
	}
	
    /**
     * Creates a selector for a method with no return type from the 
     * argument types defined in a specified array.
     * 
     * @param name Method name.
     * @param parameterTypes Array containing the parameter types of the 
     * method.
     * @return The selector.
     * @see #newSelectorRT(Class, String, Class[])
     */
	public static Selector newSelector(String name, Class[] parameterTypes) {
		return newSelectorRT(null, name, parameterTypes);
	}
	
    /**
     * Creates a selector for a method with a return type from the 
     * argument types defined in a specified array.
     * 
     * This is the method that will be called from the other convenient
     * creation method.
     * 
     * @param returnType Return type of the method (may be null).
     * @param name Method name.
     * @param parameterTypes Array containing the parameter types of the 
     * method.
     * @return The selector.
     * @throws IllegalArgumentException If the name of the method was null.
     */
	public static Selector newSelectorRT(Class returnType, String name, 
            Class[] parameterTypes) {
		if (name != null) {
            final Selector tempSelector = new Selector(returnType, name, parameterTypes);
            final Selector selector = (Selector) SELECTOR_MAP.get(tempSelector);
            if (selector == null) {
                SELECTOR_MAP.put(tempSelector, tempSelector);
                return tempSelector;
            }
            return selector;
		} else {
			throw new IllegalArgumentException("The name of the message was null.");
		}
	}
	
    /**
     * Invocate the method with no argument that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @return The result of the invocation.
     */
	public Object invoke(Object obj) {
		return invoke(obj, null);
	}
	
    /**
     * Invocate the method with one argument that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @param arg1 The argument.
     * @return The result of the invocation.
     */
	public Object invoke(Object obj, Object arg1) {
		return invoke(obj, new Object[] { arg1 } );
	}
	
    /**
     * Invocate the method with two arguments that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @param arg1 The first argument.
     * @param arg2 The second argument.
     * @return The result of the invocation.
     */
	public Object invoke(Object obj, Object arg1, Object arg2) {
		return invoke(obj, new Object[] { arg1, arg2 } );
	}
	
    /**
     * Invocate the method with three arguments that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @param arg1 The argument.
     * @param arg2 The argument.
     * @param arg3 The argument.
     * @return The result of the invocation.
     */
	public Object invoke(Object obj, Object arg1, Object arg2, Object arg3) {
		return invoke(obj, new Object[] { arg1, arg2, arg3 } );
	}
	
    /**
     * Invocate the method with four arguments that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @param arg1 The first argument.
     * @param arg2 The second argument.
     * @param arg3 The third argument.
     * @param arg4 The fourth argument.
     * @return The result of the invocation.
     */
	public Object invoke(Object obj, Object arg1, Object arg2, Object arg3, 
            Object arg4) {
		return invoke(obj, new Object[] { arg1, arg2, arg3, arg4 } );
	}
	
    /**
     * Invocate the method with five arguments that match the selector on the 
     * object obj.
     * 
     * @param obj The object on wich we invoke the method.
     * @param arg1 The first argument.
     * @param arg2 The second argument.
     * @param arg3 The third argument.
     * @param arg4 The fourth argument.
     * @param arg5 The fiveth argument.
     * @return The result of the invocation.
     * @see #invoke(Object, Object[])
     */
	public Object invoke(Object obj, Object arg1, Object arg2, Object arg3, 
            Object arg4, Object arg5) {
		return invoke(obj, new Object[] { arg1, arg2, arg3, arg4, arg5 } );
	}
	
    /**
     * Invocate the method with the arguments countain in args that match the
     * selector on the object obj.
     * 
     * This is the main method called for the other convenience methods of
     * the same type.
     * 
     * @param obj The object on wich we invoke the method.
     * @param args The array of arguments.
     * @return The result of the invocation.
     * @throws NoSuchMethodException If the method can not be called. It 
     * could be because one of the following errors occured internaly:
     * SecurityException, NoSuchMethodException, IllegalArgumentException,
     * IllegalAccessException or InvocationTargetException.
     * (NoSuchMethodException is defined internally in this class.)
     */
	
    public Object invoke(Object obj, Object[] args) {
        try {
            final Method method = obj.getClass().getMethod(name, 
                    parameterTypes);
            if (returnType == null || method.getReturnType() == returnType) {
                try {
                    return method.invoke(obj, args);
                } catch (Exception e) {
                    throwException(obj);
                }
            } else {
                throwException(obj);
            }                        
        } catch (Exception e) {
            throwException(obj);
        }
                
        return null;
    }

    /**
     * Returns true if a method is implemented for the selector on the object 
     * obj.
     * 
     * @param obj The object on wich we do the verification.
     * @return True if and only if obj implements the method.
     */
	public boolean isImplemented(Object obj) {
		if (obj != null) {
			try {
				Method method = obj.getClass().getMethod(name, parameterTypes);
				return returnType == null || method.getReturnType() == returnType;
			} catch (Exception e) {
				return false;
			}	
		} 
		return false;
	}
	
    /**
     * Return the method name associated with the selector.
     * @return The name of the method.
     */
    public String getName() {
		return name;
	}

    /**
     * Return the parameter types of the method associated with the selector.
     * @return The parameter types.
     */
	public Class[] getParameterTypes() {
		return parameterTypes;
	}

    /**
     * Return the return type of method associated with the selector.
     * It may be null. In such case the return type is not used when matching
     * a method.
     * @return the return type.
     */
	public Class getReturnType() {
		return returnType;
	}

	//------------------------------------------------------------------------

    /**
     * Equality comparator.
     * @return True if equal.
     */
	public final boolean equals(Object anObject) {
		if (anObject == this) return true;
        boolean result = false;
		if (anObject instanceof Selector) {
			final Selector selector = (Selector) anObject;
			result = selector.name.equals(name) 
                    && selector.returnType == returnType
                    && Arrays.equals(parameterTypes, selector.parameterTypes);
		}
		return result;
	}
	
    /**
     * Hash code.
     * @return Hash code.
     */
	public final int hashCode() {
      if (hashCode == 0) {
          hashCode = name.hashCode();
          if (parameterTypes != null) {
              for (int i = 0; i < parameterTypes.length; i++) {
                  hashCode ^= parameterTypes[i].getName().hashCode();
               }
          }
      }
      return hashCode;
	}

    /**
     * Private constructor of the class.
     * @param returnType Return type of the selector.
     * @param name Method name associeted with the selector.
     * @param parameterTypes Parameter types of the arguments.
     */
	private Selector(Class returnType, String name, Class[] parameterTypes) {
		this.name = name;
		this.parameterTypes = parameterTypes;
		this.returnType = returnType;
	}

	/**
     * Throw a NoSuchMethodException nicelly.
     * @throws NoSuchMethodException
	 */
    private void throwException(Object obj) {
		throw new NoSuchMethodException("Mehtod " + name + " was not found for "+obj);
	}
	
}