Commits

Jason S committed 6979e53

init checkin

Comments (0)

Files changed (22)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>testinvoke</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>

src/com/example/test/reflect1/AbstractBarBaz.java

+package com.example.test.reflect1;
+
+public class AbstractBarBaz implements IBar, IBaz 
+{
+	@Override public int compute(Object obj, String s) {
+		return 1;
+	}
+
+	@Override public int compute(Object obj1, Object obj2) {
+		return 2;
+	}
+
+	@Override public int compute(Integer i1, String s) {
+		return 3;
+	}	
+}

src/com/example/test/reflect1/IBar.java

+package com.example.test.reflect1;
+
+public interface IBar extends IFoo {
+	public int compute(Object obj, String s);
+}

src/com/example/test/reflect1/IBaz.java

+package com.example.test.reflect1;
+
+public interface IBaz extends IFoo {
+	public int compute(Integer i1, String s);	
+}

src/com/example/test/reflect1/IBlivet.java

+package com.example.test.reflect1;
+
+public interface IBlivet {
+	public int compute(String str, short s);
+
+	public int compute(String str, long l);
+
+	public int compute(String str, float f);
+
+	public int compute(String str, boolean b);
+
+	public int compute(String str, char c);
+}

src/com/example/test/reflect1/IFoo.java

+package com.example.test.reflect1;
+
+public interface IFoo {
+	public int compute(Object obj1, Object obj2);
+}

src/com/example/test/reflect1/IQuux.java

+package com.example.test.reflect1;
+
+public interface IQuux {
+	public int compute(IFoo obj, int i1);
+	
+	public String compute(Object obj, double i2);
+}

src/com/example/test/reflect1/MethodRecordingObject.java

+package com.example.test.reflect1;
+
+public interface MethodRecordingObject {
+	public String describeLastMethod();
+	public void setAutoPrintMethodDescription(boolean print);
+	public boolean getAutoPrintMethodDescription();
+}

src/com/example/test/reflect1/Thing1.java

+package com.example.test.reflect1;
+
+class Thing1 extends AbstractBarBaz implements IQuux
+{
+	@Override public int compute(IFoo obj, int i1) {
+		return 11;
+	}
+
+	@Override public String compute(Object obj, double i2) {
+		return "S12";
+	}
+	
+	public int compute(String obj, int i3) {
+		return 13;
+	}
+	@Override public String toString() { return "Thing1"; }
+}

src/com/example/test/reflect1/Thing2.java

+package com.example.test.reflect1;
+
+class Thing2 extends AbstractBarBaz 
+{
+	public int compute(AbstractBarBaz abb, String s) {
+		return 21;
+	}	
+
+	public int compute(IQuux quux, String s) {
+		return 22;
+	}
+	@Override public String toString() { return "Thing2"; }
+}

src/com/example/test/reflect1/Thing3.java

+package com.example.test.reflect1;
+
+class Thing3 implements IBar, IQuux
+{
+	public int compute(IBar bar, Object obj2) {
+		return 31;
+	}
+
+	@Override public int compute(Object obj, String s) {
+		return 32;
+	}
+
+	@Override public int compute(Object obj1, Object obj2) {
+		return 33;
+	}
+
+	@Override public int compute(IFoo obj, int i1) {
+		return 34;
+	}
+
+	@Override public String compute(Object obj, double i2) {
+		return "S35";
+	}	
+	@Override public String toString() { return "Thing3"; }
+}

src/com/example/test/reflect1/Thing4.java

+package com.example.test.reflect1;
+
+class Thing4 implements IBaz 
+{
+	@Override public int compute(Integer i1, String s) {
+		return 41;
+	}
+
+	@Override public int compute(Object obj1, Object obj2) {
+		return 42;
+	}
+	
+	public String compute(IFoo obj1, Object obj2)
+	{
+		return "S43";
+	}
+	@Override public String toString() { return "Thing4"; }
+}

src/com/example/test/reflect1/Thing5.java

+package com.example.test.reflect1;
+
+public class Thing5 extends Thing4 {
+	public String compute(Object obj1, IBaz baz)
+	{
+		return "S51";
+	}
+	public String compute(String s, Object... objects)
+	{
+		return "S52";
+	}
+	public String compute(Object... objects)
+	{
+		return "S53";
+	}
+	public String compute(float f, Number... objects)
+	{
+		return "S54";
+	}
+	@Override public String toString() { return "Thing5"; }
+}

src/com/example/test/reflect1/Thing6.java

