PageRenderTime 55ms CodeModel.GetById 15ms app.highlight 34ms RepoModel.GetById 2ms app.codeStats 0ms

/interpreter/tags/at_build150307/src/edu/vub/at/objects/mirrors/JavaInterfaceAdaptor.java

http://ambienttalk.googlecode.com/
Java | 340 lines | 196 code | 18 blank | 126 comment | 94 complexity | 3468369bcb7acf4037ba237d87e1939a MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * JavaInterfaceAdaptor.java created on Jul 13, 2006 at 10:25:01 PM
  4 * (c) Programming Technology Lab, 2006 - 2007
  5 * Authors: Tom Van Cutsem & Stijn Mostinckx
  6 * 
  7 * Permission is hereby granted, free of charge, to any person
  8 * obtaining a copy of this software and associated documentation
  9 * files (the "Software"), to deal in the Software without
 10 * restriction, including without limitation the rights to use,
 11 * copy, modify, merge, publish, distribute, sublicense, and/or
 12 * sell copies of the Software, and to permit persons to whom the
 13 * Software is furnished to do so, subject to the following
 14 * conditions:
 15 *
 16 * The above copyright notice and this permission notice shall be
 17 * included in all copies or substantial portions of the Software.
 18 *
 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 26 * OTHER DEALINGS IN THE SOFTWARE.
 27 */
 28package edu.vub.at.objects.mirrors;
 29
 30import edu.vub.at.exceptions.InterpreterException;
 31import edu.vub.at.exceptions.XArityMismatch;
 32import edu.vub.at.exceptions.XIllegalArgument;
 33import edu.vub.at.exceptions.XIllegalOperation;
 34import edu.vub.at.exceptions.XReflectionFailure;
 35import edu.vub.at.exceptions.XSelectorNotFound;
 36import edu.vub.at.exceptions.XTypeMismatch;
 37import edu.vub.at.exceptions.signals.Signal;
 38import edu.vub.at.objects.ATObject;
 39import edu.vub.at.objects.coercion.Coercer;
 40import edu.vub.at.objects.grammar.ATSymbol;
 41import edu.vub.at.objects.natives.NATBoolean;
 42import edu.vub.at.objects.natives.NATFraction;
 43import edu.vub.at.objects.natives.NATNumber;
 44import edu.vub.at.objects.natives.NATText;
 45
 46import java.lang.reflect.Constructor;
 47import java.lang.reflect.InvocationTargetException;
 48import java.lang.reflect.Method;
 49import java.lang.reflect.Modifier;
 50import java.util.Vector;
 51
 52/**
 53 * JavaInterfaceAdaptor is a class providing several static methods which allow 
 54 * accessing and invoking Java methods which represent native AmbientTalk methods.
 55 * It is used by the Reflection class to up ambienttalk invocations and field 
 56 * accesses and translate them using java reflection. 
 57 * 
 58 * @author tvcutsem
 59 * @author smostinc
 60 */
 61public class JavaInterfaceAdaptor {
 62		
 63	/**
 64	 * Tests given a class, whether the class either declares or inherits a method
 65	 * for a given selector. 
 66	 * @param jClass - a Java class, representing an AT object.
 67	 * @param jSelector - a selector, describing the method to be searched for.
 68	 * @return whether a methods with a matching selector can be found
 69	 */
 70	public static boolean hasApplicableJavaMethod(Class jClass, String jSelector) {
 71		Method[] allMethods = jClass.getMethods();
 72		for (int i = 0; i < allMethods.length; i++) {
 73			if (allMethods[i].getName().equals(jSelector)) {
 74				return true;
 75			}
 76		}
 77		return false;
 78	}
 79	
 80	/**
 81	 * Invokes a method on a Java object identified by a selector.
 82	 * 
 83	 * @param jClass the class of the receiver object
 84	 * @param natReceiver the receiver (a native AmbientTalk object)
 85	 * @param jSelector the java-level selector identifying the method to invoke
 86	 * @param atSelector TODO
 87	 * @param jArguments parameters, normally AT objects
 88	 * @return the return value of the reflectively invoked method
 89	 */	
 90	public static ATObject invokeNativeATMethod(Class jClass, ATObject natReceiver,
 91										        String jSelector, ATSymbol atSelector, ATObject[] jArguments) throws InterpreterException {
 92		return invokeNativeATMethod(getNativeATMethod(jClass, natReceiver, jSelector, atSelector), natReceiver, jArguments);
 93	}
 94	
 95	/**
 96	 * Invokes a method on a native AmbientTalk object identified by a java.lang.reflect.Method object.
 97	 * Note that if the method to invoke reflectively has a formal parameter list consisting
 98	 * of one argument of type ATObject[], then the arguments are wrapped in an array such that
 99	 * the function actually takes a variable number of arguments.
100	 * 
101	 * A native AmbientTalk method is an ordinary Java method with the following constraints:
102	 *  - its name usually starts with base_ or meta_, identifying whether the method is accessible at
103	 *    the AmbientTalk base or meta level
104	 *  - its formal parameters MUST all be subtypes of ATObject (or be a single array of ATObject[] for varargs)
105	 *  - its return type must be a subtype of ATObject or a native Java type
106	 *    (native types are subject to default conversion to the appropriate AmbientTalk natives)
107	 *  - it may only throw InterpreterException exceptions
108	 * 
109	 * @param javaMethod the Java method to invoke
110	 * @param jReceiver the Java object representing the receiver (normally an AT object)
111	 * @param jArguments the AT arguments to pass
112	 * @return the return value of the reflectively invoked method
113	 * 
114	 * TODO: code duplication w.r.t. invokeSymbioticMethod => replace this method by calls to invokeSymbioticMethod?
115	 */
116	public static ATObject invokeNativeATMethod(Method javaMethod, ATObject jReceiver, ATObject[] jArguments) throws InterpreterException {
117		try {
118			// if the native method takes an array as its sole parameter, it is interpreted as taking
119			// a variable number of ambienttalk arguments
120			Class[] params = javaMethod.getParameterTypes();
121			Object[] args;
122			if ((params.length == 1) && params[0].equals(ATObject[].class)) {
123				args= new Object[] { jArguments };
124			} else {
125				if (params.length != jArguments.length) {
126					throw new XArityMismatch("native method "+Reflection.downSelector(javaMethod.getName()), params.length, jArguments.length);
127				}
128				// make sure to properly 'coerce' each argument into the proper AT interface type
129				args = coerceArguments(jArguments, params);
130			}
131			Object rval = javaMethod.invoke(jReceiver, args);
132			if (rval instanceof ATObject) {
133				return (ATObject) rval;
134			} else {
135				return primitiveJavaToATObject(rval);
136			}
137		} catch (IllegalAccessException e) {
138			// the invoked method is not publicly accessible
139			throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName()) + " not accessible.", e);
140		} catch (IllegalArgumentException e) {
141			// illegal argument types were supplied
142			throw new XIllegalArgument("Illegal argument for native method "+Reflection.downSelector(javaMethod.getName()) + ": " + e.getMessage(), e);
143		} catch (InvocationTargetException e) {
144			// the invoked method threw an exception
145			if (e.getCause() instanceof InterpreterException)
146				throw (InterpreterException) e.getCause();
147			else if (e.getCause() instanceof Signal) {
148			    throw (Signal) e.getCause();	
149			} else {
150				e.printStackTrace();
151				throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName())+" threw internal exception", e.getCause());
152		    }
153		}
154	}
155	
156	/**
157	 * Try to create a new instance of a Java class given an array of initialization arguments.
158	 * Because we do not have exact typing information, all of the public constructors of the
159	 * class are traversed until one is found that can create new instances given the current
160	 * initargs.
161	 */
162	public static ATObject createNativeATObject(Class jClass, ATObject[] jInitArgs) throws InterpreterException {
163		Constructor[] ctors = jClass.getConstructors();
164		for (int i = 0; i < ctors.length; i++) {
165			Constructor ctor = ctors[i];
166			if (ctor.getParameterTypes().length == jInitArgs.length) {
167				try {
168					// make sure to properly 'coerce' each argument into the proper AT interface type
169					Object[] coercedInitArgs = coerceArguments(jInitArgs, ctor.getParameterTypes());
170					return (ATObject) ctor.newInstance(coercedInitArgs);
171				} catch (IllegalArgumentException e) {
172					continue; // argument types don't match, may find other constructor
173				} catch (InstantiationException e) {
174					break; // class is an abstract class, won't find a match
175				} catch (IllegalAccessException e) {
176					continue; // private or protected constructor, may find another one
177				} catch (InvocationTargetException e) {
178					// an exception was raised by the constructor
179					if (e.getCause() instanceof InterpreterException)
180						throw ((InterpreterException) e.getCause());
181					else if (e.getCause() instanceof Signal) {
182					    throw (Signal) e.getCause();	
183					} else // fatal exception
184						throw new XIllegalOperation("Instance creation of type " + jClass.getName() + " failed: " + e.getMessage());
185				}
186			} else {
187				// arity does not match, try finding another one
188				continue;
189			}
190		}
191		// no matching constructors were found
192		throw new XIllegalOperation("Unable to create a new instance of type " + jClass.getName());
193	}
194	
195	public static Method getNativeATMethod(
196			Class baseInterface, 
197			ATObject receiver,
198			String methodName, ATSymbol atSelector) throws InterpreterException {
199		Method[] applicable = getMethodsForSelector(baseInterface, methodName);
200		switch (applicable.length) {
201			case 0:
202				throw new XSelectorNotFound(atSelector, receiver);
203			case 1:
204				return applicable[0];
205			default:
206				throw new XIllegalOperation("Native method uses overloading: " + atSelector + " in " + baseInterface);
207		}
208	}
209	
210	/**
211	 * Returns all public methods from the given class parameter whose name starts with the
212	 * given prefix. Moreover, the boolean parameter isStatic determines whether
213	 * to consider only static or only non-static methods.
214	 */
215	public static Method[] allMethodsPrefixed(Class fromClass, String prefix, boolean isStatic) {
216		// all public methods defined in the class
217		Method[] allPublicMethods = fromClass.getMethods();
218		
219		Vector matchingMethods = new Vector(allPublicMethods.length);
220		for (int i = 0; i < allPublicMethods.length; i++) {
221			Method m = allPublicMethods[i];
222			if (Modifier.isStatic(m.getModifiers()) == isStatic) {
223				if (m.getName().startsWith(prefix)) {
224					matchingMethods.add(m);
225				}
226			}
227		}
228		return (Method[]) matchingMethods.toArray(new Method[matchingMethods.size()]);
229	}
230	
231	/**
232	 * Since Java uses strict matching when asked for a method, given an array of 
233	 * classes, this often means that the types are overspecified and therefore no
234	 * matches can be found. As a consequence we have our own mechanism to select
235	 * which set of methods is applicable given a selector. Further dispatch needs
236	 * only to be performed when more than a single match exists.
237	 * @param jClass - the class from which the methods will be selected.
238	 * @param selector - the name of the requested method.
239	 * @return an array of applicable methods
240	 */
241	private static Method[] getMethodsForSelector(Class jClass, String selector) {
242		Method[] allMethods = jClass.getMethods();
243		
244		Vector matchingMethods = new Vector();
245		int numMatchingMethods = 0;
246		
247		for (int i = 0; i < allMethods.length; i++) {
248			if (allMethods[i].getName().equals(selector)) {
249				matchingMethods.addElement(allMethods[i]);
250				numMatchingMethods++;
251			}
252		}
253		
254		return (Method[])matchingMethods.toArray(new Method[numMatchingMethods]);
255	}
256	
257	private static Object[] coerceArguments(ATObject[] args, Class[] types) throws XTypeMismatch {
258		Object[] coercedArgs = new Object[args.length];
259		for (int i = 0; i < args.length; i++) {
260			coercedArgs[i] = Coercer.coerce(args[i], types[i]);
261		}
262		return coercedArgs;
263	}
264	
265	public static final boolean isPrimitiveType(Class c) {
266		return (c.isPrimitive() ||
267				c == Integer.class ||
268				c == Double.class ||
269				c == Float.class ||
270				c == Character.class ||
271				c == Boolean.class ||
272				c == Byte.class ||
273				c == Long.class ||
274				c == Short.class);
275	}
276	
277	public static final ATObject primitiveJavaToATObject(Object jObj) throws XReflectionFailure {
278		// integer
279		if (jObj instanceof Integer) {
280			return NATNumber.atValue(((Integer) jObj).intValue());
281		// double
282		} else if (jObj instanceof Double) {
283			return NATFraction.atValue(((Double) jObj).doubleValue());
284		// float
285		} else if (jObj instanceof Float) {
286			return NATFraction.atValue(((Float) jObj).floatValue());
287		// char
288		} else if (jObj instanceof Character) {
289			return NATText.atValue(((Character) jObj).toString());
290		// boolean
291		} else if (jObj instanceof Boolean) {
292			return NATBoolean.atValue(((Boolean) jObj).booleanValue());
293		// byte
294		} else if (jObj instanceof Byte) {
295			return NATNumber.atValue(((Byte) jObj).byteValue());
296		// long
297		} else if (jObj instanceof Long) {
298			return NATFraction.atValue(((Long) jObj).longValue());
299		// short
300		} else if (jObj instanceof Short) {
301			return NATNumber.atValue(((Short) jObj).shortValue());
302		} else {
303		    throw new XReflectionFailure("Expected a primitive Java value, given: " + jObj);
304		}
305	}
306	
307	public static final Object atObjectToPrimitiveJava(ATObject atObj, Class type) throws InterpreterException {
308		// integer
309		if (type == int.class || type == Integer.class) {
310			return new Integer(atObj.asNativeNumber().javaValue);
311		// double
312		} else if (type == double.class || type == Double.class) {
313			return new Double(atObj.asNativeFraction().javaValue);
314		// char
315		} else if (type == char.class || type == Character.class) {
316			return new Character(atObj.asNativeText().asChar());
317		// boolean
318		} else if (type == boolean.class || type == Boolean.class) {
319			return Boolean.valueOf(atObj.asNativeBoolean().javaValue);
320		// float
321		} else if (type == float.class || type == Float.class) {
322			throw new XTypeMismatch(Float.class, atObj);
323			//return Float.valueOf((float) atObj.asNativeFraction().javaValue);
324		// byte
325		} else if (type == byte.class || type == Byte.class) {
326			throw new XTypeMismatch(Byte.class, atObj);
327			//return Byte.valueOf((byte) atObj.asNativeNumber().javaValue);
328		// long
329		} else if (type == long.class || type == Long.class) {
330			throw new XTypeMismatch(Long.class, atObj);
331			//return Long.valueOf((long) atObj.asNativeFraction().javaValue);
332		// short
333		} else if (type == short.class || type == Short.class) {
334			throw new XTypeMismatch(Short.class, atObj);
335			//return Short.valueOf((short) atObj.asNativeNumber().javaValue);
336		} else {
337		    throw new XIllegalArgument("Expected a primitive Java type, given: " + type);
338		}
339	}
340}