PageRenderTime 64ms CodeModel.GetById 9ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/objects/mirrors/JavaInterfaceAdaptor.java

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