+package com.example.test.reflect1;
+
+public class Thing6 implements IBlivet, IQuux
+{
+
+	@Override public int compute(String str, short s) { return 61; }
+	@Override public int compute(String str, long l) { return 62; }
+	@Override public int compute(String str, float f) { return 63; }
+	@Override public int compute(String str, boolean b) { return 64; }
+	@Override public int compute(String str, char c) { return 65; }
+	@Override public int compute(IFoo obj, int i1) { return 66; }
+	@Override public String compute(Object obj, double i2) { return "S67"; }
+}

src/com/example/test/reflect1/Thing7.java

+package com.example.test.reflect1;
+
+public class Thing7 {
+	
+}

src/com/example/test/reflect1/Thing8.java

+package com.example.test.reflect1;
+
+public class Thing8 {
+	
+}

src/com/example/test/reflect1/ThingFactory.java

+package com.example.test.reflect1;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ThingFactory {
+	public int getThingCount() { return 7; }
+	public Object getThing(int choice)
+	{
+		switch (choice)
+		{
+			case 1:
+				return new Thing1();
+			case 2:
+				return new Thing2();
+			case 3:
+				return new Thing3();
+			case 4:
+				return new Thing4();
+			case 5:
+				return new Thing5();
+			case 6:
+				return new Thing6();
+			case 7:
+				return new Thing7();
+			case 8:
+				return new Thing8();
+			case 9:
+				return createDescribingProxy(null, new DefaultMethodRecordingObject("Thing9"), IBar.class, IBaz.class);
+			case 10:
+				return createDescribingProxy(null, new DefaultMethodRecordingObject("Thing10"), IFoo.class, IQuux.class);
+		}
+		return null;
+	}
+	
+	static private class DefaultMethodRecordingObject implements MethodRecordingObject
+	{
+		private String description;
+		private boolean autoPrint = false;
+		final private String name;
+		void setDescription(String s) { 
+			this.description = s;
+			if (this.autoPrint)
+				System.out.println(s);
+		}
+		@Override public String describeLastMethod() { return this.description; }
+		@Override public String toString() { return this.name; }
+		@Override public boolean getAutoPrintMethodDescription() { return this.autoPrint; }
+		@Override public void setAutoPrintMethodDescription(boolean print) { this.autoPrint = print; }
+		public DefaultMethodRecordingObject(String name) { this.name = name; }
+	}
+	
+	private Object createDescribingProxy(final String name, final Object target, Class<?>... interfaces) {
+		InvocationHandler describer = new InvocationHandler() {
+			final private Map<Class<?>, Object> defaultValues = 
+				createDefaultValues();
+			private Map<Class<?>, Object> createDefaultValues()
+			{
+				Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
+				map.put(Boolean.TYPE, false);
+				map.put(Byte.TYPE, (byte)1);
+				map.put(Short.TYPE, (short)2);
+				map.put(Integer.TYPE, 3);
+				map.put(Long.TYPE, (long)4);
+				map.put(Character.TYPE, 'c');
+				map.put(Float.TYPE, 5.0f);
+				map.put(Double.TYPE, 6.0);
+				
+				map.put(String.class, "A string!!!");
+				return map;
+			}
+			@Override public Object invoke(Object proxy, 
+					Method method, Object[] args) 
+				throws Throwable 
+			{
+				Object result = null;
+				if (method.getName().equals("toString") && 
+						method.getDeclaringClass() == Object.class)
+				{
+					result = chooseImplementationOfToString(target, name);
+				}
+				else if (target != null && method.getDeclaringClass().isAssignableFrom(target.getClass()))
+				{
+					result = method.invoke(target, args); 
+				}
+				else
+					result = this.defaultValues.get(method.getReturnType());
+
+				if (target instanceof DefaultMethodRecordingObject)
+				{
+					((DefaultMethodRecordingObject) target).setDescription(describe(method, args));
+				}
+
+				return result;
+			}
+			
+			private String chooseImplementationOfToString(Object target, String name) {
+				String result = name;
+				for (Class<?> cl = target.getClass(); cl != null; cl = cl.getSuperclass())
+				{
+					try
+					{
+						Method m = cl.getDeclaredMethod("toString");
+						if (m.getReturnType().equals(String.class))
+							return (String)m.invoke(target);
+					}
+					catch (SecurityException e) { /* gulp */ }
+					catch (NoSuchMethodException e) { /* gulp */ }
+					catch (IllegalArgumentException e) { /* gulp */ }
+					catch (IllegalAccessException e) { /* gulp */ }
+					catch (InvocationTargetException e) { /* gulp */ }
+				}
+				return result;
+			}
+			private String describe(Method method, @SuppressWarnings("unused") Object[] args) {
+				StringBuilder sb = new StringBuilder();
+				sb.append(method.getDeclaringClass());
+				sb.append(".");
+				sb.append(method.getName());
+				sb.append("(");
+				boolean first = true;
+				for (Class<?> cl : method.getParameterTypes())
+				{
+					if (first)
+						first = false;
+					else
+						sb.append(",");						
+					sb.append(cl.getName());					
+				}
+				sb.append(") : ");
+				sb.append(method.getReturnType().getName());
+				return sb.toString();
+			}
+			
+		};
+		
+		Set<Class<?>> proxyInterfaces = new HashSet<Class<?>>();
+		for (Class<?> intf : target.getClass().getInterfaces())
+			proxyInterfaces.add(intf);
+		for (Class<?> intf : interfaces)
+			proxyInterfaces.add(intf);
+		return Proxy.newProxyInstance(
+				getClass().getClassLoader(), 
+				proxyInterfaces.toArray(new Class<?>[0]),
+				describer
+				);
+	}
+	@Override public String toString() { return "ThingFactory"; }
+}

