/interpreter/tags/at2dist110511/src/edu/vub/at/objects/mirrors/Reflection.java
Java | 491 lines | 224 code | 29 blank | 238 comment | 41 complexity | a1bdd457c19945e8c2f3a62ce7e4c66f MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * Reflection.java created on 10-aug-2006 at 16:19:17 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.XIllegalArgument; 32import edu.vub.at.objects.ATMethod; 33import edu.vub.at.objects.ATObject; 34import edu.vub.at.objects.ATTable; 35import edu.vub.at.objects.grammar.ATSymbol; 36import edu.vub.at.objects.natives.NATTable; 37import edu.vub.at.objects.natives.OBJLexicalRoot; 38import edu.vub.at.objects.natives.grammar.AGSymbol; 39import edu.vub.at.objects.symbiosis.JavaConstructor; 40import edu.vub.at.objects.symbiosis.Symbiosis; 41import edu.vub.at.util.logging.Logging; 42import edu.vub.util.Regexp; 43 44import java.lang.reflect.Method; 45import java.util.Vector; 46 47import org.apache.regexp.RE; 48import org.apache.regexp.REProgram; 49 50/** 51 * Reflection is an auxiliary class meant to serve as a repository for methods 52 * related to 'up' and 'down' Java values properly into and out of the AmbientTalk base level. 53 * 54 * Keep the following mental picture in mind: 55 * 56 * ^ Java = implementation-level | 57 * to deify | Java AT objects = meta-level | to reify 58 * (= to up) | ------------------------------------------ | (= to down) 59 * (= to absorb) | AmbientTalk = base-level v (= to reflect) 60 * 61 * Although deification and reification are more accurate terms, we will use 'up' and 'down' 62 * because they are the clearest terminology, and clarity matters. 63 * 64 * In this class, the following conventions hold: 65 * - methods start with either 'up' or 'down', denoting whether they deify or reify something 66 * - arguments start with either 'j' or 'at', denoting whether they represent Java or AmbientTalk values 67 * With 'java values' is meant 'java objects representing mirrors' 68 * 69 * @author tvc 70 */ 71public final class Reflection { 72 73 private static final String _BASE_PREFIX_ = "base_"; 74 private static final String _META_PREFIX_ = "meta_"; 75 76 /** 77 * A selector passed from the Java to the AmbientTalk level undergoes the following transformations: 78 * 79 * - any pattern of the form _op{code}_ is transformed to a symbol corresponding to the operator code 80 * Operator codes are: 81 * pls -> + 82 * mns -> - 83 * tms -> * 84 * div -> / 85 * bsl -> \ 86 * not -> ! 87 * gtx -> > 88 * ltx -> < 89 * eql -> = 90 * til -> ~ 91 * que -> ? 92 * rem -> % 93 * - any underscores (_) are replaced by colons (:) 94 */ 95 public static final ATSymbol downSelector(String jSelector) { 96 return AGSymbol.jAlloc(javaToAmbientTalkSelector(jSelector)); 97 } 98 99 /** 100 * Transforms a Java selector prefixed with base_ into an AmbientTalk selector without the prefix. 101 */ 102 public static final ATSymbol downBaseLevelSelector(String jSelector) throws InterpreterException { 103 if (jSelector.startsWith(Reflection._BASE_PREFIX_)) { 104 return downSelector(stripPrefix(jSelector, Reflection._BASE_PREFIX_)); 105 } else if (jSelector.startsWith(Reflection._META_PREFIX_)) { 106 return downSelector(stripPrefix(jSelector, Reflection._META_PREFIX_)); 107 } else { 108 throw new XIllegalArgument("Illegal base level selector to down: " + jSelector); 109 } 110 } 111 112 /** 113 * Transforms a Java selector prefixed with meta_ into an AmbientTalk selector without the prefix. 114 */ 115 public static final ATSymbol downMetaLevelSelector(String jSelector) throws InterpreterException { 116 if (jSelector.startsWith(Reflection._META_PREFIX_)) { 117 return downSelector(stripPrefix(jSelector, Reflection._META_PREFIX_)); 118 } else { 119 throw new XIllegalArgument("Illegal meta level selector to down: " + jSelector); 120 } 121 } 122 123 /** 124 * A selector passed from the AmbientTalk to the Java level undergoes the following transformations: 125 * 126 * - any colons (:) are replaced by underscores (_) 127 * - any operator symbol is replaced by _op{code}_ where code is generated as follows: 128 * Operator codes are: 129 * + -> pls 130 * - -> mns 131 * * -> tms 132 * / -> div 133 * \ -> bsl 134 * ! -> not 135 * > -> gtx 136 * < -> ltx 137 * = -> eql 138 * ~ -> til 139 * ? -> que 140 * % -> rem 141 */ 142 public static final String upSelector(ATSymbol atSelector) throws InterpreterException { 143 // : -> _ 144 String nam = new RE(colon).subst(atSelector.base_text().asNativeText().javaValue, "_"); 145 146 // operator symbol -> _op{code}_ 147 // find every occurence of a non-word character and convert it into a symbol 148 return Regexp.replaceAll(new RE(symbol), nam, new Regexp.StringCallable() { 149 public String call(String match) { 150 String oprCode = symbol2oprCode(match); 151 // only add the _op prefix and _ postfix if the code has been found... 152 return (oprCode.length() == 3) ? "_op" + oprCode + "_" : oprCode; 153 } 154 }); 155 } 156 157 /** 158 * Transforms an AmbientTalk selector into a Java-level selector prefixed with base_. 159 */ 160 public static final String upBaseLevelSelector(ATSymbol atSelector) throws InterpreterException { 161 return Reflection._BASE_PREFIX_ + upSelector(atSelector); 162 } 163 164 /** 165 * Transforms an AmbientTalk selector into a Java-level selector prefixed with meta_. 166 */ 167 public static final String upMetaLevelSelector(ATSymbol atSelector) throws InterpreterException { 168 return Reflection._META_PREFIX_ + upSelector(atSelector); 169 } 170 171 /** 172 * Constructs an AmbientTalk ATMethod from a Java method. 173 * Given an object obj and a String sel, it is checked whether obj has a method 174 * named sel. If so, the corresponding Java method is wrapped in a NativeMethod. 175 * If not, the downing fails. 176 * 177 * @param natObject the native AmbientTalk object in whose class the method should be found 178 * @param jSelector a selector which should yield a method in natObject 179 * @param origName the original AmbientTalk name of the method 180 * @return a reified method wrapping the Java method 181 * 182 * Example: 183 * eval "(reflect: tbl).getMethod('at')" where tbl is a NATTable 184 * => downMethod(aNATTable, "base_at") 185 * => NATTable must have a method named base_at 186 * 187 * Callers should use the more specialised 'downBaseLevelMethod' and 'downMetaLevelMethod' 188 * methods to specify the prefix of the method to be found 189 */ 190 public static final ATMethod downMethod(ATObject natObject, String jSelector, ATSymbol origName) throws InterpreterException { 191 return new NativeMethod(JavaInterfaceAdaptor.getNativeATMethod(natObject.getClass(), natObject, jSelector, origName), 192 origName, 193 natObject); 194 } 195 196 public static final ATMethod downBaseLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException { 197 return downMethod(natObject, upBaseLevelSelector(atSelector), atSelector); 198 } 199 200 public static final ATMethod downMetaLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException { 201 return downMethod(natObject, upMetaLevelSelector(atSelector), atSelector); 202 } 203 204 /** 205 * downInvocation takes an implicit Java invocation and turns it into an explicit 206 * AmbientTalk invocation process. This happens when Java code sends normal 207 * Java messages to AmbientTalk objects (wrapped by a mirage). 208 * 209 * @param atRcvr the AmbientTalk object having received the Java method invocation 210 * @param jSelector the Java selector, to be converted to an AmbientTalk selector 211 * @param jArgs the arguments to the Java method invocation (normally all args are ATObjects) 212 * jArgs may be null, indicating that there are no arguments 213 * @return the return value of the AmbientTalk method invoked via the java invocation. 214 * 215 * Example: 216 * in Java: "tbl.base_at(1)" where tbl is an ATTable coercer wrapping aNATObject 217 * => downInvocation(aNATObject, "base_at", ATObject[] { ATNumber(1) }) 218 * => aNATObject must implement a method named "at" 219 * 220 * Depending on the prefix of the invoked Java method selector, the following translation should occur: 221 * - obj.base_selector(args) => obj.meta_invoke(obj, selector, args) 222 * - obj.base_selector() => obj.meta_invokeField(obj, selector) 223 * - obj.meta_selector(args) => either obj.meta_selector(args) if selector is understood natively 224 * or (reflect: obj).meta_selector(args) otherwise 225 * - obj.selector(args) => either obj.selector(args) if selector is understood natively 226 * or obj.meta_invoke(obj, selector, args) otherwise 227 * - obj.selector() => obj.meta_invokeField(obj, selector) 228 */ 229 public static final ATObject downInvocation(ATObject atRcvr, Method jMethod, ATObject[] jArgs) throws InterpreterException { 230 String jSelector = jMethod.getName(); 231 if (jArgs == null) { jArgs = NATTable.EMPTY.elements_; } 232 233 if (jSelector.startsWith(Reflection._BASE_PREFIX_)) { 234 if (jArgs.length == 0) { 235 // obj.base_selector() => obj.meta_invokeField(obj, selector) 236 return atRcvr.meta_invokeField(atRcvr, downBaseLevelSelector(jSelector)); 237 } else { 238 // obj.base_selector(args) => obj.meta_invoke(obj, selector, args) 239 return atRcvr.impl_invoke(atRcvr, downBaseLevelSelector(jSelector), NATTable.atValue(jArgs)); 240 } 241 } else if (jSelector.startsWith(Reflection._META_PREFIX_)) { 242 if (jMethod.getDeclaringClass().isInstance(atRcvr)) { 243 // obj.meta_selector(args) => obj.meta_selector(args) 244 return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs); 245 } else { 246 ATObject mirror = OBJLexicalRoot._INSTANCE_.base_reflect_(atRcvr); 247 if (jArgs.length == 0) { 248 // obj.selector() => (reflect: obj).meta_invokeField(obj, selector) 249 return mirror.meta_invokeField(mirror, downMetaLevelSelector(jSelector)); 250 } else { 251 // obj.selector(args) => (reflect: obj).meta_invoke(obj, selector, args) 252 return mirror.impl_invoke(mirror, downMetaLevelSelector(jSelector), NATTable.atValue(jArgs)); 253 } 254 } 255 } else { 256 // atRcvr can respond to the given method natively 257 if (jMethod.getDeclaringClass().isInstance(atRcvr)) { 258 return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs); 259 } else { 260 if (jArgs.length == 0) { 261 // obj.selector() => obj.meta_invokeField(obj, selector) 262 return atRcvr.meta_invokeField(atRcvr, downSelector(jSelector)); 263 } else { 264 // obj.selector(args) => obj.meta_invoke(obj, selector, args) 265 return atRcvr.impl_invoke(atRcvr, downSelector(jSelector), NATTable.atValue(jArgs)); 266 } 267 } 268 } 269 } 270 271 /** 272 * upInvocation takes an explicit AmbientTalk method invocation and turns it into an 273 * implicitly performed Java invocation. 274 * 275 * Depending on whether the AmbientTalk invocation happens at the base-level or the meta-level 276 * (i.e. the receiver denotes a base-level object or a mirror), the jSelector parameter will have 277 * a different prefix. 278 * 279 * @param atOrigRcvr the original AmbientTalk object that received the invocation 280 * @param jSelector the selector of the message to be invoked, converted to a Java selector 281 * @param atArgs the arguments to the AmbientTalk method invocation 282 * @return the return value of the Java method invoked via the java invocation. 283 * 284 * Example: 285 * eval "tbl.at(1)" where tbl is a NATTable 286 * => upInvocation(aNATTable, "base_at", ATObject[] { ATNumber(1) }) 287 * => NATTable must have a method named base_at 288 * 289 * Example: 290 * eval "(reflect: tbl).invoke(tbl, "at", [1])" where tbl is a NATTable 291 * => upInvocation(aNATTable, "meta_invoke", ATObject[] { aNATTable, ATSymbol('at'), ATTable([ATNumber(1)]) }) 292 * => NATTable must have a method named meta_invoke 293 */ 294 public static final ATObject upInvocation(ATObject atOrigRcvr, String jSelector, ATSymbol atSelector, ATTable atArgs) throws InterpreterException { 295 return JavaInterfaceAdaptor.invokeNativeATMethod( 296 atOrigRcvr.getClass(), 297 atOrigRcvr, 298 jSelector, 299 atSelector, atArgs.asNativeTable().elements_); 300 } 301 302 /** 303 * upRespondsTo transforms an explicit AmbientTalk respondsTo meta-level request 304 * into an implicit check whether the given jRcvr java object has a method 305 * corresponding to the given selector, prefixed with base_ 306 * 307 * @param jRcvr the Java object being queried for a certain selector 308 * @param jSelector the selector of the message to be invoked, converted to a Java selector 309 * @return a boolean indicating whether the jRcvr implements a method corresponding to base_ + atSelector 310 * 311 * Example: 312 * eval "(reflect: [1,2,3]).respondsTo("at")" where the receiver of repondsTo is a NATTable 313 * => upRespondsTo(aNATTable, "at") 314 * => NATTable must have a method named base_at 315 */ 316 public static final boolean upRespondsTo(ATObject jRcvr,String jSelector) throws InterpreterException { 317 return JavaInterfaceAdaptor.hasApplicableJavaMethod( 318 jRcvr.getClass(), 319 jSelector); 320 } 321 322 /** 323 * upMethodSelection takes an explicit AmbientTalk field selection and checks whether 324 * a Java method exists that matches the selector. If so, this method is wrapped in a 325 * NativeClosure and returned. 326 * 327 * @param atOrigRcvr the original AmbientTalk object that received the selection 328 * @param jSelector the selector of the message to be invoked, converted to a Java selector 329 * @return a closure wrapping the method selected via the AmbientTalk selection. 330 * 331 * Example: 332 * eval "[1,2,3].at" 333 * => upSelection(aNATTable, "at") 334 * => either NATTable must have a method base_at, which is then wrapped 335 */ 336 public static final NativeMethod upMethodSelection(ATObject atOrigRcvr, String jSelector, ATSymbol origSelector) throws InterpreterException { 337 Method m = JavaInterfaceAdaptor.getNativeATMethod(atOrigRcvr.getClass(), atOrigRcvr, jSelector, origSelector); 338 return new NativeMethod(m, origSelector, atOrigRcvr); 339 } 340 341 /** 342 * upInstanceCreation takes an explicit AmbientTalk 'new' invocation and turns it into an 343 * implicit Java instance creation by calling a constructor. The initargs are upped as well 344 * and are passed as arguments to the constructor. 345 * 346 * @param jRcvr the Java object having received the call to new 347 * @param atInitargs the arguments to the constructor 348 * @return a new instance of a Java class 349 * @throws InterpreterException 350 */ 351 public static final ATObject upInstanceCreation(ATObject jRcvr, ATTable atInitargs) throws InterpreterException { 352 ATObject[] args = atInitargs.asNativeTable().elements_; 353 return JavaInterfaceAdaptor.createNativeATObject(jRcvr.getClass(), args); 354 } 355 356 public static final ATObject upExceptionCreation(InterpreterException jRcvr, ATTable atInitargs) throws InterpreterException { 357 ATObject[] args = atInitargs.asNativeTable().elements_; 358 return Symbiosis.symbioticInstanceCreation(new JavaConstructor(jRcvr.getClass()), args); 359 } 360 361 /** 362 * Pass an AmbientTalk meta-level object into the base-level 363 */ 364 public static final ATObject downObject(ATObject metaObject) throws InterpreterException { 365 return metaObject; 366 /*if (metaObject.meta_isTaggedAs(NativeTypeTags._MIRROR_).asNativeBoolean().javaValue) { 367 return metaObject.meta_select(metaObject, OBJMirrorRoot._BASE_NAME_); 368 } else { 369 return metaObject; // most native objects represent both the object at the base and at the meta-level 370 }*/ 371 } 372 373 /** 374 * Pass an AmbientTalk base-level object to the meta-level 375 */ 376 public static final ATObject upObject(ATObject baseObject) { 377 if (baseObject instanceof NATMirage) { 378 return ((NATMirage) baseObject).getMirror(); 379 } else { 380 return baseObject; 381 } 382 } 383 384 /** 385 * Returns, for a given AmbientTalk object atObj, an array of NativeMethod objects corresponding 386 * to all non-static methods of that object's class, where each method's name is prefixed with 'base_' 387 */ 388 public static final ATMethod[] downBaseLevelMethods(ATObject atObj) throws InterpreterException { 389 Method[] allBaseMethods = 390 JavaInterfaceAdaptor.allMethodsPrefixed(atObj.getClass(), Reflection._BASE_PREFIX_, false); 391 Vector allATBaseMethods = new Vector(); 392 for (int i = 0; i < allBaseMethods.length; i++) { 393 Method m = allBaseMethods[i]; 394 String nam = m.getName(); 395 allATBaseMethods.add(new NativeMethod(m, downBaseLevelSelector(nam), atObj)); 396 } 397 return (ATMethod[]) allATBaseMethods.toArray(new ATMethod[allATBaseMethods.size()]); 398 } 399 400 /** 401 * Returns, for a given AmbientTalk object natObj, an array of NativeMethod objects corresponding 402 * to all non-static methods of that object's class, where each method's name is prefixed with 'meta_' 403 */ 404 public static final ATMethod[] downMetaLevelMethods(ATObject natObj) throws InterpreterException { 405 Method[] allMetaMethods = 406 JavaInterfaceAdaptor.allMethodsPrefixed(natObj.getClass(), Reflection._META_PREFIX_, false); 407 Vector allATMetaMethods = new Vector(); 408 for (int i = 0; i < allMetaMethods.length; i++) { 409 Method m = allMetaMethods[i]; 410 String nam = m.getName(); 411 allATMetaMethods.add(new NativeMethod(m, downMetaLevelSelector(nam), natObj)); 412 } 413 return (ATMethod[]) allATMetaMethods.toArray(new ATMethod[allATMetaMethods.size()]); 414 } 415 416 private static final REProgram oprCode = Regexp.compile("_op(\\w\\w\\w)_"); //'_op', 3 chars, '_' 417 private static final REProgram symbol = Regexp.compile("\\W"); //any non-word character 418 private static final REProgram underScore = Regexp.compile("_"); 419 private static final REProgram colon = Regexp.compile(":"); 420 421 private static String stripPrefix(String input, String prefix) { 422 // ^ matches start of input 423 // Backport from JDK 1.4 to 1.3 424 // return input.replaceFirst("\\A"+prefix, ""); 425 return new RE(Regexp.compile("^"+prefix)).subst(input, "", RE.REPLACE_FIRSTONLY); 426 // return Pattern.compile("\\A"+prefix).matcher(new StringBuffer(input)).replaceFirst(""); 427 } 428 429 private static final String oprCode2Symbol(String code) { 430 switch (code.charAt(0)) { 431 case 'p': if (code.equals("pls")) { return "+"; } else break; 432 case 'm': if (code.equals("mns")) { return "-"; } else break; 433 case 't': if (code.equals("tms")) { return "*"; } else 434 if (code.equals("til")) { return "~"; } else break; 435 case 'd': if (code.equals("div")) { return "/"; } else break; 436 case 'b': if (code.equals("bsl")) { return "\\"; } else break; 437 case 'n': if (code.equals("not")) { return "!"; } else break; 438 case 'g': if (code.equals("gtx")) { return ">"; } else break; 439 case 'l': if (code.equals("ltx")) { return "<"; } else break; 440 case 'e': if (code.equals("eql")) { return "="; } else break; 441 case 'q': if (code.equals("que")) { return "?"; } else break; 442 case 'r': if (code.equals("rem")) { return "%"; } else break; 443 } 444 return "_op" + code + "_"; // no match, return original input 445 } 446 447 private static final String symbol2oprCode(String symbol) { 448 switch (symbol.charAt(0)) { 449 case '+': return "pls"; 450 case '-': return "mns"; 451 case '*': return "tms"; 452 case '/': return "div"; 453 case '\\': return "bsl"; 454 case '!': return "not"; 455 case '>': return "gtx"; 456 case '<': return "ltx"; 457 case '=': return "eql"; 458 case '~': return "til"; 459 case '?': return "que"; 460 case '%': return "rem"; 461 default: return symbol; // no match, return original input 462 } 463 } 464 465 private static final String javaToAmbientTalkSelector(String jSelector) { 466 // find every occurence of _op\w\w\w_ and convert it into a symbol 467 try { 468 final RE codePattern = new RE(oprCode); 469 470 String oprcodesReplaced = Regexp.replaceAll(codePattern, jSelector, new Regexp.StringCallable() { 471 public String call(String match) { 472 // match == _op{code}_ 473 474 // CAREFUL: we are mutating the same RE object used by the replaceAll function 475 // itself! This is normally not a problem if we do not depend on the codePattern's 476 // stateful methods 477 codePattern.match(match); 478 479 // _op{code}_ -> operator symbol 480 return oprCode2Symbol(codePattern.getParen(1)); 481 } 482 }); 483 // finally, replace all "_" by ":" 484 return new RE(underScore).subst(oprcodesReplaced,":"); 485 } catch (InterpreterException e) { // all this to make compiler happy 486 Logging.VirtualMachine_LOG.fatal("unexpected exception: " + e.getMessage(), e); 487 throw new RuntimeException("Unexpected exception: " + e.getMessage()); 488 } 489 } 490 491}