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