src/com/example/test/reflect2/InvokeTestHarness.java

+package com.example.test.reflect2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import com.example.test.reflect1.MethodRecordingObject;
+
+abstract public class InvokeTestHarness implements Runnable {
+	abstract public void checkInvocation(Object obj, String method, Object expectedReturnValue, Object... arguments);
+	abstract public void checkProxyInvocation(MethodRecordingObject obj, String method, String expectedSignature, Object... arguments);
+	abstract public void onFailure(AbstractTestItem<?> item, TestFailure tf);
+
+	static public class TestFailure extends RuntimeException
+	{
+		public TestFailure(RuntimeException re)
+		{
+			super(re);
+		}
+		public TestFailure(String msg)
+		{
+			super(msg);
+		}
+	}
+	
+	abstract static public class AbstractTestItem<T> {
+		final private T obj;
+		final private String method;
+		final private List<?> arguments;
+		
+		public AbstractTestItem(T obj, String method, Object... args)
+		{
+			this.obj = obj;
+			this.method = method;
+			List<Object> alist = new ArrayList<Object>();
+			for (Object arg : args)
+				alist.add(arg);
+			this.arguments = Collections.unmodifiableList(alist);
+		}
+		public T getObject() { return this.obj; }
+		public String getMethodName() { return this.method; }
+		public Object[] getArgumentsArray() { return this.arguments.toArray(new Object[0]); }
+		abstract public void run(InvokeTestHarness harness);
+		protected void appendArguments(StringBuilder sb) 
+		{
+			boolean first = true;
+			for (Object arg : arguments)
+			{
+				if (first)
+					first = false;
+				else
+					sb.append(",");
+				sb.append(arg);
+			}
+		}
+		protected void appendCall(StringBuilder sb)
+		{
+			sb.append(getObject())
+				.append(".")
+				.append(getMethodName())
+				.append("(");
+			appendArguments(sb);
+			sb.append(")");		
+		}
+	}
+	static public class TestItem extends AbstractTestItem<Object> 
+	{
+		final private Object expectedReturnValue;
+		public Object getExpectedReturnValue() { return this.expectedReturnValue; } 
+		public TestItem(Object obj, String method, Object expectedReturnValue, Object... args)
+		{
+			super(obj, method, args);
+			this.expectedReturnValue = expectedReturnValue;
+		}
+		@Override public void run(InvokeTestHarness harness)
+		{
+			harness.checkInvocation(getObject(), getMethodName(), getExpectedReturnValue(), getArgumentsArray());
+		}
+		@Override public String toString() {
+			StringBuilder sb = new StringBuilder();
+			appendCall(sb);
+			sb.append("\nexpected return value: ");
+			sb.append(getExpectedReturnValue());
+			return sb.toString();
+		}
+	}
+	static public class MethodRecordingTestItem extends AbstractTestItem<MethodRecordingObject>
+	{
+		final private String expectedSignature;
+		public String getExpectedSignature() { return this.expectedSignature; } 
+		public MethodRecordingTestItem(MethodRecordingObject obj, String method, String expectedSignature, Object... args)
+		{
+			super(obj, method, args);
+			this.expectedSignature = expectedSignature;
+		}
+		@Override public void run(InvokeTestHarness harness)
+		{
+			harness.checkProxyInvocation(getObject(), getMethodName(), getExpectedSignature(), getArgumentsArray());
+		}
+		@Override public String toString() {
+			StringBuilder sb = new StringBuilder();
+			appendCall(sb);
+			sb.append("\nexpected signature: ");
+			sb.append(getExpectedSignature());
+			return sb.toString();
+		}
+	}
+	
+	static public class TestItemListBuilder
+	{
+		final private List<AbstractTestItem<?>> items;
+		public TestItemListBuilder()
+		{
+			this.items = new ArrayList<AbstractTestItem<?>>();
+		}
+		public TestItemListBuilder addTest(Object obj, String method, Object expectedReturnValue, Object... args)
+		{
+			this.items.add(new TestItem(obj, method, expectedReturnValue, args));
+			return this;
+		}
+		public TestItemListBuilder addRecordingTest(MethodRecordingObject obj, String method, String expectedSignature, Object... args)
+		{
+			this.items.add(new MethodRecordingTestItem(obj, method, expectedSignature, args));
+			return this;
+		}
+		public List<AbstractTestItem<?>> build() { return this.items; }
+	}
+	
+	static public TestItemListBuilder testItemsBuilder() { return new TestItemListBuilder(); }
+	
+	final private List<AbstractTestItem<?>> testItems;
+	public InvokeTestHarness(List<AbstractTestItem<?>> testItems)
+	{
+		this.testItems = Collections.unmodifiableList(new ArrayList<AbstractTestItem<?>>(testItems)); 
+	}
+	@Override public void run()
+	{
+		for (AbstractTestItem<?> item : this.testItems)
+		{
+			try
+			{
+				item.run(this);
+			}
+			catch (TestFailure tf)
+			{
+				onFailure(item, tf);
+			}
+		}
+	}
+}

