PageRenderTime 83ms CodeModel.GetById 8ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

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

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