/interpreter/tags/at2-build270707/src/edu/vub/at/objects/symbiosis/Symbiosis.java
Java | 712 lines | 397 code | 32 blank | 283 comment | 135 complexity | e4710077032f3b190411ec82c94458bf MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * Symbiosis.java created on 5-nov-2006 at 19:22:26 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.symbiosis; 29 30import edu.vub.at.eval.Evaluator; 31import edu.vub.at.exceptions.InterpreterException; 32import edu.vub.at.exceptions.XArityMismatch; 33import edu.vub.at.exceptions.XIllegalArgument; 34import edu.vub.at.exceptions.XNotInstantiatable; 35import edu.vub.at.exceptions.XReflectionFailure; 36import edu.vub.at.exceptions.XSelectorNotFound; 37import edu.vub.at.exceptions.XSymbiosisFailure; 38import edu.vub.at.exceptions.XTypeMismatch; 39import edu.vub.at.exceptions.XUnassignableField; 40import edu.vub.at.exceptions.XUndefinedSlot; 41import edu.vub.at.exceptions.signals.Signal; 42import edu.vub.at.objects.ATObject; 43import edu.vub.at.objects.coercion.Coercer; 44import edu.vub.at.objects.mirrors.JavaInterfaceAdaptor; 45import edu.vub.at.objects.mirrors.Reflection; 46import edu.vub.at.objects.natives.NATException; 47import edu.vub.at.objects.natives.OBJNil; 48import edu.vub.at.objects.natives.NATTable; 49import edu.vub.at.objects.natives.NATText; 50 51import java.lang.reflect.Array; 52import java.lang.reflect.Constructor; 53import java.lang.reflect.Field; 54import java.lang.reflect.InvocationTargetException; 55import java.lang.reflect.Method; 56import java.lang.reflect.Modifier; 57import java.util.EventListener; 58import java.util.HashSet; 59import java.util.Hashtable; 60import java.util.Iterator; 61import java.util.LinkedList; 62import java.util.Vector; 63 64/** 65 * The Symbiosis class is a container for auxiliary methods pertaining to making symbiotic 66 * reflective Java invocations. 67 * 68 * @author tvcutsem 69 */ 70public final class Symbiosis { 71 72 /** 73 * Invoke a java method symbiotically, given only its name (not its implementation). 74 * First retrieves all of the methods matching the given selector in the given class, then tries 75 * to invoke the method symbiotically using the default symbiotic invocation algorithm. 76 * 77 * @see #symbioticInvocation(ATObject, Object, String, JavaMethod, ATObject[]) the symbiotic invocation algorithm. 78 */ 79 public static ATObject symbioticInvocation(ATObject wrapper, Object symbiont, Class ofClass, String selector, ATObject[] atArgs) throws InterpreterException { 80 return symbioticInvocation(wrapper, symbiont, selector, getMethods(ofClass, selector, (symbiont==null)), atArgs); 81 } 82 83 /** 84 * Invoke a java method symbiotically. 85 * The Java method invocation algorithm is as follows: 86 * <pre> 87 * case of # of methods matching selector: 88 * 0 => XSelectorNotFound 89 * 1 => invoke the method OR XIllegalArgument, XArityMismatch, XReflectionFailure 90 * * => (case of # of methods with matching arity OR taking varargs: 91 * 0 => XSymbiosisFailure 92 * 1 => invoke the method OR XIllegalArgument, XReflectionFailure 93 * * => (case of # of methods matching 'default type' of the actual arguments: 94 * 0 => XSymbiosisFailure 95 * 1 => invoke OR XReflectionFailure 96 * * => XSymbiosisFailure)) 97 * </pre> 98 * A Java method takes a variable number of AT arguments <=> it has one formal parameter of type ATObject[] 99 * 100 * @param wrapper the ATObject wrapper for the symbiont 101 * @param symbiont the Java object being accessed from within AmbientTalk 102 * @param selector the Java selector corresponding to the method invocation 103 * @param jMethod a JavaMethod encapsulating all applicable Java methods that correspond to the selector 104 * @param atArgs the AT args to the symbiotic invocation 105 * @return the wrapped result of the Java method invocation 106 * 107 * @throws XArityMismatch if the wrong number of arguments were supplied 108 * @throws XSelectorNotFound if no methods correspond to the given selector (i.e. jMethod is null) 109 * @throws XTypeMismatch if one of the arguments cannot be converted into the static type expected by the Java method 110 * @throws XSymbiosisFailure if the method is overloaded and cannot be unambiguously resolved given the actual arguments 111 * @throws XReflectionFailure if the invoked method is not accessible from within AmbientTalk 112 * @throws XJavaException if the invoked Java method throws a Java exception 113 */ 114 public static ATObject symbioticInvocation(ATObject wrapper, Object symbiont, String selector, JavaMethod jMethod, ATObject[] atArgs) 115 throws InterpreterException { 116 if (jMethod == null) { 117 // no methods found? selector does not exist... 118 throw new XSelectorNotFound(Reflection.downSelector(selector), wrapper); 119 } else { 120 Method[] methods = jMethod.choices_; 121 if (methods.length == 1) { 122 // just one method found, no need to resolve overloaded methods 123 // if the Java method takes an ATObject array as its sole parameter, it is interpreted as taking 124 // a variable number of ambienttalk arguments 125 Class[] params = methods[0].getParameterTypes(); 126 Object[] args; 127 if ((params.length == 1) && params[0].equals(ATObject[].class)) { 128 args = new Object[] { atArgs }; 129 } else { 130 if (params.length != atArgs.length) { 131 throw new XArityMismatch("Java method "+Reflection.downSelector(methods[0].getName()), params.length, atArgs.length); 132 } 133 // make sure to properly 'coerce' each argument into the proper AT interface type 134 args = atArgsToJavaArgs(atArgs, params); 135 } 136 return invokeUniqueSymbioticMethod(symbiont, methods[0], args); 137 } else { 138 // overloading: filter out all methods that do not match arity or whose 139 // argument types do not match 140 Object[] actuals = null; 141 Class[] params; 142 LinkedList matchingMethods = new LinkedList(); 143 // this boolean keeps track of whether or not failure to resolve the overloaded 144 // method is solely because of an arity mismatch, not because of a type mismatch 145 146 boolean failedDueToArityOnly = true; 147 148 for (int i = 0; i < methods.length; i++) { 149 params = methods[i].getParameterTypes(); 150 // is the method a varargs method? 151 if ((params.length == 1) && params[0].equals(ATObject[].class)) { 152 actuals = new Object[] { atArgs }; 153 matchingMethods.addFirst(methods[i]); 154 // does the arity match? 155 } else if (params.length == atArgs.length) { 156 // can it be invoked with the given actuals? 157 try { 158 actuals = atArgsToJavaArgs(atArgs, params); 159 matchingMethods.addFirst(methods[i]); 160 } catch(XTypeMismatch e) { 161 // types don't match 162 failedDueToArityOnly = false; 163 } 164 } else { 165 // arity does not match 166 } 167 } 168 169 switch (matchingMethods.size()) { 170 case 0: { 171 // no methods left: overloading resolution failed... 172 if (failedDueToArityOnly) { 173 // ... because of an arity mismatch 174 throw new XSymbiosisFailure(methods[0], atArgs.length); 175 } else { 176 // ... because the types could not be resolved 177 throw new XSymbiosisFailure(symbiont, methods[0], atArgs); 178 } 179 180 } 181 case 1: { 182 // just one method left, invoke it 183 return invokeUniqueSymbioticMethod(symbiont, (Method) matchingMethods.getFirst(), actuals); 184 } 185 default: { 186 // more than one method left: overloading resolution failed 187 throw new XSymbiosisFailure(symbiont, selector, matchingMethods, atArgs); 188 } 189 } 190 } 191 } 192 } 193 194 /** 195 * Creates a new instance of a Java class. 196 * 197 * @param ofClass the Java class of which to create an instance 198 * @param atArgs the AmbientTalk arguments to the constructor, to be converted to Java arguments 199 * @return an unitialized JavaObject wrapper around a newly created instance of the class 200 * 201 * @throws XArityMismatch if the wrong number of arguments were supplied 202 * @throws XNotInstantiatable if no public constructors are available or if the class is abstract 203 * @throws XTypeMismatch if one of the arguments cannot be converted into the static type expected by the constructor 204 * @throws XSymbiosisFailure if the constructor is overloaded and cannot be unambiguously resolved given the actual arguments 205 * @throws XReflectionFailure if the invoked constructor is not accessible from within AmbientTalk 206 * @throws XJavaException if the invoked Java constructor throws a Java exception 207 */ 208 public static ATObject symbioticInstanceCreation(Class ofClass, ATObject[] atArgs) throws InterpreterException { 209 Constructor[] ctors = ofClass.getConstructors(); 210 switch (ctors.length) { 211 // no constructors found? class is not instantiatable... 212 case 0: 213 throw new XNotInstantiatable(ofClass); 214 // just one constructor found, no need to resolve overloaded methods 215 case 1: { 216 // if the constructor takes an ATObject array as its sole parameter, it is interpreted as taking 217 // a variable number of ambienttalk arguments 218 Class[] params = ctors[0].getParameterTypes(); 219 Object[] args; 220 if ((params.length == 1) && params[0].equals(ATObject[].class)) { 221 args = new Object[] { atArgs }; 222 } else { 223 if (params.length != atArgs.length) { 224 throw new XArityMismatch("Java constructor "+Reflection.downSelector(ctors[0].getName()), params.length, atArgs.length); 225 } 226 // make sure to properly convert actual arguments into Java objects 227 args = atArgsToJavaArgs(atArgs, params); 228 } 229 return invokeUniqueSymbioticConstructor(ctors[0], args); 230 } 231 } 232 233 // overloading: filter out all constructors that do not match arity or whose argument types do not match 234 int matchingCtors = 0; 235 Constructor matchingCtor = null; 236 Object[] actuals = null; 237 Class[] params; 238 for (int i = 0; i < ctors.length; i++) { 239 params = ctors[i].getParameterTypes(); 240 // is the constructor a varargs constructor? 241 if ((params.length == 1) && params[0].equals(ATObject[].class)) { 242 actuals = new Object[] { atArgs }; 243 matchingCtor = ctors[i]; 244 matchingCtors++; 245 // does the arity match? 246 } else if (params.length == atArgs.length) { 247 // can it be invoked with the given actuals? 248 try { 249 actuals = atArgsToJavaArgs(atArgs, params); 250 matchingCtor = ctors[i]; 251 matchingCtors++; 252 } catch(XTypeMismatch e) { 253 // types don't match 254 ctors[i] = null; // TODO: don't assign to null, array may be cached or used later on (or by wrapper method) 255 } 256 } else { 257 // arity does not match 258 ctors[i] = null; 259 } 260 } 261 262 if (matchingCtors != 1) { 263 // no constructors left or more than one constructor left? overloading resolution failed 264 throw new XSymbiosisFailure(ofClass, ctors, atArgs, matchingCtors); 265 } else { 266 // just one constructor left, invoke it 267 return invokeUniqueSymbioticConstructor(matchingCtor, actuals); 268 } 269 } 270 271 /** 272 * Read a field from the given Java object reflectively. 273 * @return the contents of the Java field, converted into its AmbientTalk equivalent 274 */ 275 public static ATObject readField(Object fromObject, Class ofClass, String fieldName) 276 throws InterpreterException { 277 Field f = getField(ofClass, fieldName, (fromObject == null)); 278 return readField(fromObject, f); 279 } 280 281 /** 282 * Read a field from the given Java object reflectively. 283 * @return the contents of the Java field, converted into its AmbientTalk equivalent 284 */ 285 public static ATObject readField(Object fromObject, Field f) throws InterpreterException { 286 try { 287 return Symbiosis.javaToAmbientTalk(f.get(fromObject)); 288 } catch (IllegalArgumentException e) { 289 // the given object is of the wrong class, should not happen! 290 throw new XReflectionFailure("Illegal class for field access of "+f.getName() + ": " + e.getMessage()); 291 } catch (IllegalAccessException e) { 292 // the read field is not publicly accessible 293 throw new XReflectionFailure("field access of " + f.getName() + " not accessible."); 294 } 295 } 296 297 /** 298 * Write a field in the given Java object reflectively. 299 * @param toObject if null, the field is assumed to be static 300 * @param value the AmbientTalk value which will be converted into its Java equivalent to be written int he field 301 */ 302 public static void writeField(Object toObject, Class ofClass, String fieldName, ATObject value) 303 throws InterpreterException { 304 Field f = getField(ofClass, fieldName, (toObject == null)); 305 writeField(toObject, f, value); 306 } 307 308 /** 309 * Write a field in the given Java object reflectively. 310 * @param value the AmbientTalk value which will be converted into its Java equivalent to be written int he field 311 */ 312 public static void writeField(Object toObject, Field f, ATObject value) throws InterpreterException { 313 try { 314 f.set(toObject, Symbiosis.ambientTalkToJava(value, f.getType())); 315 } catch (IllegalArgumentException e) { 316 // the given value is of the wrong type 317 throw new XIllegalArgument("Illegal value for field "+f.getName() + ": " + e.getMessage()); 318 } catch (IllegalAccessException e) { 319 // the read field is not publicly accessible or final 320 throw new XUnassignableField(Reflection.downSelector(f.getName()).toString()); 321 } 322 } 323 324 /** 325 * Query whether the given Java Class contains a (non-)static method with the given selector 326 */ 327 public static boolean hasMethod(Class c, String selector, boolean isStatic) { 328 Method[] methods = c.getMethods(); 329 for (int i = 0; i < methods.length; i++) { 330 if (Modifier.isStatic(methods[i].getModifiers()) == isStatic) { 331 if (methods[i].getName().equals(selector)) { 332 return true; 333 } 334 } 335 } 336 return false; 337 } 338 339 /** 340 * Query whether the given Java Class contains a (non-)static field with the given selector 341 */ 342 public static boolean hasField(Class c, String selector, boolean isStatic) { 343 try { 344 Field f = c.getField(selector); 345 return (Modifier.isStatic(f.getModifiers()) == isStatic); 346 } catch (NoSuchFieldException e) { 347 return false; 348 } 349 } 350 351 /** 352 * Retrieve a field from a Java object. 353 * @throws XUndefinedSlot if the field does not exist or its static property does not match 354 */ 355 public static Field getField(Class fromClass, String fieldName, boolean isStatic) throws XUndefinedSlot { 356 try { 357 Field f = fromClass.getField(fieldName); 358 if ((Modifier.isStatic(f.getModifiers())) == isStatic) { 359 return f; 360 } else { 361 throw new XUndefinedSlot("field access ", fieldName + " not accessible."); 362 } 363 } catch (NoSuchFieldException e) { 364 // the field does not exist 365 throw new XUndefinedSlot("field access ", fieldName + " not accessible."); 366 } 367 } 368 369 /** 370 * Retrieve all methods of a given name from a Java object. These are bundled together 371 * in a first-class JavaMethod object, which is cached for later reference. 372 * 373 * A null return value indicates no matches. 374 */ 375 public static JavaMethod getMethods(Class fromClass, String selector, boolean isStatic) { 376 // first, check the method cache 377 JavaMethod cachedEntry = JMethodCache._INSTANCE_.get(fromClass, selector, isStatic); 378 if (cachedEntry != null) { 379 // cache hit 380 return cachedEntry; 381 } else { 382 // cache miss: assemble a new JavaMethod entry 383 Method[] methods = fromClass.getMethods(); 384 Method m; 385 Vector properMethods = new Vector(methods.length); 386 for (int i = 0; i < methods.length; i++) { 387 m = methods[i]; 388 if ((Modifier.isStatic(m.getModifiers())) == isStatic && m.getName().equals(selector)) { 389 properMethods.add(methods[i]); 390 } 391 } 392 Method[] choices = (Method[]) properMethods.toArray(new Method[properMethods.size()]); 393 if (choices.length == 0) { 394 // no matches 395 return null; 396 } else { 397 // add entry to cache and return it 398 JavaMethod jMethod = new JavaMethod(choices); 399 JMethodCache._INSTANCE_.put(fromClass, selector, isStatic, jMethod); 400 return jMethod; 401 } 402 } 403 } 404 405 /** 406 * Retrieve all public static or non-static methods from a given Java class 407 * (this includes methods defined in superclasses). All methods are properly wrapped in a 408 * JavaMethod wrapper, taking care to wrap a set of overloaded methods using the same wrapper. 409 * 410 * @param isStatic if true, all static methods of fromClass are returned, otherwise the instance methods are returned 411 */ 412 public static JavaMethod[] getAllMethods(Class fromClass, boolean isStatic) { 413 // assemble a set of all unique selectors of all (non-)static methods of the class 414 HashSet uniqueNames = new HashSet(); 415 Method[] methods = fromClass.getMethods(); 416 for (int i = 0; i < methods.length; i++) { 417 Method m = methods[i]; 418 if ((Modifier.isStatic(m.getModifiers())) == isStatic) { 419 uniqueNames.add(m.getName()); 420 } 421 } 422 423 // create a JavaMethod[] array large enough to contain all 'unique methods' 424 JavaMethod[] jmethods = new JavaMethod[uniqueNames.size()]; 425 // loop over all entries and group the methods into a single wrapper 426 int i = 0; 427 for (Iterator iter = uniqueNames.iterator(); iter.hasNext();) { 428 String methodName = (String) iter.next(); 429 jmethods[i++] = getMethods(fromClass, methodName, isStatic); 430 } 431 return jmethods; 432 } 433 434 /** 435 * Retrieve all public static or non-static fields from a given Java class 436 * (this includes fields defined in superclasses, but excludes shadowed superclass fields). All fields are properly wrapped in a 437 * JavaField wrapper. 438 * 439 * @param ofObject if null, all static fields of fromClass are returned, otherwise the instance fields are returned 440 */ 441 public static JavaField[] getAllFields(Object ofObject, Class fromClass) { 442 boolean isStatic = (ofObject == null); 443 Field[] fields = fromClass.getFields(); 444 // we do not consider shadowed superclass fields, therefore we store all encountered fields 445 // in a table and only keep the field with the most specific class 446 Hashtable recordedFields = new Hashtable(); 447 for (int i = 0; i < fields.length; i++) { 448 Field f = fields[i]; 449 if ((Modifier.isStatic(f.getModifiers())) == isStatic) { 450 // did we already encounter this field? 451 if (recordedFields.contains(f.getName())) { 452 // yes, then compare encountered field with previous field and only store most specific one 453 Field prev = (Field) recordedFields.get(f.getName()); 454 // is f's Class a subclass of prev's Class? 455 if (prev.getDeclaringClass().isAssignableFrom(f.getDeclaringClass())) { 456 // yes, so f is more specific, store it instead of prev 457 recordedFields.remove(prev.getName()); 458 recordedFields.put(f.getName(), f); 459 } // if not, keep previous field 460 } else { 461 // field not encountered yet, store it 462 recordedFields.put(f.getName(), f); 463 } 464 } 465 } 466 // create a JavaField[] array large enough to contain all entries in the table 467 JavaField[] jfields = new JavaField[recordedFields.size()]; 468 // loop over all entries in the table and wrap each field 469 int i = 0; 470 for (Iterator iter = recordedFields.values().iterator(); iter.hasNext(); i++) { 471 jfields[i] = new JavaField(ofObject, (Field) iter.next()); 472 } 473 return jfields; 474 } 475 476 /** 477 * Convert a Java object into an AmbientTalk object, according to 478 * the following rules: 479 * <pre> 480 * null = nil 481 * ATObject obj = obj 482 * int n = Number(n) 483 * double d = Fraction(d) 484 * boolean b = Boolean(b) 485 * String s = Text(s) 486 * T[] array = Table(array.length) 487 * InterpreterException e = NATException(e) 488 * Exception e = NATException(XJavaException(e)) 489 * SymbioticATObj o = o.wrappedATObject 490 * Class c = JavaClass(c) 491 * Object o = JavaObject(o) 492 * </pre> 493 * 494 * @param jObj the Java object representing a mirror or a native type 495 * @return the same object if it implements the ATObject interface 496 */ 497 public static final ATObject javaToAmbientTalk(Object jObj) throws InterpreterException { 498 // -- NULL => NIL -- 499 if (jObj == null) { 500 return OBJNil._INSTANCE_; 501 // -- AmbientTalk implementation-level objects -- 502 } else if (jObj instanceof ATObject) { 503 return (ATObject) jObj; 504 // -- PRIMITIVE TYPE => NUMERIC, TXT -- 505 } else if (JavaInterfaceAdaptor.isPrimitiveType(jObj.getClass())) { 506 return JavaInterfaceAdaptor.primitiveJavaToATObject(jObj); 507 // -- STRING => TEXT -- 508 } else if (jObj instanceof String) { 509 return NATText.atValue((String) jObj); 510 // -- ARRAY => TABLE -- 511 } else if (jObj.getClass().isArray()) { 512 int length = Array.getLength(jObj); 513 ATObject[] atTable = new ATObject[length]; 514 for (int i = 0; i < length; i++) { 515 atTable[i] = javaToAmbientTalk(Array.get(jObj, i)); 516 } 517 return NATTable.atValue(atTable); 518 // -- EXCEPTION => NATEXCEPTION -- 519 } else if(jObj instanceof InterpreterException) { 520 return ((InterpreterException)jObj).getAmbientTalkRepresentation(); 521 } else if (jObj instanceof Exception) { 522 return new NATException(new XJavaException((Exception) jObj)); 523 // -- Symbiotic AmbientTalk object => AmbientTalk object -- 524 } else if (jObj instanceof SymbioticATObjectMarker) { 525 return ((SymbioticATObjectMarker) jObj)._returnNativeAmbientTalkObject(); 526 // -- java.lang.Class => Symbiotic Class -- 527 } else if (jObj instanceof Class) { 528 return JavaClass.wrapperFor((Class) jObj); 529 // -- Object => Symbiotic AT Object -- 530 } else { 531 return JavaObject.wrapperFor(jObj); 532 } 533 } 534 535 /** 536 * Convert an AmbientTalk object into an equivalent Java object, according 537 * to the following rules: 538 * <pre> 539 * Number n -> int = n.javaValue 540 * Fraction f -> double = f.javaValue 541 * Boolean b -> boolean = b.javaValue 542 * Text t -> String = t.javaValue 543 * JavaObject jobj -> T = (T) jobj.wrappedObject 544 * ATObject obj -> ATObject = obj 545 * Table obj -> T[] = new T[obj.length] 546 * NATException exc -> Exception = exc.wrappedException 547 * JavaClass jcls -> Class = jcls.wrappedClass 548 * nil -> Object = null 549 * ATObject obj -> Interface = Coercer<obj,Interface> 550 * </pre> 551 * @param atObj the AmbientTalk object to convert to a Java value 552 * @param targetType the known static type of the Java object that should be attained 553 * @return a Java object o where (o instanceof targetType) should yield true 554 * 555 * @throws XTypeMismatch if the object cannot be converted into the correct Java targetType 556 */ 557 public static final Object ambientTalkToJava(ATObject atObj, Class targetType) throws InterpreterException { 558 // -- PRIMITIVE TYPES -- 559 if (JavaInterfaceAdaptor.isPrimitiveType(targetType)) { 560 return JavaInterfaceAdaptor.atObjectToPrimitiveJava(atObj, targetType); 561 // -- WRAPPED JAVA OBJECTS -- 562 } else if (atObj.isJavaObjectUnderSymbiosis()) { 563 Object jObj = atObj.asJavaObjectUnderSymbiosis().getWrappedObject(); 564 Class jCls = jObj.getClass(); 565 // dynamic subtype test: is jCls a subclass of targetType? 566 if (targetType.isAssignableFrom(jCls)) { 567 return jObj; 568 } 569 } 570 571 // -- IMPLEMENTATION-LEVEL OBJECTS -- 572 if (targetType.isInstance(atObj)) { 573 // target type is a subtype of ATObject, return the implementation-level object itself 574 return atObj; 575 // -- STRINGS -- 576 } else if (targetType == String.class) { 577 return atObj.asNativeText().javaValue; 578 // -- ARRAYS -- 579 } else if (targetType.isArray()) { 580 ATObject[] atArray = atObj.asNativeTable().elements_; 581 Object jArray = Array.newInstance(targetType.getComponentType(), atArray.length); 582 for (int i = 0; i < Array.getLength(jArray); i++) { 583 Array.set(jArray, i, ambientTalkToJava(atArray[i], targetType.getComponentType())); 584 } 585 return jArray; 586 // -- EXCEPTIONS -- 587 } else if (Exception.class.isAssignableFrom(targetType)) { 588 return Evaluator.asNativeException(atObj); 589 // -- CLASS OBJECTS -- 590 } else if (targetType == Class.class) { 591 return atObj.asJavaClassUnderSymbiosis().getWrappedClass(); 592 // -- nil => NULL -- 593 } else if (atObj == OBJNil._INSTANCE_) { 594 return null; 595 // -- INTERFACE TYPES AND NAT CLASSES -- 596 } else { 597 return Coercer.coerce(atObj, targetType); 598 } 599 } 600 601 /** 602 * Returns whether the symbiosis layer should process the given method purely 603 * asynchronously or not. 604 * 605 * @return whether the specified Java method denotes an event notification 606 */ 607 public static boolean isEvent(Method method) { 608 return EventListener.class.isAssignableFrom(method.getDeclaringClass()) // is an EventListener 609 && (method.getReturnType() == Void.TYPE) // does not return a value 610 && (method.getExceptionTypes().length == 0); // throws no exceptions 611 } 612 613 private static ATObject invokeUniqueSymbioticMethod(Object symbiont, Method javaMethod, Object[] jArgs) throws InterpreterException { 614 try { 615 return Symbiosis.javaToAmbientTalk(javaMethod.invoke(symbiont, jArgs)); 616 } catch (IllegalAccessException e) { 617 // the invoked method is not publicly accessible 618 // sometimes this may happen when accessing inner classes, try again with an interface method: 619 Method interfaceMethod = toInterfaceMethod(javaMethod); 620 if (interfaceMethod == null) { // no success 621 // try to perform the call without access protection 622 if (!javaMethod.isAccessible()) { 623 javaMethod.setAccessible(true); 624 return invokeUniqueSymbioticMethod(symbiont, javaMethod, jArgs); 625 } else { 626 // if access protection was already disabled, bail out 627 throw new XReflectionFailure("Java method "+Reflection.downSelector(javaMethod.getName()) + " is not accessible.", e); 628 } 629 } else { 630 return invokeUniqueSymbioticMethod(symbiont, interfaceMethod, jArgs); 631 } 632 } catch (IllegalArgumentException e) { 633 // illegal argument types were supplied, should not happen because the conversion should have already failed earlier (in atArgsToJavaArgs) 634 // Backport from JDK 1.4 to 1.3 635 // throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java method "+javaMethod.getName(), e); 636 throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java method "+javaMethod.getName()); 637 } catch (InvocationTargetException e) { 638 // the invoked method threw an exception 639 if (e.getTargetException() instanceof InterpreterException) 640 throw (InterpreterException) e.getTargetException(); 641 else if (e.getTargetException() instanceof Signal) { 642 throw (Signal) e.getTargetException(); 643 } else { 644 throw new XJavaException(symbiont, javaMethod, e.getTargetException()); 645 } 646 } 647 } 648 649 private static ATObject invokeUniqueSymbioticConstructor(Constructor ctor, Object[] jArgs) throws InterpreterException { 650 try { 651 return Symbiosis.javaToAmbientTalk(ctor.newInstance(jArgs)); 652 } catch (IllegalAccessException e) { 653 // the invoked method is not publicly accessible 654 throw new XReflectionFailure("Java constructor "+Reflection.downSelector(ctor.getName()) + " is not accessible.", e); 655 } catch (IllegalArgumentException e) { 656 // illegal argument types were supplied, should not happen because the conversion should have already failed earlier (in atArgsToJavaArgs) 657 // Backport from JDK 1.4 to 1.3 658 // throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java constructor "+ctor.getName(), e); 659 throw new RuntimeException("[broken at2java conversion?] Illegal argument for Java constructor "+ctor.getName()); 660 } catch (InstantiationException e) { 661 // the given class is abstract 662 throw new XNotInstantiatable(ctor.getDeclaringClass(), e); 663 } catch (InvocationTargetException e) { 664 // the invoked method threw an exception 665 if (e.getTargetException() instanceof InterpreterException) 666 throw (InterpreterException) e.getTargetException(); 667 else if (e.getTargetException() instanceof Signal) { 668 throw (Signal) e.getTargetException(); 669 } else { 670 throw new XJavaException(null, ctor, e.getTargetException()); 671 } 672 } 673 } 674 675 private static Object[] atArgsToJavaArgs(ATObject[] args, Class[] types) throws InterpreterException { 676 Object[] jArgs = new Object[args.length]; 677 for (int i = 0; i < args.length; i++) { 678 jArgs[i] = Symbiosis.ambientTalkToJava(args[i], types[i]); 679 } 680 return jArgs; 681 } 682 683 /** 684 * Extremely vague and dirty feature of Java reflection: it can sometimes happen that 685 * a method is invoked on a private inner class via a publicly accessible interface method. 686 * In those cases, invoking that method results in an IllegalAccessException. 687 * One example is invoking aVector.iterator().hasNext() 688 * 689 * The problem is that aVector.iterator() returns an instance of java.util.AbstractList$Itr 690 * which is probably private. Selecting that class's hasNext method and invoking it results in 691 * an IllegalAccessException. This can be circumvented by invoking the hasNext method through 692 * the java.util.Iterator interface class. 693 */ 694 private static Method toInterfaceMethod(Method m) { 695 Class[] interfaces = m.getDeclaringClass().getInterfaces(); 696 if (interfaces == null) { 697 return null; 698 } else { 699 // find the method in one of the interface declarations 700 for (int i = 0; i < interfaces.length; i++) { 701 try { 702 return interfaces[i].getMethod(m.getName(), m.getParameterTypes()); 703 } catch(NoSuchMethodException e) { 704 // continue searching 705 } 706 } 707 // no declared method found 708 return null; 709 } 710 } 711 712}