PageRenderTime 57ms CodeModel.GetById 18ms app.highlight 29ms RepoModel.GetById 0ms app.codeStats 1ms

/interpreter/tags/at2-build270707/src/edu/vub/at/objects/symbiosis/Symbiosis.java

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