/interpreter/tags/at2dist130208/src/edu/vub/at/objects/mirrors/JavaInterfaceAdaptor.java

http://ambienttalk.googlecode.com/ · Java · 414 lines · 224 code · 18 blank · 172 comment · 108 complexity · b94b339b0d8a3ebb653edc7636d12de7 MD5 · raw file

  1. /**
  2. * AmbientTalk/2 Project
  3. * JavaInterfaceAdaptor.java created on Jul 13, 2006 at 10:25:01 PM
  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.mirrors;
  29. import edu.vub.at.exceptions.InterpreterException;
  30. import edu.vub.at.exceptions.XArityMismatch;
  31. import edu.vub.at.exceptions.XIllegalArgument;
  32. import edu.vub.at.exceptions.XIllegalOperation;
  33. import edu.vub.at.exceptions.XReflectionFailure;
  34. import edu.vub.at.exceptions.XSelectorNotFound;
  35. import edu.vub.at.exceptions.XTypeMismatch;
  36. import edu.vub.at.exceptions.signals.Signal;
  37. import edu.vub.at.objects.ATObject;
  38. import edu.vub.at.objects.coercion.Coercer;
  39. import edu.vub.at.objects.grammar.ATSymbol;
  40. import edu.vub.at.objects.natives.NATBoolean;
  41. import edu.vub.at.objects.natives.NATFraction;
  42. import edu.vub.at.objects.natives.NATNumber;
  43. import edu.vub.at.objects.natives.NATText;
  44. import edu.vub.at.objects.symbiosis.JavaObject;
  45. import java.lang.reflect.Constructor;
  46. import java.lang.reflect.InvocationTargetException;
  47. import java.lang.reflect.Method;
  48. import java.lang.reflect.Modifier;
  49. import java.util.Vector;
  50. /**
  51. * JavaInterfaceAdaptor is a class providing several static methods which allow
  52. * accessing and invoking Java methods which represent native AmbientTalk methods.
  53. * It is used by the Reflection class to up ambienttalk invocations and field
  54. * accesses and translate them using java reflection.
  55. *
  56. * @author tvcutsem
  57. * @author smostinc
  58. */
  59. public class JavaInterfaceAdaptor {
  60. /**
  61. * Tests given a class, whether the class either declares or inherits a method
  62. * for a given selector.
  63. * @param jClass - a Java class, representing an AT object.
  64. * @param jSelector - a selector, describing the method to be searched for.
  65. * @return whether a methods with a matching selector can be found
  66. */
  67. public static boolean hasApplicableJavaMethod(Class jClass, String jSelector) {
  68. Method[] allMethods = jClass.getMethods();
  69. for (int i = 0; i < allMethods.length; i++) {
  70. if (allMethods[i].getName().equals(jSelector)) {
  71. return true;
  72. }
  73. }
  74. return false;
  75. }
  76. /**
  77. * Invokes a method on a Java object identified by a selector.
  78. *
  79. * @param jClass the class of the receiver object
  80. * @param natReceiver the receiver (a native AmbientTalk object)
  81. * @param jSelector the java-level selector identifying the method to invoke
  82. * @param atSelector the original AmbientTalk selector
  83. * @param jArguments parameters, normally AT objects
  84. * @return the return value of the reflectively invoked method
  85. */
  86. public static ATObject invokeNativeATMethod(Class jClass, ATObject natReceiver,
  87. String jSelector, ATSymbol atSelector, ATObject[] jArguments) throws InterpreterException {
  88. return invokeNativeATMethod(getNativeATMethod(jClass, natReceiver, jSelector, atSelector), natReceiver, jArguments);
  89. }
  90. /**
  91. * Invokes a method on a native AmbientTalk object identified by a java.lang.reflect.Method object.
  92. * Note that if the method to invoke reflectively has a formal parameter list consisting
  93. * of one argument of type ATObject[], then the arguments are wrapped in an array such that
  94. * the function actually takes a variable number of arguments.
  95. *
  96. * A native AmbientTalk method is an ordinary Java method with the following constraints:
  97. * - its name usually starts with base_ or meta_, identifying whether the method is accessible at
  98. * the AmbientTalk base or meta level
  99. * - its formal parameters MUST all be subtypes of ATObject (or be a single array of ATObject[] for varargs)
  100. * - its return type must be a subtype of ATObject or a native Java type
  101. * (native types are subject to default conversion to the appropriate AmbientTalk natives)
  102. * - it may only throw InterpreterException exceptions
  103. *
  104. * @param javaMethod the Java method to invoke
  105. * @param jReceiver the Java object representing the receiver (normally an AT object)
  106. * @param jArguments the AT arguments to pass
  107. * @return the return value of the reflectively invoked method
  108. *
  109. * TODO: code duplication w.r.t. invokeSymbioticMethod => replace this method by calls to invokeSymbioticMethod?
  110. */
  111. public static ATObject invokeNativeATMethod(Method javaMethod, ATObject jReceiver, ATObject[] jArguments) throws InterpreterException {
  112. try {
  113. // if the native method takes an array as its sole parameter, it is interpreted as taking
  114. // a variable number of ambienttalk arguments
  115. Class[] params = javaMethod.getParameterTypes();
  116. Object[] args;
  117. if ((params.length == 1) && params[0].equals(ATObject[].class)) {
  118. args= new Object[] { jArguments };
  119. } else {
  120. if (params.length != jArguments.length) {
  121. throw new XArityMismatch("native method "+Reflection.downSelector(javaMethod.getName()), params.length, jArguments.length);
  122. }
  123. // make sure to properly 'coerce' each argument into the proper AT interface type
  124. args = coerceArguments(jArguments, params);
  125. }
  126. Object rval = javaMethod.invoke(jReceiver, args);
  127. if (rval instanceof ATObject) {
  128. return (ATObject) rval;
  129. } else {
  130. return primitiveJavaToATObject(rval);
  131. }
  132. } catch (IllegalAccessException e) {
  133. // the invoked method is not publicly accessible
  134. throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName()) + " not accessible.", e);
  135. } catch (IllegalArgumentException e) {
  136. // illegal argument types were supplied
  137. throw new XIllegalArgument("Illegal argument for native method "+Reflection.downSelector(javaMethod.getName()) + ": " + e.getMessage(), e);
  138. } catch (InvocationTargetException e) {
  139. // the invoked method threw an exception
  140. if (e.getTargetException() instanceof InterpreterException)
  141. throw (InterpreterException) e.getTargetException();
  142. else if (e.getTargetException() instanceof Signal) {
  143. throw (Signal) e.getTargetException();
  144. } else {
  145. e.printStackTrace();
  146. throw new XReflectionFailure("Native method "+Reflection.downSelector(javaMethod.getName())+" threw internal exception", e.getTargetException());
  147. }
  148. }
  149. }
  150. /**
  151. * Try to create a new instance of a Java class given an array of initialization arguments.
  152. * Because we do not have exact typing information, all of the public constructors of the
  153. * class are traversed until one is found that can create new instances given the current
  154. * initargs.
  155. */
  156. public static ATObject createNativeATObject(Class jClass, ATObject[] jInitArgs) throws InterpreterException {
  157. Constructor[] ctors = jClass.getConstructors();
  158. for (int i = 0; i < ctors.length; i++) {
  159. Constructor ctor = ctors[i];
  160. if (ctor.getParameterTypes().length == jInitArgs.length) {
  161. try {
  162. // make sure to properly 'coerce' each argument into the proper AT interface type
  163. Object[] coercedInitArgs = coerceArguments(jInitArgs, ctor.getParameterTypes());
  164. return (ATObject) ctor.newInstance(coercedInitArgs);
  165. } catch (IllegalArgumentException e) {
  166. continue; // argument types don't match, may find other constructor
  167. } catch (InstantiationException e) {
  168. break; // class is an abstract class, won't find a match
  169. } catch (IllegalAccessException e) {
  170. continue; // private or protected constructor, may find another one
  171. } catch (InvocationTargetException e) {
  172. // an exception was raised by the constructor
  173. if (e.getTargetException() instanceof InterpreterException)
  174. throw ((InterpreterException) e.getTargetException());
  175. else if (e.getTargetException() instanceof Signal) {
  176. throw (Signal) e.getTargetException();
  177. } else // fatal exception
  178. throw new XIllegalOperation("Instance creation of type " + jClass.getName() + " failed: " + e.getMessage());
  179. }
  180. } else {
  181. // arity does not match, try finding another one
  182. continue;
  183. }
  184. }
  185. // no matching constructors were found
  186. throw new XIllegalOperation("Unable to create a new instance of type " + jClass.getName());
  187. }
  188. public static Method getNativeATMethod(
  189. Class baseInterface,
  190. ATObject receiver,
  191. String methodName, ATSymbol atSelector) throws InterpreterException {
  192. Method[] applicable = getMethodsForSelector(baseInterface, methodName);
  193. switch (applicable.length) {
  194. case 0:
  195. throw new XSelectorNotFound(atSelector, receiver);
  196. case 1:
  197. return applicable[0];
  198. default:
  199. throw new XIllegalOperation("Native method uses overloading: " + atSelector + " in " + baseInterface);
  200. }
  201. }
  202. /**
  203. * Returns all public methods from the given class parameter whose name starts with the
  204. * given prefix. Moreover, the boolean parameter isStatic determines whether
  205. * to consider only static or only non-static methods.
  206. */
  207. public static Method[] allMethodsPrefixed(Class fromClass, String prefix, boolean isStatic) {
  208. // all public methods defined in the class
  209. Method[] allPublicMethods = (isStatic) ? fromClass.getDeclaredMethods() : fromClass.getMethods();
  210. Vector matchingMethods = new Vector(allPublicMethods.length);
  211. for (int i = 0; i < allPublicMethods.length; i++) {
  212. Method m = allPublicMethods[i];
  213. if (Modifier.isStatic(m.getModifiers()) == isStatic) {
  214. if (m.getName().startsWith(prefix)) {
  215. matchingMethods.add(m);
  216. }
  217. }
  218. }
  219. return (Method[]) matchingMethods.toArray(new Method[matchingMethods.size()]);
  220. }
  221. /**
  222. * Since Java uses strict matching when asked for a method, given an array of
  223. * classes, this often means that the types are overspecified and therefore no
  224. * matches can be found. As a consequence we have our own mechanism to select
  225. * which set of methods is applicable given a selector. Further dispatch needs
  226. * only to be performed when more than a single match exists.
  227. * @param jClass - the class from which the methods will be selected.
  228. * @param selector - the name of the requested method.
  229. * @return an array of applicable methods
  230. */
  231. private static Method[] getMethodsForSelector(Class jClass, String selector) {
  232. Method[] allMethods = jClass.getMethods();
  233. Vector matchingMethods = new Vector();
  234. int numMatchingMethods = 0;
  235. for (int i = 0; i < allMethods.length; i++) {
  236. if (allMethods[i].getName().equals(selector)) {
  237. matchingMethods.addElement(allMethods[i]);
  238. numMatchingMethods++;
  239. }
  240. }
  241. return (Method[])matchingMethods.toArray(new Method[numMatchingMethods]);
  242. }
  243. private static Object[] coerceArguments(ATObject[] args, Class[] types) throws XTypeMismatch {
  244. Object[] coercedArgs = new Object[args.length];
  245. for (int i = 0; i < args.length; i++) {
  246. coercedArgs[i] = Coercer.coerce(args[i], types[i]);
  247. }
  248. return coercedArgs;
  249. }
  250. public static final boolean isPrimitiveType(Class c) {
  251. return (c.isPrimitive() ||
  252. c == Integer.class ||
  253. c == Double.class ||
  254. c == Float.class ||
  255. c == Character.class ||
  256. c == Boolean.class ||
  257. c == Byte.class ||
  258. c == Long.class ||
  259. c == Short.class ||
  260. c == Void.class);
  261. }
  262. /**
  263. * Convert an primitive Java value to an AmbientTalk object
  264. * Supported mappings are:
  265. * int -> NATNumber
  266. * double -> NATFraction
  267. * char -> NATText
  268. * boolean -> NATBoolean
  269. * void -> NIL
  270. *
  271. * float, byte, long and short are left as JavaObjects.
  272. * They are not 'converted' into AmbientTalk primitives because of the opposite
  273. * reasons why e.g. numbers cannot be converted automatically to long, short, etc.
  274. * See the description of the 'atObjectToPrimitiveJava' method.
  275. *
  276. * Conversion from e.g. a java.lang.Long to a NATNumber is still possible by
  277. * using code such as:
  278. *
  279. * <code>aWrappedLongValue.intValue();</code>
  280. */
  281. public static final ATObject primitiveJavaToATObject(Object jObj) throws XReflectionFailure {
  282. // integer
  283. if (jObj instanceof Integer) {
  284. return NATNumber.atValue(((Integer) jObj).intValue());
  285. // double
  286. } else if (jObj instanceof Double) {
  287. return NATFraction.atValue(((Double) jObj).doubleValue());
  288. // char
  289. } else if (jObj instanceof Character) {
  290. return NATText.atValue(((Character) jObj).toString());
  291. // boolean
  292. } else if (jObj instanceof Boolean) {
  293. return NATBoolean.atValue(((Boolean) jObj).booleanValue());
  294. // float
  295. } else if (jObj instanceof Float) {
  296. return JavaObject.wrapperFor(jObj);
  297. //return NATFraction.atValue(((Float) jObj).floatValue());
  298. // byte
  299. } else if (jObj instanceof Byte) {
  300. return JavaObject.wrapperFor(jObj);
  301. //return NATNumber.atValue(((Byte) jObj).byteValue());
  302. // long
  303. } else if (jObj instanceof Long) {
  304. return JavaObject.wrapperFor(jObj);
  305. //return NATFraction.atValue(((Long) jObj).longValue());
  306. // short
  307. } else if (jObj instanceof Short) {
  308. return JavaObject.wrapperFor(jObj);
  309. //return NATNumber.atValue(((Short) jObj).shortValue());
  310. } else {
  311. throw new XReflectionFailure("Expected a primitive Java value, given: " + jObj);
  312. }
  313. }
  314. /**
  315. * Convert an ambienttalk object to a primitive type.
  316. * Supported mappings are:
  317. * NATNumber -> int
  318. * NATFraction -> double
  319. * NATText -> char
  320. * NATBoolean -> boolean
  321. * ATObject -> void
  322. *
  323. * Conversion to float, byte, long and short is not supported.
  324. * The reason for this is that otherwise, symbiotic invocations
  325. * will match with a lot of method signatures, and we would have to keep
  326. * track of the 'best fitting match'. E.g. given methods m(int) m(long) and m(short)
  327. * then invoking 'o.m(10)' in AmbientTalk would match all three methods.
  328. * By disabling conversions from NATNumber to long and short, only one match remains.
  329. * If conversion to any one of these primitive types is needed, use code such as:
  330. *
  331. * <code>jlobby.java.lang.Integer.new(10).longValue();</code>
  332. */
  333. public static final Object atObjectToPrimitiveJava(ATObject atObj, Class type) throws XTypeMismatch, XIllegalArgument {
  334. // integer
  335. if (type == int.class || type == Integer.class) {
  336. return new Integer(atObj.asNativeNumber().javaValue);
  337. // double
  338. } else if (type == double.class || type == Double.class) {
  339. return new Double(atObj.asNativeFraction().javaValue);
  340. // char
  341. } else if (type == char.class || type == Character.class) {
  342. return new Character(atObj.asNativeText().asChar());
  343. // boolean
  344. } else if (type == boolean.class || type == Boolean.class) {
  345. return new Boolean(atObj.asNativeBoolean().javaValue);
  346. // float
  347. } else if (type == float.class || type == Float.class) {
  348. // can only convert wrapped java.lang.Float
  349. if (atObj.isJavaObjectUnderSymbiosis()) {
  350. Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
  351. if (wrapped instanceof Float) {
  352. return wrapped;
  353. }
  354. }
  355. throw new XTypeMismatch(Float.class, atObj);
  356. //return Float.valueOf((float) atObj.asNativeFraction().javaValue);
  357. // byte
  358. } else if (type == byte.class || type == Byte.class) {
  359. // can only convert wrapped java.lang.Byte
  360. if (atObj.isJavaObjectUnderSymbiosis()) {
  361. Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
  362. if (wrapped instanceof Byte) {
  363. return wrapped;
  364. }
  365. }
  366. throw new XTypeMismatch(Byte.class, atObj);
  367. //return Byte.valueOf((byte) atObj.asNativeNumber().javaValue);
  368. // long
  369. } else if (type == long.class || type == Long.class) {
  370. // can only convert wrapped java.lang.Long
  371. if (atObj.isJavaObjectUnderSymbiosis()) {
  372. Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
  373. if (wrapped instanceof Long) {
  374. return wrapped;
  375. }
  376. }
  377. throw new XTypeMismatch(Long.class, atObj);
  378. //return Long.valueOf((long) atObj.asNativeFraction().javaValue);
  379. // short
  380. } else if (type == short.class || type == Short.class) {
  381. // can only convert wrapped java.lang.Short
  382. if (atObj.isJavaObjectUnderSymbiosis()) {
  383. Object wrapped = atObj.asJavaObjectUnderSymbiosis().getWrappedObject();
  384. if (wrapped instanceof Short) {
  385. return wrapped;
  386. }
  387. }
  388. throw new XTypeMismatch(Short.class, atObj);
  389. //return Short.valueOf((short) atObj.asNativeNumber().javaValue);
  390. } else if (type == void.class || type == Void.class) {
  391. return null;
  392. } else {
  393. throw new XIllegalArgument("Expected a primitive Java type, given: " + type);
  394. }
  395. }
  396. }