/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/objects/mirrors/JavaInterfaceAdaptor.java

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