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

http://ambienttalk.googlecode.com/ · Java · 712 lines · 397 code · 32 blank · 283 comment · 135 complexity · e4710077032f3b190411ec82c94458bf MD5 · raw file

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