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