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