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

http://ambienttalk.googlecode.com/ · Java · 723 lines · 403 code · 32 blank · 288 comment · 137 complexity · 75c2c947a5a0e1207c98d1eef579be2a MD5 · raw file

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