src/com/example/test/reflect2/ReflectInvocations.java

+package com.example.test.reflect2;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ReflectInvocations {
+	
+	/* static utility class */ 
+	private ReflectInvocations() {}
+	
+	static public class Invoker 
+	{
+		public Object invoke(Object obj, String methodName)
+		{
+			return ReflectInvocations.invoke(obj, methodName);
+		}
+		public Object invoke(Object obj, String methodName, Object arg0)
+		{
+			return ReflectInvocations.invoke(obj, methodName, arg0);
+		}
+		public Object invoke(Object obj, String methodName, Object arg0, Object arg1)
+		{
+			return ReflectInvocations.invoke(obj, methodName, arg0, arg1);
+		}		
+		public Object invoke(Object obj, String methodName, Object arg0, Object arg1, Object... remainingArgs)
+		{
+			Object[] args = new Object[remainingArgs.length+2];
+			args[0] = arg0;
+			args[1] = arg1;
+			System.arraycopy(remainingArgs, 0, args, 2, remainingArgs.length);
+			return ReflectInvocations.invoke(obj, methodName, args);
+		}
+		@Override public String toString() { return "ReflectInvocations.Invoker"; }
+	}
+	
+	static public Object invoke(Object obj, String methodName, Object... arguments)
+	{
+		Exception eout = null;
+		try
+		{
+			Method m = findMethod(obj, methodName, arguments);
+			return m.invoke(obj, arguments);
+		}
+		catch (IllegalArgumentException e) { eout = e; }
+		catch (IllegalAccessException e) { eout = e; }
+		catch (InvocationTargetException e) { eout = e; }
+		
+		throw new RuntimeException(eout);
+	}
+
+	static private Method findMethod(Object obj, String methodName, Object[] arguments)
+	{
+		Class<?> cl = obj.getClass();
+		
+		Set<Method> methods = new HashSet<Method>();
+		collectCompatibleMethods(methods, cl, methodName, arguments);
+		if (methods.isEmpty())
+			throw new RuntimeException("No method "+methodName+" found that is compatible with the specified arguments");
+		return methods.iterator().next();
+	}
+
+	static private void collectCompatibleMethods(Set<Method> methods, 
+			Class<?> cl, String methodName, Object[] arguments) 
+	{
+		for (Method m : cl.getDeclaredMethods())
+		{
+			if (!methods.contains(m)
+					&& methodName.equals(m.getName())
+					&& isPublic(m)
+					&& isCompatibleMethod(m, arguments))
+			{
+				methods.add(m);
+			}
+		}
+		Class<?> superclass = cl.getSuperclass();
+		if (superclass != null)
+			collectCompatibleMethods(methods, superclass, methodName, arguments);
+		
+		for (Class<?> cli : cl.getInterfaces())
+		{
+			collectCompatibleMethods(methods, cli, methodName, arguments);
+		}
+	}
+
+	static private boolean isPublic(Method m) {
+		return Modifier.isPublic(m.getModifiers())
+			&& Modifier.isPublic(m.getDeclaringClass().getModifiers());
+	}
+
+	static private boolean isCompatibleMethod(Method m, Object[] arguments) 
+	{
+		Class<?>[] pt = m.getParameterTypes();
+		int L = pt.length;
+		if (L != arguments.length)
+			return false;
+		for (int i = 0; i < L; ++i)
+		{
+			if (!pt[i].isAssignableFrom(arguments[i].getClass()))
+				return false;				
+		}
+		return true;
+	}
+	
+	public static Invoker invoker() { return new Invoker(); }
+}

