PageRenderTime 81ms CodeModel.GetById 18ms app.highlight 55ms RepoModel.GetById 2ms app.codeStats 0ms

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

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