PageRenderTime 54ms CodeModel.GetById 14ms app.highlight 32ms RepoModel.GetById 2ms app.codeStats 0ms

/interpreter/tags/at2dist130208/src/edu/vub/at/objects/symbiosis/Symbiosis.java

http://ambienttalk.googlecode.com/
Java | 723 lines | 403 code | 32 blank | 288 comment | 137 complexity | 75c2c947a5a0e1207c98d1eef579be2a MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * Symbiosis.java created on 5-nov-2006 at 19:22:26
  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.symbiosis;
 29
 30import edu.vub.at.eval.Evaluator;
 31import edu.vub.at.exceptions.InterpreterException;
 32import edu.vub.at.exceptions.XArityMismatch;
 33import edu.vub.at.exceptions.XIllegalArgument;
 34import edu.vub.at.exceptions.XNotInstantiatable;
 35import edu.vub.at.exceptions.XReflectionFailure;
 36import edu.vub.at.exceptions.XSelectorNotFound;
 37import edu.vub.at.exceptions.XSymbiosisFailure;
 38import edu.vub.at.exceptions.XTypeMismatch;
 39import edu.vub.at.exceptions.XUnassignableField;
 40import edu.vub.at.exceptions.XUndefinedSlot;
 41import edu.vub.at.exceptions.signals.Signal;
 42import edu.vub.at.objects.ATObject;
 43import edu.vub.at.objects.coercion.Coercer;
 44import edu.vub.at.objects.mirrors.JavaInterfaceAdaptor;
 45import edu.vub.at.objects.mirrors.Reflection;
 46import edu.vub.at.objects.natives.NATException;
 47import edu.vub.at.objects.natives.OBJNil;
 48import edu.vub.at.objects.natives.NATTable;
 49import edu.vub.at.objects.natives.NATText;
 50
 51import java.lang.reflect.Array;
 52import java.lang.reflect.Constructor;
 53import java.lang.reflect.Field;
 54import java.lang.reflect.InvocationTargetException;
 55import java.lang.reflect.Method;
 56import java.lang.reflect.Modifier;
 57import java.lang.reflect.UndeclaredThrowableException;
 58import java.util.EventListener;
 59import java.util.HashSet;
 60import java.util.Hashtable;
 61import java.util.Iterator;
 62import java.util.LinkedList;
 63import java.util.Vector;
 64
 65/**
 66 * The Symbiosis class is a container for auxiliary methods pertaining to making symbiotic
 67 * reflective Java invocations.
 68 * 
 69 * @author tvcutsem
 70 */
 71public final class Symbiosis {
 72
 73	/**
 74	 * Invoke a java method symbiotically, given only its name (not its implementation).
 75	 * First retrieves all of the methods matching the given selector in the given class, then tries
 76	 * to invoke the method symbiotically using the default symbiotic invocation algorithm.
 77	 * 
 78	 * @see #symbioticInvocation(ATObject, Object, String, JavaMethod, ATObject[]) the symbiotic invocation algorithm.
 79	 */
 80	public static ATObject symbioticInvocation(ATObject wrapper, Object symbiont, Class ofClass, String selector, ATObject[] atArgs) throws InterpreterException {
 81		return symbioticInvocation(wrapper, symbiont, selector, getMethods(ofClass, selector, (symbiont==null)), atArgs);
 82	}
 83	
 84	/**
 85	 * Invoke a java method symbiotically.
 86	 * The Java method invocation algorithm is as follows:
 87	 * <pre>
 88	 * case of # of methods matching selector:
 89	 *   0 => XSelectorNotFound
 90	 *   1 => invoke the method OR XIllegalArgument, XArityMismatch, XReflectionFailure
 91	 *   * => (case of # of methods with matching arity OR taking varargs:
 92	 *           0 => XSymbiosisFailure
 93	 *           1 => invoke the method OR XIllegalArgument, XReflectionFailure
 94	 *           * => (case of # of methods matching 'default type' of the actual arguments:
 95	 *                   0 => XSymbiosisFailure
 96	 *                   1 => invoke OR XReflectionFailure
 97	 *                   * => XSymbiosisFailure))
 98	 * </pre>
 99	 * A Java method takes a variable number of AT arguments <=> it has one formal parameter of type ATObject[]
100	 * 
101	 * @param wrapper the ATObject wrapper for the symbiont
102	 * @param symbiont the Java object being accessed from within AmbientTalk
103	 * @param selector the Java selector corresponding to the method invocation
104	 * @param jMethod a JavaMethod encapsulating all applicable Java methods that correspond to the selector
105	 * @param atArgs the AT args to the symbiotic invocation
106	 * @return the wrapped result of the Java method invocation
107	 * 
108	 * @throws XArityMismatch if the wrong number of arguments were supplied
109	 * @throws XSelectorNotFound if no methods correspond to the given selector (i.e. jMethod is null)
110	 * @throws XTypeMismatch if one of the arguments cannot be converted into the static type expected by the Java method
111	 * @throws XSymbiosisFailure if the method is overloaded and cannot be unambiguously resolved given the actual arguments
112	 * @throws XReflectionFailure if the invoked method is not accessible from within AmbientTalk
113	 * @throws XJavaException if the invoked Java method throws a Java exception
114	 */
115	public static ATObject symbioticInvocation(ATObject wrapper, Object symbiont, String selector, JavaMethod jMethod, ATObject[] atArgs)
116	                                           throws InterpreterException {
117		if (jMethod == null) {
118		    // no methods found? selector does not exist...
119			throw new XSelectorNotFound(Reflection.downSelector(selector), wrapper);
120		} else {
121			Method[] methods = jMethod.choices_;
122			if (methods.length == 1) {
123				// just one method found, no need to resolve overloaded methods
124				// if the Java method takes an ATObject array as its sole parameter, it is interpreted as taking
125				// a variable number of ambienttalk arguments
126				Class[] params = methods[0].getParameterTypes();
127				Object[] args;
128				if ((params.length == 1) && params[0].equals(ATObject[].class)) {
129					args = new Object[] { atArgs };
130				} else {
131					if (params.length != atArgs.length) {
132						throw new XArityMismatch("Java method "+Reflection.downSelector(methods[0].getName()), params.length, atArgs.length);
133					}
134					// make sure to properly 'coerce' each argument into the proper AT interface type
135					args = atArgsToJavaArgs(atArgs, params);
136				}
137				return invokeUniqueSymbioticMethod(symbiont, methods[0], args);
138			} else {	
139				// overloading: filter out all methods that do not match arity or whose
140				// argument types do not match
141				Object[] actuals = null;
142				Class[] params;
143				LinkedList matchingMethods = new LinkedList();
144				// this boolean keeps track of whether or not failure to resolve the overloaded
145				// method is solely because of an arity mismatch, not because of a type mismatch
146				
147				boolean failedDueToArityOnly = true;
148				
149				for (int i = 0; i < methods.length; i++) {
150					params = methods[i].getParameterTypes();
151					// is the method a varargs method?
152					if ((params.length == 1) && params[0].equals(ATObject[].class)) {
153						actuals = new Object[] { atArgs };
154						matchingMethods.addFirst(methods[i]);
155					// does the arity match?
156					} else if (params.length == atArgs.length) {
157						// can it be invoked with the given actuals?
158						try {
159							actuals = atArgsToJavaArgs(atArgs, params);
160							matchingMethods.addFirst(methods[i]);
161						} catch(XTypeMismatch e) {
162							// types don't match
163							failedDueToArityOnly = false;
164						}
165					} else {
166				      // arity does not match
167					}
168				}
169				
170				switch (matchingMethods.size()) {
171				    case 0: {
172					    // no methods left: overloading resolution failed...
173				    	if (failedDueToArityOnly) {
174				    		// ... because of an arity mismatch
175				    		throw new XSymbiosisFailure(methods[0], atArgs.length);
176				    	} else {
177				    		// ... because the types could not be resolved
178				    		throw new XSymbiosisFailure(symbiont, methods[0], atArgs);
179				    	}
180					    
181				    }
182				    case 1: {
183				    	// just one method left, invoke it
184						return invokeUniqueSymbioticMethod(symbiont, (Method) matchingMethods.getFirst(), actuals);
185				    }
186				    default: {
187				    	// more than one method left: overloading resolution failed
188						throw new XSymbiosisFailure(symbiont, selector, matchingMethods, atArgs);
189				    }
190				}
191			}
192		}
193	}
194	
195	/**
196	 * Creates a new instance of a Java class.
197	 * 
198	 * @param ofClass the Java class of which to create an instance
199	 * @param atArgs the AmbientTalk arguments to the constructor, to be converted to Java arguments
200	 * @return an unitialized JavaObject wrapper around a newly created instance of the class
201	 * 
202	 * @throws XArityMismatch if the wrong number of arguments were supplied
203	 * @throws XNotInstantiatable if no public constructors are available or if the class is abstract
204	 * @throws XTypeMismatch if one of the arguments cannot be converted into the static type expected by the constructor
205	 * @throws XSymbiosisFailure if the constructor is overloaded and cannot be unambiguously resolved given the actual arguments
206	 * @throws XReflectionFailure if the invoked constructor is not accessible from within AmbientTalk
207	 * @throws XJavaException if the invoked Java constructor throws a Java exception
208	 */
209	public static ATObject symbioticInstanceCreation(Class ofClass, ATObject[] atArgs) throws InterpreterException {
210		Constructor[] ctors = ofClass.getConstructors();
211		switch (ctors.length) {
212		     // no constructors found? class is not instantiatable...
213		case 0:
214			throw new XNotInstantiatable(ofClass);
215			// just one constructor found, no need to resolve overloaded methods
216		case 1: {
217			// if the constructor takes an ATObject array as its sole parameter, it is interpreted as taking
218			// a variable number of ambienttalk arguments
219			Class[] params = ctors[0].getParameterTypes();
220			Object[] args;
221			if ((params.length == 1) && params[0].equals(ATObject[].class)) {
222				args = new Object[] { atArgs };
223			} else {
224				if (params.length != atArgs.length) {
225					throw new XArityMismatch("Java constructor "+Reflection.downSelector(ctors[0].getName()), params.length, atArgs.length);
226				}
227				// make sure to properly convert actual arguments into Java objects
228				args = atArgsToJavaArgs(atArgs, params);
229			}
230			return invokeUniqueSymbioticConstructor(ctors[0], args);
231		  }	
232		}
233		
234		// overloading: filter out all constructors that do not match arity or whose argument types do not match
235		int matchingCtors = 0;
236		Constructor matchingCtor = null;
237		Object[] actuals = null;
238		Class[] params;
239		for (int i = 0; i < ctors.length; i++) {
240			params = ctors[i].getParameterTypes();
241			// is the constructor a varargs constructor?
242			if ((params.length == 1) && params[0].equals(ATObject[].class)) {
243				actuals = new Object[] { atArgs };
244				matchingCtor = ctors[i];
245				matchingCtors++;
246				// does the arity match?
247			} else if (params.length == atArgs.length) {
248				// can it be invoked with the given actuals?
249				try {
250					actuals = atArgsToJavaArgs(atArgs, params);
251					matchingCtor = ctors[i];
252					matchingCtors++;
253				} catch(XTypeMismatch e) {
254					// types don't match
255					ctors[i] = null; // TODO: don't assign to null, array may be cached or used later on (or by wrapper method)
256				}
257			} else {
258				// arity does not match
259				ctors[i] = null;
260			}
261		}
262		
263		if (matchingCtors != 1) {
264			// no constructors left or more than one constructor left? overloading resolution failed
265			throw new XSymbiosisFailure(ofClass, ctors, atArgs, matchingCtors);
266		} else {
267			// just one constructor left, invoke it
268			return invokeUniqueSymbioticConstructor(matchingCtor, actuals);
269		}
270	}
271	
272	/**
273	 * Read a field from the given Java object reflectively.
274	 * @return the contents of the Java field, converted into its AmbientTalk equivalent
275	 */
276	public static ATObject readField(Object fromObject, Class ofClass, String fieldName)
277	                     throws InterpreterException {
278		Field f = getField(ofClass, fieldName, (fromObject == null));
279		return readField(fromObject, f);
280	}
281	
282	/**
283	 * Read a field from the given Java object reflectively.
284	 * @return the contents of the Java field, converted into its AmbientTalk equivalent
285	 */
286	public static ATObject readField(Object fromObject, Field f) throws InterpreterException {
287		try {
288			return Symbiosis.javaToAmbientTalk(f.get(fromObject));
289		} catch (IllegalArgumentException e) {
290			// the given object is of the wrong class, should not happen!
291			throw new XReflectionFailure("Illegal class for field access of "+f.getName() + ": " + e.getMessage());
292		} catch (IllegalAccessException e) {
293             // the read field is not publicly accessible
294			throw new XReflectionFailure("field access of " + f.getName() + " not accessible.");
295		}
296	}
297	
298	/**
299	 * Write a field in the given Java object reflectively.
300	 * @param toObject if null, the field is assumed to be static
301	 * @param value the AmbientTalk value which will be converted into its Java equivalent to be written int he field
302	 */
303	public static void writeField(Object toObject, Class ofClass, String fieldName, ATObject value)
304	                              throws InterpreterException {
305		Field f = getField(ofClass, fieldName, (toObject == null));
306		writeField(toObject, f, value);
307	}
308	
309	/**
310	 * Write a field in the given Java object reflectively.
311	 * @param value the AmbientTalk value which will be converted into its Java equivalent to be written int he field
312	 */
313	public static void writeField(Object toObject, Field f, ATObject value) throws InterpreterException {
314		try {
315			f.set(toObject, Symbiosis.ambientTalkToJava(value, f.getType()));
316		} catch (IllegalArgumentException e) {
317			// the given value is of the wrong type
318			throw new XIllegalArgument("Illegal value for field "+f.getName() + ": " + e.getMessage());
319		} catch (IllegalAccessException e) {
320             // the read field is not publicly accessible or final
321			throw new XUnassignableField(Reflection.downSelector(f.getName()).toString());
322		}
323	}
324	
325	/**
326	 * Query whether the given Java Class contains a (non-)static method with the given selector
327	 */
328	public static boolean hasMethod(Class c, String selector, boolean isStatic) {
329		Method[] methods = (isStatic) ? c.getDeclaredMethods() : c.getMethods();
330		for (int i = 0; i < methods.length; i++) {
331			if (Modifier.isStatic(methods[i].getModifiers()) == isStatic) {
332				if (methods[i].getName().equals(selector)) {
333					return true;
334				}
335			}
336		}
337		return false;
338	}
339	
340	/**
341	 * Query whether the given Java Class contains a (non-)static field with the given selector
342	 */
343	public static boolean hasField(Class c, String selector, boolean isStatic) {
344		try {
345			Field f = c.getField(selector);
346			return (Modifier.isStatic(f.getModifiers()) == isStatic);
347		} catch (NoSuchFieldException e) {
348			return false;
349		}
350	}
351	
352	/**
353	 * Retrieve a field from a Java object.
354	 * @throws XUndefinedSlot if the field does not exist or its static property does not match
355	 */
356	public static Field getField(Class fromClass, String fieldName, boolean isStatic) throws XUndefinedSlot {
357		try {
358			Field f = fromClass.getField(fieldName);
359			if ((Modifier.isStatic(f.getModifiers())) == isStatic) {
360				return f;
361			} else {
362				throw new XUndefinedSlot("field access ", fieldName + " not accessible.");
363			}
364		} catch (NoSuchFieldException e) {
365			// the field does not exist
366			throw new XUndefinedSlot("field access ", fieldName + " not accessible.");
367		}
368	}
369	
370	/**
371	 * Retrieve all methods of a given name from a Java object. These are bundled together
372	 * in a first-class JavaMethod object, which is cached for later reference.
373	 * 
374	 * A null return value indicates no matches.
375	 */
376	public static JavaMethod getMethods(Class fromClass, String selector, boolean isStatic) {
377		// first, check the method cache
378		JavaMethod cachedEntry = JMethodCache._INSTANCE_.get(fromClass, selector, isStatic);
379		if (cachedEntry != null) {
380			// cache hit
381			return cachedEntry;
382		} else {
383			// cache miss: assemble a new JavaMethod entry
384			Method[] methods = (isStatic) ? fromClass.getDeclaredMethods() : fromClass.getMethods();
385			Method m;
386			Vector properMethods = new Vector(methods.length);
387			for (int i = 0; i < methods.length; i++) {
388				m = methods[i];
389				if ((Modifier.isStatic(m.getModifiers())) == isStatic && m.getName().equals(selector)) {
390					properMethods.add(methods[i]);
391				}
392			}
393			Method[] choices = (Method[]) properMethods.toArray(new Method[properMethods.size()]);
394			if (choices.length == 0) {
395				// no matches
396				return null;
397			} else {
398				// add entry to cache and return it
399				JavaMethod jMethod = new JavaMethod(choices);
400				JMethodCache._INSTANCE_.put(fromClass, selector, isStatic, jMethod);
401				return jMethod;
402			}
403		}
404	}
405	
406	/**
407	 * Retrieve all public static or non-static methods from a given Java class
408	 * (this includes methods defined in superclasses). All methods are properly wrapped in a
409	 * JavaMethod wrapper, taking care to wrap a set of overloaded methods using the same wrapper.
410	 * 
411	 * @param isStatic if true, all static methods of fromClass are returned, otherwise the instance methods are returned
412	 */
413	public static JavaMethod[] getAllMethods(Class fromClass, boolean isStatic) {
414		// assemble a set of all unique selectors of all (non-)static methods of the class
415		HashSet uniqueNames = new HashSet();
416		Method[] methods = (isStatic) ? fromClass.getDeclaredMethods() : fromClass.getMethods();
417		for (int i = 0; i < methods.length; i++) {
418			Method m = methods[i];
419			if ((Modifier.isStatic(m.getModifiers())) == isStatic) {
420				uniqueNames.add(m.getName());
421			}
422		}
423		
424		// create a JavaMethod[] array large enough to contain all 'unique methods'
425		JavaMethod[] jmethods = new JavaMethod[uniqueNames.size()];
426		// loop over all entries and group the methods into a single wrapper
427		int i = 0;
428		for (Iterator iter = uniqueNames.iterator(); iter.hasNext();) {
429			String methodName = (String) iter.next();
430			jmethods[i++] = getMethods(fromClass, methodName, isStatic);
431		}
432		return jmethods;
433	}
434	
435	/**
436	 * Retrieve all public static or non-static fields from a given Java class
437	 * (this includes fields defined in superclasses, but excludes shadowed superclass fields). All fields are properly wrapped in a
438	 * JavaField wrapper.
439	 * 
440	 * @param ofObject if null, all static fields of fromClass are returned, otherwise the instance fields are returned
441	 */
442	public static JavaField[] getAllFields(Object ofObject, Class fromClass) {
443		boolean isStatic = (ofObject == null);
444		Field[] fields = fromClass.getFields();
445		// we do not consider shadowed superclass fields, therefore we store all encountered fields
446		// in a table and only keep the field with the most specific class
447		Hashtable recordedFields = new Hashtable();
448		for (int i = 0; i < fields.length; i++) {
449			Field f = fields[i];
450			if ((Modifier.isStatic(f.getModifiers())) == isStatic) {
451				// did we already encounter this field?
452				if (recordedFields.contains(f.getName())) {
453					// yes, then compare encountered field with previous field and only store most specific one
454					Field prev = (Field) recordedFields.get(f.getName());
455					// is f's Class a subclass of prev's Class?
456					if (prev.getDeclaringClass().isAssignableFrom(f.getDeclaringClass())) {
457						// yes, so f is more specific, store it instead of prev
458						recordedFields.remove(prev.getName());
459						recordedFields.put(f.getName(), f);
460					} // if not, keep previous field
461				} else {
462					// field not encountered  yet, store it
463					recordedFields.put(f.getName(), f);
464				}
465			}
466		}
467		// create a JavaField[] array large enough to contain all entries in the table
468		JavaField[] jfields = new JavaField[recordedFields.size()];
469		// loop over all entries in the table and wrap each field
470		int i = 0;
471		for (Iterator iter = recordedFields.values().iterator(); iter.hasNext(); i++) {
472			jfields[i] = new JavaField(ofObject, (Field) iter.next());
473		}
474		return jfields;
475	}
476
477	/**
478	 * Convert a Java object into an AmbientTalk object, according to
479	 * the following rules:
480	 * <pre>
481	 * null = nil
482	 * ATObject obj = obj
483	 * int n = Number(n)
484	 * double d = Fraction(d)
485	 * boolean b = Boolean(b)
486	 * String s = Text(s)
487	 * T[] array = Table(array.length)
488	 * InterpreterException e = NATException(e)
489	 * Exception e = NATException(XJavaException(e))
490	 * Class c = JavaClass(c)
491	 * Object o = JavaObject(o)
492	 * </pre>
493	 * 
494	 * @param jObj the Java object representing a mirror or a native type
495	 * @return the same object if it implements the ATObject interface
496	 */
497	public static final ATObject javaToAmbientTalk(Object jObj) throws InterpreterException {
498		// -- NULL => NIL --
499	    if (jObj == null) {
500		  return OBJNil._INSTANCE_;
501		// -- AmbientTalk implementation-level objects --
502		// -- This also includes COERCED objects! (i.e. symbiotic objects) --
503	    } else if (jObj instanceof ATObject) {
504			return (ATObject) jObj;
505	    // -- PRIMITIVE TYPE => NUMERIC, TXT --
506		} else if (JavaInterfaceAdaptor.isPrimitiveType(jObj.getClass())) {
507		    return JavaInterfaceAdaptor.primitiveJavaToATObject(jObj);
508		// -- STRING => TEXT --
509		} else if (jObj instanceof String) {
510			return NATText.atValue((String) jObj);
511		// -- ARRAY => TABLE --
512		} else if (jObj.getClass().isArray()) {
513			int length = Array.getLength(jObj);
514			ATObject[] atTable = new ATObject[length];
515			for (int i = 0; i < length; i++) {
516				atTable[i] = javaToAmbientTalk(Array.get(jObj, i));
517			}
518			return NATTable.atValue(atTable);
519	    // -- EXCEPTION => NATEXCEPTION --
520		} else if(jObj instanceof InterpreterException) {
521			return ((InterpreterException)jObj).getAmbientTalkRepresentation();
522		} else if (jObj instanceof Exception) {
523			return new NATException(new XJavaException((Exception) jObj));
524		// -- java.lang.Class => Symbiotic Class --
525		} else if (jObj instanceof Class) {
526			return JavaClass.wrapperFor((Class) jObj);
527		// -- Object => Symbiotic AT Object --
528		} else {
529			return JavaObject.wrapperFor(jObj);
530		}
531	}
532
533	/**
534	 * Convert an AmbientTalk object into an equivalent Java object, according
535	 * to the following rules:
536	 * <pre>
537	 * Number n -> int = n.javaValue
538	 * Fraction f -> double = f.javaValue
539	 * Boolean b -> boolean = b.javaValue
540	 * Text t -> String = t.javaValue
541	 * JavaObject jobj -> T = (T) jobj.wrappedObject
542	 * ATObject obj -> ATObject = obj
543	 * Table obj -> T[] = new T[obj.length]
544	 * NATException exc -> Exception = exc.wrappedException
545	 * JavaClass jcls -> Class = jcls.wrappedClass
546	 * nil -> Object = null
547	 * ATObject obj -> Interface = Coercer<obj,Interface>
548	 * </pre>
549	 * @param atObj the AmbientTalk object to convert to a Java value
550	 * @param targetType the known static type of the Java object that should be attained
551	 * @return a Java object o where (o instanceof targetType) should yield true
552	 * 
553	 * @throws XTypeMismatch if the object cannot be converted into the correct Java targetType
554	 */
555	public static final Object ambientTalkToJava(ATObject atObj, Class targetType) throws InterpreterException {
556		// -- PRIMITIVE TYPES --
557        if (JavaInterfaceAdaptor.isPrimitiveType(targetType)) {
558		    return JavaInterfaceAdaptor.atObjectToPrimitiveJava(atObj, targetType);
559		// -- WRAPPED JAVA OBJECTS --
560        } else if (atObj.isJavaObjectUnderSymbiosis()) {
561	    	Object jObj = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
562		    Class jCls = jObj.getClass();
563		    // dynamic subtype test: is jCls a subclass of targetType?
564		    if (targetType.isAssignableFrom(jCls)) {
565		    	return jObj;
566		    }
567	    }
568        
569        // -- IMPLEMENTATION-LEVEL OBJECTS --
570        if (targetType.isInstance(atObj)) {
571			// target type is a subtype of ATObject, return the implementation-level object itself
572        	// also, it may be that atObj is actually a coerced AT object, so it may
573        	// directly match a target Java interface
574			return atObj;
575		// -- STRINGS --
576		} else if (targetType == String.class) {
577			return atObj.asNativeText().javaValue;
578		// -- ARRAYS --
579		} else if (targetType.isArray()) {
580			ATObject[] atArray = atObj.asNativeTable().elements_;
581			Object jArray = Array.newInstance(targetType.getComponentType(), atArray.length);
582			for (int i = 0; i < Array.getLength(jArray); i++) {
583				Array.set(jArray, i, ambientTalkToJava(atArray[i], targetType.getComponentType()));
584			}
585			return jArray;
586		// -- EXCEPTIONS --
587		} else if (Exception.class.isAssignableFrom(targetType)) {
588			return Evaluator.asNativeException(atObj);
589		// -- CLASS OBJECTS --
590		} else if (targetType == Class.class) {
591			return atObj.asJavaClassUnderSymbiosis().getWrappedClass();
592	    // -- nil => NULL --
593		} else if (atObj == OBJNil._INSTANCE_) {
594			return null;
595		// -- INTERFACE TYPES AND NAT CLASSES --
596		} else {
597			// only allow NATObject and subclasses to be coerced into an interface
598			if (atObj.isAmbientTalkObject()) {
599				return Coercer.coerce(atObj, targetType);
600			} else {
601				throw new XTypeMismatch(targetType, atObj);
602			}
603		}
604	}
605	
606	/**
607	 * Returns whether the symbiosis layer should process the given method purely
608	 * asynchronously or not.
609	 * 
610	 * @return whether the specified Java method denotes an event notification
611	 */
612	public static boolean isEvent(Method method) {
613		return EventListener.class.isAssignableFrom(method.getDeclaringClass()) // is an EventListener
614		    && (method.getReturnType() == Void.TYPE) // does not return a value
615		    && (method.getExceptionTypes().length == 0); // throws no exceptions
616	}
617	
618	private static ATObject invokeUniqueSymbioticMethod(Object symbiont, Method javaMethod, Object[] jArgs) throws InterpreterException {
619		try {
620			return Symbiosis.javaToAmbientTalk(javaMethod.invoke(symbiont, jArgs));
621		} catch (IllegalAccessException e) {
622			// the invoked method is not publicly accessible
623			// sometimes this may happen when accessing inner classes, try again with an interface method:
624			Method interfaceMethod = toInterfaceMethod(javaMethod);
625			if (interfaceMethod == null) { // no success
626				// try to perform the call without access protection
627				if (!javaMethod.isAccessible()) {
628					javaMethod.setAccessible(true);
629					return invokeUniqueSymbioticMethod(symbiont, javaMethod, jArgs);
630				} else {
631					// if access protection was already disabled, bail out
632		            throw new XReflectionFailure("Java method "+Reflection.downSelector(javaMethod.getName()) + " is not accessible.", e);
633				}
634			} else {
635				return invokeUniqueSymbioticMethod(symbiont, interfaceMethod, jArgs);
636			}
637		} catch (IllegalArgumentException e) {
638			// illegal argument types were supplied, should not happen because the conversion should have already failed earlier (in atArgsToJavaArgs)
639            // Backport from JDK 1.4 to 1.3
640            // throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java method "+javaMethod.getName(), e);
641			throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java method "+javaMethod.getName());
642		} catch (InvocationTargetException e) {
643			// the invoked method threw an exception
644			if (e.getTargetException() instanceof InterpreterException)
645				throw (InterpreterException) e.getTargetException();
646			else if (e.getTargetException() instanceof Signal) {
647			    throw (Signal) e.getTargetException();
648			} else if (e.getTargetException() instanceof UndeclaredThrowableException) {
649				// Undeclared throwable exceptions in symbiotically invoked Java code are automatically
650				// 'unwrapped' into the undeclared exception. This is useful because such undeclared
651				// thrown exceptions are often InterpreterExceptions caused by Java code calling back on AT code
652				throw new XJavaException(symbiont, javaMethod,
653						                 ((UndeclaredThrowableException) e.getTargetException()).getUndeclaredThrowable());
654			} else {
655				throw new XJavaException(symbiont, javaMethod, e.getTargetException());
656		    }
657		}
658	}
659	
660	private static ATObject invokeUniqueSymbioticConstructor(Constructor ctor, Object[] jArgs) throws InterpreterException {
661		try {
662			return Symbiosis.javaToAmbientTalk(ctor.newInstance(jArgs));
663		} catch (IllegalAccessException e) {
664			// the invoked method is not publicly accessible
665			throw new XReflectionFailure("Java constructor "+Reflection.downSelector(ctor.getName()) + " is not accessible.", e);
666		} catch (IllegalArgumentException e) {
667			// illegal argument types were supplied, should not happen because the conversion should have already failed earlier (in atArgsToJavaArgs)
668		    // Backport from JDK 1.4 to 1.3
669            // throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java constructor "+ctor.getName(), e);
670			throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java constructor "+ctor.getName());
671		} catch (InstantiationException e) {
672			// the given class is abstract
673			throw new XNotInstantiatable(ctor.getDeclaringClass(), e);
674		} catch (InvocationTargetException e) {
675			// the invoked method threw an exception
676			if (e.getTargetException() instanceof InterpreterException)
677				throw (InterpreterException) e.getTargetException();
678			else if (e.getTargetException() instanceof Signal) {
679			    throw (Signal) e.getTargetException();	
680			} else {
681				throw new XJavaException(null, ctor, e.getTargetException());
682		    }
683		}
684	}
685	
686	private static Object[] atArgsToJavaArgs(ATObject[] args, Class[] types) throws InterpreterException {
687		Object[] jArgs = new Object[args.length];
688		for (int i = 0; i < args.length; i++) {
689			jArgs[i] = Symbiosis.ambientTalkToJava(args[i], types[i]);
690		}
691		return jArgs;
692	}
693	
694	/**
695	 * Extremely vague and dirty feature of Java reflection: it can sometimes happen that
696	 * a method is invoked on a private inner class via a publicly accessible interface method.
697	 * In those cases, invoking that method results in an IllegalAccessException.
698	 * One example is invoking aVector.iterator().hasNext()
699	 * 
700	 * The problem is that aVector.iterator() returns an instance of java.util.AbstractList$Itr
701	 * which is probably private. Selecting that class's hasNext method and invoking it results in
702	 * an IllegalAccessException. This can be circumvented by invoking the hasNext method through
703	 * the java.util.Iterator interface class.
704	 */
705	private static Method toInterfaceMethod(Method m) {
706		Class[] interfaces = m.getDeclaringClass().getInterfaces();
707		if (interfaces == null) {
708			return null;
709		} else {
710			// find the method in one of the interface declarations
711			for (int i = 0; i < interfaces.length; i++) {
712				try {
713					return interfaces[i].getMethod(m.getName(), m.getParameterTypes());
714				} catch(NoSuchMethodException e) {
715					// continue searching
716				}
717			}
718			// no declared method found
719			return null;
720		}
721	}
722	
723}