src/com/example/test/reflect2/Shell.java

+package com.example.test.reflect2;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import com.example.test.reflect1.ThingFactory;
+
+public class Shell {
+	final private ScriptEngine engine;
+	
+	public Shell(String engineName) {
+		ScriptEngineManager engineMgr = new ScriptEngineManager();
+		this.engine = engineMgr.getEngineByName(engineName);
+	}
+
+	public static void main(String[] args) {
+		new Shell(args[0]).run();
+	}
+
+	private void run() {
+		Bindings bindings = this.engine.getBindings(ScriptContext.ENGINE_SCOPE);
+		ThingFactory factory = new ThingFactory();
+		bindings.put("factory", factory);
+		int N = factory.getThingCount();
+		for (int i = 1; i <= N; ++i)
+		{
+			bindings.put("thing"+i, factory.getThing(i));
+		}
+		bindings.put("invoker", ReflectInvocations.invoker());
+		
+		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+		try
+		{
+			while (true)
+			{
+				System.out.print(">> ");
+				String line = br.readLine();
+				if (line == null)
+					break;
+				try
+				{
+					Object result = this.engine.eval(line);
+					System.out.println(result);
+				}
+				catch (ScriptException e) {
+					e.printStackTrace();
+				}
+			}
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+}

src/com/example/test/reflect2/Test1.java

+package com.example.test.reflect2;
+
+import java.util.List;
+import com.example.test.reflect1.MethodRecordingObject;
+import com.example.test.reflect1.ThingFactory;
+
+public class Test1 extends InvokeTestHarness
+{
+ 	@Override public void checkInvocation(Object obj, String method, Object expectedReturnValue, Object... arguments) 
+ 	{
+		Object result = ReflectInvocations.invoke(obj, method, arguments);
+		boolean success = (expectedReturnValue == null) ? (result == null) : (expectedReturnValue.equals(result));
+		if (!success)
+			throw new InvokeTestHarness.TestFailure("unexpected return value: "+result);
+	}
+
+	@Override public void checkProxyInvocation(MethodRecordingObject obj, String method, String expectedSignature, Object... arguments) {
+		Object result = ReflectInvocations.invoke(obj, method, arguments);
+		String signature = obj.describeLastMethod();
+		boolean success = expectedSignature.equals(signature);
+		if (!success)
+			throw new InvokeTestHarness.TestFailure("unexpected signature: "+signature);
+	}
+
+	@Override public void onFailure(AbstractTestItem<?> item, TestFailure tf) {
+		System.err.println(tf.getMessage());
+		System.err.println(item);
+		tf.printStackTrace(System.err);		
+	}
+	
+	public Test1(List<AbstractTestItem<?>> items)
+	{
+		super(items);
+	}
+	
+	public static void main(String[] args) {
+		ThingFactory factory = new ThingFactory();
+		int n = factory.getThingCount();
+		Object[] thing = new Object[n+1];
+		for (int i = 1; i <= n; ++i)
+			thing[i] = factory.getThing(i);
+		
+		String plainMethod = "compute";
+		Object object0 = new Object();
+		Test1 test = new Test1(
+			InvokeTestHarness.testItemsBuilder()
+				.addTest(thing[1], plainMethod, 1, object0, "hi")
+				.addTest(thing[1], plainMethod, 2, object0, object0)
+				.addTest(thing[1], plainMethod, 3, 1, "hi")
+				.build());
+		test.run();
+	}	
+}