/interpreter/tags/at2dist110511/src/edu/vub/at/objects/mirrors/Reflection.java

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