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

http://ambienttalk.googlecode.com/ · Java · 654 lines · 389 code · 27 blank · 238 comment · 133 complexity · 1ab2d198793cf52ca266dd5f3b907ee6 MD5 · raw file

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