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