PageRenderTime 45ms CodeModel.GetById 22ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://ambienttalk.googlecode.com/
Java | 414 lines | 224 code | 18 blank | 172 comment | 108 complexity | b94b339b0d8a3ebb653edc7636d12de7 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;
 45import edu.vub.at.objects.symbiosis.JavaObject;
 46
 47import java.lang.reflect.Constructor;
 48import java.lang.reflect.InvocationTargetException;
 49import java.lang.reflect.Method;
 50import java.lang.reflect.Modifier;
 51import java.util.Vector;
 52
 53/**
 54 * JavaInterfaceAdaptor is a class providing several static methods which allow 
 55 * accessing and invoking Java methods which represent native AmbientTalk methods.
 56 * It is used by the Reflection class to up ambienttalk invocations and field 
 57 * accesses and translate them using java reflection. 
 58 * 
 59 * @author tvcutsem
 60 * @author smostinc
 61 */
 62public class JavaInterfaceAdaptor {
 63		
 64	/**
 65	 * Tests given a class, whether the class either declares or inherits a method
 66	 * for a given selector. 
 67	 * @param jClass - a Java class, representing an AT object.
 68	 * @param jSelector - a selector, describing the method to be searched for.
 69	 * @return whether a methods with a matching selector can be found
 70	 */
 71	public static boolean hasApplicableJavaMethod(Class jClass, String jSelector) {
 72		Method[] allMethods = jClass.getMethods();
 73		for (int i = 0; i < allMethods.length; i++) {
 74			if (allMethods[i].getName().equals(jSelector)) {
 75				return true;
 76			}
 77		}
 78		return false;
 79	}
 80	
 81	/**
 82	 * Invokes a method on a Java object identified by a selector.
 83	 * 
 84	 * @param jClass the class of the receiver object
 85	 * @param natReceiver the receiver (a native AmbientTalk object)
 86	 * @param jSelector the java-level selector identifying the method to invoke
 87	 * @param atSelector the original AmbientTalk selector
 88	 * @param jArguments parameters, normally AT objects
 89	 * @return the return value of the reflectively invoked method
 90	 */	
 91	public static ATObject invokeNativeATMethod(Class jClass, ATObject natReceiver,
 92										        String jSelector, ATSymbol atSelector, ATObject[] jArguments) throws InterpreterException {
 93		return invokeNativeATMethod(getNativeATMethod(jClass, natReceiver, jSelector, atSelector), natReceiver, jArguments);
 94	}
 95	
 96	/**
 97	 * Invokes a method on a native AmbientTalk object identified by a java.lang.reflect.Method object.
 98	 * Note that if the method to invoke reflectively has a formal parameter list consisting
 99	 * of one argument of type ATObject[], then the arguments are wrapped in an array such that
100	 * the function actually takes a variable number of arguments.
101	 * 
102	 * A native AmbientTalk method is an ordinary Java method with the following constraints:
103	 *  - its name usually starts with base_ or meta_, identifying whether the method is accessible at
104	 *    the AmbientTalk base or meta level
105	 *  - its formal parameters MUST all be subtypes of ATObject (or be a single array of ATObject[] for varargs)
106	 *  - its return type must be a subtype of ATObject or a native Java type
107	 *    (native types are subject to default conversion to the appropriate AmbientTalk natives)
108	 *  - it may only throw InterpreterException exceptions
109	 * 
110	 * @param javaMethod the Java method to invoke
111	 * @param jReceiver the Java object representing the receiver (normally an AT object)
112	 * @param jArguments the AT arguments to pass
113	 * @return the return value of the reflectively invoked method
114	 * 
115	 * TODO: code duplication w.r.t. invokeSymbioticMethod => replace this method by calls to invokeSymbioticMethod?
116	 */
117	public static ATObject invokeNativeATMethod(Method javaMethod, ATObject jReceiver, ATObject[] jArguments) throws InterpreterException {
118		try {
119			// if the native method takes an array as its sole parameter, it is interpreted as taking
120			// a variable number of ambienttalk arguments
121			Class[] params = javaMethod.getParameterTypes();
122			Object[] args;
123			if ((params.length == 1) && params[0].equals(ATObject[].class)) {
124				args= new Object[] { jArguments };
125			} else {
126				if (params.length != jArguments.length) {
127					throw new XArityMismatch("native method "+Reflection.downSelector(javaMethod.getName()), params.length, jArguments.length);
128				}
129				// make sure to properly 'coerce' each argument into the proper AT interface type
130				args = coerceArguments(jArguments, params);
131			}
132			Object rval = javaMethod.invoke(jReceiver, args);
133			if (rval instanceof ATObject) {
134				return (ATObject) rval;
135			} else {
136				return primitiveJavaToATObject(rval);
137			}
138		} catch (IllegalAccessException e) {
139			// the invoked method is not publicly accessible
140			throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName()) + " not accessible.", e);
141		} catch (IllegalArgumentException e) {
142			// illegal argument types were supplied
143			throw new XIllegalArgument("Illegal argument for native method "+Reflection.downSelector(javaMethod.getName()) + ": " + e.getMessage(), e);
144		} catch (InvocationTargetException e) {
145			// the invoked method threw an exception
146			if (e.getTargetException() instanceof InterpreterException)
147				throw (InterpreterException) e.getTargetException();
148			else if (e.getTargetException() instanceof Signal) {
149			    throw (Signal) e.getTargetException();	
150			} else {
151				e.printStackTrace();
152				throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName())+" threw internal exception", e.getTargetException());
153		    }
154		}
155	}
156	
157	/**
158	 * Try to create a new instance of a Java class given an array of initialization arguments.
159	 * Because we do not have exact typing information, all of the public constructors of the
160	 * class are traversed until one is found that can create new instances given the current
161	 * initargs.
162	 */
163	public static ATObject createNativeATObject(Class jClass, ATObject[] jInitArgs) throws InterpreterException {
164		Constructor[] ctors = jClass.getConstructors();
165		for (int i = 0; i < ctors.length; i++) {
166			Constructor ctor = ctors[i];
167			if (ctor.getParameterTypes().length == jInitArgs.length) {
168				try {
169					// make sure to properly 'coerce' each argument into the proper AT interface type
170					Object[] coercedInitArgs = coerceArguments(jInitArgs, ctor.getParameterTypes());
171					return (ATObject) ctor.newInstance(coercedInitArgs);
172				} catch (IllegalArgumentException e) {
173					continue; // argument types don't match, may find other constructor
174				} catch (InstantiationException e) {
175					break; // class is an abstract class, won't find a match
176				} catch (IllegalAccessException e) {
177					continue; // private or protected constructor, may find another one
178				} catch (InvocationTargetException e) {
179					// an exception was raised by the constructor
180					if (e.getTargetException() instanceof InterpreterException)
181						throw ((InterpreterException) e.getTargetException());
182					else if (e.getTargetException() instanceof Signal) {
183					    throw (Signal) e.getTargetException();	
184					} else // fatal exception
185						throw new XIllegalOperation("Instance creation of type " + jClass.getName() + " failed: " + e.getMessage());
186				}
187			} else {
188				// arity does not match, try finding another one
189				continue;
190			}
191		}
192		// no matching constructors were found
193		throw new XIllegalOperation("Unable to create a new instance of type " + jClass.getName());
194	}
195	
196	public static Method getNativeATMethod(
197			Class baseInterface, 
198			ATObject receiver,
199			String methodName, ATSymbol atSelector) throws InterpreterException {
200		Method[] applicable = getMethodsForSelector(baseInterface, methodName);
201		switch (applicable.length) {
202			case 0:
203				throw new XSelectorNotFound(atSelector, receiver);
204			case 1:
205				return applicable[0];
206			default:
207				throw new XIllegalOperation("Native method uses overloading: " + atSelector + " in " + baseInterface);
208		}
209	}
210	
211	/**
212	 * Returns all public methods from the given class parameter whose name starts with the
213	 * given prefix. Moreover, the boolean parameter isStatic determines whether
214	 * to consider only static or only non-static methods.
215	 */
216	public static Method[] allMethodsPrefixed(Class fromClass, String prefix, boolean isStatic) {
217		// all public methods defined in the class
218		Method[] allPublicMethods = (isStatic) ? fromClass.getDeclaredMethods() : fromClass.getMethods();
219		
220		Vector matchingMethods = new Vector(allPublicMethods.length);
221		for (int i = 0; i < allPublicMethods.length; i++) {
222			Method m = allPublicMethods[i];
223			if (Modifier.isStatic(m.getModifiers()) == isStatic) {
224				if (m.getName().startsWith(prefix)) {
225					matchingMethods.add(m);
226				}
227			}
228		}
229		return (Method[]) matchingMethods.toArray(new Method[matchingMethods.size()]);
230	}
231	
232	/**
233	 * Since Java uses strict matching when asked for a method, given an array of 
234	 * classes, this often means that the types are overspecified and therefore no
235	 * matches can be found. As a consequence we have our own mechanism to select
236	 * which set of methods is applicable given a selector. Further dispatch needs
237	 * only to be performed when more than a single match exists.
238	 * @param jClass - the class from which the methods will be selected.
239	 * @param selector - the name of the requested method.
240	 * @return an array of applicable methods
241	 */
242	private static Method[] getMethodsForSelector(Class jClass, String selector) {
243		Method[] allMethods = jClass.getMethods();
244		
245		Vector matchingMethods = new Vector();
246		int numMatchingMethods = 0;
247		
248		for (int i = 0; i < allMethods.length; i++) {
249			if (allMethods[i].getName().equals(selector)) {
250				matchingMethods.addElement(allMethods[i]);
251				numMatchingMethods++;
252			}
253		}
254		
255		return (Method[])matchingMethods.toArray(new Method[numMatchingMethods]);
256	}
257	
258	private static Object[] coerceArguments(ATObject[] args, Class[] types) throws XTypeMismatch {
259		Object[] coercedArgs = new Object[args.length];
260		for (int i = 0; i < args.length; i++) {
261			coercedArgs[i] = Coercer.coerce(args[i], types[i]);
262		}
263		return coercedArgs;
264	}
265	
266	public static final boolean isPrimitiveType(Class c) {
267		return (c.isPrimitive() ||
268				c == Integer.class ||
269				c == Double.class ||
270				c == Float.class ||
271				c == Character.class ||
272				c == Boolean.class ||
273				c == Byte.class ||
274				c == Long.class ||
275				c == Short.class ||
276				c == Void.class);
277	}
278	
279	/**
280	 * Convert an primitive Java value to an AmbientTalk object
281	 * Supported mappings are:
282	 *  int -> NATNumber
283	 *  double -> NATFraction
284	 *  char -> NATText
285	 *  boolean -> NATBoolean
286	 *  void -> NIL
287	 *  
288	 *  float, byte, long and short are left as JavaObjects.
289	 *  They are not 'converted' into AmbientTalk primitives because of the opposite
290	 *  reasons why e.g. numbers cannot be converted automatically to long, short, etc.
291	 *  See the description of the 'atObjectToPrimitiveJava' method.
292	 *  
293	 *  Conversion from e.g. a java.lang.Long to a NATNumber is still possible by
294	 *  using code such as:
295	 *    
296	 *    <code>aWrappedLongValue.intValue();</code>
297	 */
298	public static final ATObject primitiveJavaToATObject(Object jObj) throws XReflectionFailure {
299		// integer
300		if (jObj instanceof Integer) {
301			return NATNumber.atValue(((Integer) jObj).intValue());
302		// double
303		} else if (jObj instanceof Double) {
304			return NATFraction.atValue(((Double) jObj).doubleValue());
305		// char
306		} else if (jObj instanceof Character) {
307			return NATText.atValue(((Character) jObj).toString());
308		// boolean
309		} else if (jObj instanceof Boolean) {
310			return NATBoolean.atValue(((Boolean) jObj).booleanValue());
311		// float
312		} else if (jObj instanceof Float) {
313			return JavaObject.wrapperFor(jObj);
314			//return NATFraction.atValue(((Float) jObj).floatValue());
315		// byte
316		} else if (jObj instanceof Byte) {
317			return JavaObject.wrapperFor(jObj);
318			//return NATNumber.atValue(((Byte) jObj).byteValue());
319		// long
320		} else if (jObj instanceof Long) {
321			return JavaObject.wrapperFor(jObj);
322			//return NATFraction.atValue(((Long) jObj).longValue());
323		// short
324		} else if (jObj instanceof Short) {
325			return JavaObject.wrapperFor(jObj);
326			//return NATNumber.atValue(((Short) jObj).shortValue());
327		} else {
328		    throw new XReflectionFailure("Expected a primitive Java value, given: " + jObj);
329		}
330	}
331	
332	/**
333	 * Convert an ambienttalk object to a primitive type.
334	 * Supported mappings are:
335	 *  NATNumber -> int
336	 *  NATFraction -> double
337	 *  NATText -> char
338	 *  NATBoolean -> boolean
339	 *  ATObject -> void
340	 *  
341	 *  Conversion to float, byte, long and short is not supported.
342	 *  The reason for this is that otherwise, symbiotic invocations
343	 *  will match with a lot of method signatures, and we would have to keep
344	 *  track of the 'best fitting match'. E.g. given methods m(int) m(long) and m(short)
345	 *  then invoking 'o.m(10)' in AmbientTalk would match all three methods.
346	 *  By disabling conversions from NATNumber to long and short, only one match remains.
347	 *  If conversion to any one of these primitive types is needed, use code such as:
348	 *    
349	 *    <code>jlobby.java.lang.Integer.new(10).longValue();</code>
350	 */
351	public static final Object atObjectToPrimitiveJava(ATObject atObj, Class type) throws XTypeMismatch, XIllegalArgument {
352		// integer
353		if (type == int.class || type == Integer.class) {
354			return new Integer(atObj.asNativeNumber().javaValue);
355		// double
356		} else if (type == double.class || type == Double.class) {
357			return new Double(atObj.asNativeFraction().javaValue);
358		// char
359		} else if (type == char.class || type == Character.class) {
360			return new Character(atObj.asNativeText().asChar());
361		// boolean
362		} else if (type == boolean.class || type == Boolean.class) {
363			return new Boolean(atObj.asNativeBoolean().javaValue);
364		// float
365		} else if (type == float.class || type == Float.class) {
366			// can only convert wrapped java.lang.Float
367			if (atObj.isJavaObjectUnderSymbiosis()) {
368				Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
369				if (wrapped instanceof Float) {
370					return wrapped;
371				}
372			}
373			throw new XTypeMismatch(Float.class, atObj);
374			//return Float.valueOf((float) atObj.asNativeFraction().javaValue);
375		// byte
376		} else if (type == byte.class || type == Byte.class) {
377			// can only convert wrapped java.lang.Byte
378			if (atObj.isJavaObjectUnderSymbiosis()) {
379				Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
380				if (wrapped instanceof Byte) {
381					return wrapped;
382				}
383			}
384			throw new XTypeMismatch(Byte.class, atObj);
385			//return Byte.valueOf((byte) atObj.asNativeNumber().javaValue);
386		// long
387		} else if (type == long.class || type == Long.class) {
388			// can only convert wrapped java.lang.Long
389			if (atObj.isJavaObjectUnderSymbiosis()) {
390				Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
391				if (wrapped instanceof Long) {
392					return wrapped;
393				}
394			}
395			throw new XTypeMismatch(Long.class, atObj);
396			//return Long.valueOf((long) atObj.asNativeFraction().javaValue);
397		// short
398		} else if (type == short.class || type == Short.class) {
399			// can only convert wrapped java.lang.Short
400			if (atObj.isJavaObjectUnderSymbiosis()) {
401				Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
402				if (wrapped instanceof Short) {
403					return wrapped;
404				}
405			}
406			throw new XTypeMismatch(Short.class, atObj);
407			//return Short.valueOf((short) atObj.asNativeNumber().javaValue);
408		} else if (type == void.class || type == Void.class) {
409			return null;
410		} else {
411		    throw new XIllegalArgument("Expected a primitive Java type, given: " + type);
412		}
413	}
414}