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

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