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

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