/interpreter/tags/at2-build270707/src/edu/vub/at/objects/mirrors/Reflection.java

http://ambienttalk.googlecode.com/ · Java · 470 lines · 197 code · 32 blank · 241 comment · 52 complexity · f581cf8848ca312c37918fc577ed6d73 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.XArityMismatch;
  33. import edu.vub.at.exceptions.XIllegalArgument;
  34. import edu.vub.at.exceptions.XSelectorNotFound;
  35. import edu.vub.at.exceptions.XUndefinedSlot;
  36. import edu.vub.at.objects.ATField;
  37. import edu.vub.at.objects.ATMethod;
  38. import edu.vub.at.objects.ATObject;
  39. import edu.vub.at.objects.ATTable;
  40. import edu.vub.at.objects.grammar.ATSymbol;
  41. import edu.vub.at.objects.natives.NATTable;
  42. import edu.vub.at.objects.natives.grammar.AGSymbol;
  43. import edu.vub.at.objects.symbiosis.Symbiosis;
  44. import edu.vub.util.Matcher;
  45. import edu.vub.util.Pattern;
  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 = atSelector.base_text().asNativeText().javaValue;
  136. // Backport from JDK 1.4 to 1.3
  137. // nam = nam.replaceAll(":", "_");
  138. nam = Pattern.compile(":").matcher(new StringBuffer(nam)).replaceAll("_");
  139. // operator symbol -> _op{code}_
  140. Matcher m = symbol.matcher(new StringBuffer(nam));
  141. StringBuffer sb = new StringBuffer();
  142. while (m.find()) {
  143. // find every occurence of a non-word character and convert it into a symbol
  144. String oprCode = symbol2oprCode(m.group(0));
  145. // only add the _op prefix and _ postfix if the code has been found...
  146. m.appendReplacement(sb, (oprCode.length() == 3) ? "_op" + oprCode + "_" : oprCode);
  147. }
  148. m.appendTail(sb);
  149. return sb.toString();
  150. }
  151. /**
  152. * Transforms an AmbientTalk selector into a Java-level selector prefixed with base_.
  153. */
  154. public static final String upBaseLevelSelector(ATSymbol atSelector) throws InterpreterException {
  155. return Reflection._BASE_PREFIX_ + upSelector(atSelector);
  156. }
  157. /**
  158. * Transforms an AmbientTalk selector into a Java-level selector prefixed with meta_.
  159. */
  160. public static final String upMetaLevelSelector(ATSymbol atSelector) throws InterpreterException {
  161. return Reflection._META_PREFIX_ + upSelector(atSelector);
  162. }
  163. /**
  164. * Constructs an AmbientTalk ATMethod from a Java method.
  165. * Given an object obj and a String sel, it is checked whether obj has a method
  166. * named sel. If so, the corresponding Java method is wrapped in a NativeMethod.
  167. * If not, the downing fails.
  168. *
  169. * @param natObject the native AmbientTalk object in whose class the method should be found
  170. * @param jSelector a selector which should yield a method in natObject
  171. * @param origName the original AmbientTalk name of the method
  172. * @return a reified method wrapping the Java method
  173. *
  174. * Example:
  175. * eval "(reflect: tbl).getMethod('at')" where tbl is a NATTable
  176. * => downMethod(aNATTable, "base_at")
  177. * => NATTable must have a method named base_at
  178. *
  179. * Callers should use the more specialised 'downBaseLevelMethod' and 'downMetaLevelMethod'
  180. * methods to specify the prefix of the method to be found
  181. */
  182. public static final ATMethod downMethod(ATObject natObject, String jSelector, ATSymbol origName) throws InterpreterException {
  183. return new NativeMethod(JavaInterfaceAdaptor.getNativeATMethod(natObject.getClass(), natObject, jSelector, origName),
  184. origName,
  185. natObject);
  186. }
  187. public static final ATMethod downBaseLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
  188. return downMethod(natObject, upBaseLevelSelector(atSelector), atSelector);
  189. }
  190. public static final ATMethod downMetaLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
  191. return downMethod(natObject, upMetaLevelSelector(atSelector), atSelector);
  192. }
  193. /**
  194. * downInvocation takes an implicit Java invocation and turns it into an explicit
  195. * AmbientTalk invocation process. This happens when Java code sends normal
  196. * Java messages to AmbientTalk objects (wrapped by a mirage).
  197. *
  198. * @param atRcvr the AmbientTalk object having received the Java method invocation
  199. * @param jSelector the Java selector, to be converted to an AmbientTalk selector
  200. * @param jArgs the arguments to the Java method invocation (normally all args are ATObjects)
  201. * jArgs may be null, indicating that there are no arguments
  202. * @return the return value of the AmbientTalk method invoked via the java invocation.
  203. *
  204. * Example:
  205. * in Java: "tbl.base_at(1)" where tbl is an ATTable coercer wrapping aNATObject
  206. * => downInvocation(aNATObject, "base_at", ATObject[] { ATNumber(1) })
  207. * => aNATObject must implement a method named "at"
  208. *
  209. * Depending on the prefix of the invoked Java method selector, the following translation should occur:
  210. * - obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
  211. * - obj.base_selector() => obj.meta_invokeField(obj, selector)
  212. * - obj.meta_selector(args) => obj.meta_selector(args)
  213. * - obj.selector(args) => either obj.selector(args) if selector is understood natively
  214. * or obj.meta_invoke(obj, selector, args) otherwise
  215. * - obj.selector() => obj.meta_invokeField(obj, selector)
  216. */
  217. public static final ATObject downInvocation(ATObject atRcvr, Method jMethod, ATObject[] jArgs) throws InterpreterException {
  218. String jSelector = jMethod.getName();
  219. if (jArgs == null) { jArgs = NATTable.EMPTY.elements_; }
  220. if (jSelector.startsWith(Reflection._BASE_PREFIX_)) {
  221. if (jArgs.length == 0) {
  222. // obj.base_selector() => obj.meta_invokeField(obj, selector)
  223. return atRcvr.meta_invokeField(atRcvr, downBaseLevelSelector(jSelector));
  224. } else {
  225. // obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
  226. return atRcvr.meta_invoke(atRcvr, downBaseLevelSelector(jSelector), NATTable.atValue(jArgs));
  227. }
  228. } else if (jSelector.startsWith(Reflection._META_PREFIX_)) {
  229. // obj.meta_selector(args) => obj.meta_selector(args)
  230. return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
  231. } else {
  232. // atRcvr can respond to the given method natively
  233. if (jMethod.getDeclaringClass().isInstance(atRcvr)) {
  234. return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
  235. } else {
  236. if (jArgs.length == 0) {
  237. // obj.selector() => obj.meta_invokeField(obj, selector)
  238. return atRcvr.meta_invokeField(atRcvr, downSelector(jSelector));
  239. } else {
  240. // obj.selector(args) => obj.meta_invoke(obj, selector, args)
  241. return atRcvr.meta_invoke(atRcvr, downSelector(jSelector), NATTable.atValue(jArgs));
  242. }
  243. }
  244. }
  245. }
  246. /**
  247. * upInvocation takes an explicit AmbientTalk method invocation and turns it into an
  248. * implicitly performed Java invocation.
  249. *
  250. * Depending on whether the AmbientTalk invocation happens at the base-level or the meta-level
  251. * (i.e. the receiver denotes a base-level object or a mirror), the jSelector parameter will have
  252. * a different prefix.
  253. *
  254. * @param atOrigRcvr the original AmbientTalk object that received the invocation
  255. * @param jSelector the selector of the message to be invoked, converted to a Java selector
  256. * @param atArgs the arguments to the AmbientTalk method invocation
  257. * @return the return value of the Java method invoked via the java invocation.
  258. *
  259. * Example:
  260. * eval "tbl.at(1)" where tbl is a NATTable
  261. * => upInvocation(aNATTable, "base_at", ATObject[] { ATNumber(1) })
  262. * => NATTable must have a method named base_at
  263. *
  264. * Example:
  265. * eval "(reflect: tbl).invoke(tbl, "at", [1])" where tbl is a NATTable
  266. * => upInvocation(aNATTable, "meta_invoke", ATObject[] { aNATTable, ATSymbol('at'), ATTable([ATNumber(1)]) })
  267. * => NATTable must have a method named meta_invoke
  268. */
  269. public static final ATObject upInvocation(ATObject atOrigRcvr, String jSelector, ATSymbol atSelector, ATTable atArgs) throws InterpreterException {
  270. return JavaInterfaceAdaptor.invokeNativeATMethod(
  271. atOrigRcvr.getClass(),
  272. atOrigRcvr,
  273. jSelector,
  274. atSelector, atArgs.asNativeTable().elements_);
  275. }
  276. /**
  277. * upRespondsTo transforms an explicit AmbientTalk respondsTo meta-level request
  278. * into an implicit check whether the given jRcvr java object has a method
  279. * corresponding to the given selector, prefixed with base_
  280. *
  281. * @param jRcvr the Java object being queried for a certain selector
  282. * @param jSelector the selector of the message to be invoked, converted to a Java selector
  283. * @return a boolean indicating whether the jRcvr implements a method corresponding to base_ + atSelector
  284. *
  285. * Example:
  286. * eval "(reflect: [1,2,3]).respondsTo("at")" where the receiver of repondsTo is a NATTable
  287. * => upRespondsTo(aNATTable, "at")
  288. * => NATTable must have a method named base_at
  289. */
  290. public static final boolean upRespondsTo(ATObject jRcvr,String jSelector) throws InterpreterException {
  291. return JavaInterfaceAdaptor.hasApplicableJavaMethod(
  292. jRcvr.getClass(),
  293. jSelector);
  294. }
  295. /**
  296. * upMethodSelection takes an explicit AmbientTalk field selection and checks whether
  297. * a Java method exists that matches the selector. If so, this method is wrapped in a
  298. * NativeClosure and returned.
  299. *
  300. * @param atOrigRcvr the original AmbientTalk object that received the selection
  301. * @param jSelector the selector of the message to be invoked, converted to a Java selector
  302. * @return a closure wrapping the method selected via the AmbientTalk selection.
  303. *
  304. * Example:
  305. * eval "[1,2,3].at"
  306. * => upSelection(aNATTable, "at")
  307. * => either NATTable must have a method base_at, which is then wrapped
  308. */
  309. public static final NativeMethod upMethodSelection(ATObject atOrigRcvr, String jSelector, ATSymbol origSelector) throws InterpreterException {
  310. Method m = JavaInterfaceAdaptor.getNativeATMethod(atOrigRcvr.getClass(), atOrigRcvr, jSelector, origSelector);
  311. return new NativeMethod(m, origSelector, atOrigRcvr);
  312. }
  313. /**
  314. * upInstanceCreation takes an explicit AmbientTalk 'new' invocation and turns it into an
  315. * implicit Java instance creation by calling a constructor. The initargs are upped as well
  316. * and are passed as arguments to the constructor.
  317. *
  318. * @param jRcvr the Java object having received the call to new
  319. * @param atInitargs the arguments to the constructor
  320. * @return a new instance of a Java class
  321. * @throws InterpreterException
  322. */
  323. public static final ATObject upInstanceCreation(ATObject jRcvr, ATTable atInitargs) throws InterpreterException {
  324. ATObject[] args = atInitargs.asNativeTable().elements_;
  325. return JavaInterfaceAdaptor.createNativeATObject(jRcvr.getClass(), args);
  326. }
  327. public static final ATObject upExceptionCreation(InterpreterException jRcvr, ATTable atInitargs) throws InterpreterException {
  328. ATObject[] args = atInitargs.asNativeTable().elements_;
  329. return Symbiosis.symbioticInstanceCreation(jRcvr.getClass(), args);
  330. }
  331. /**
  332. * Pass an AmbientTalk meta-level object into the base-level
  333. */
  334. public static final ATObject downObject(ATObject metaObject) throws InterpreterException {
  335. return metaObject;
  336. /*if (metaObject.meta_isTaggedAs(NativeTypeTags._MIRROR_).asNativeBoolean().javaValue) {
  337. return metaObject.meta_select(metaObject, OBJMirrorRoot._BASE_NAME_);
  338. } else {
  339. return metaObject; // most native objects represent both the object at the base and at the meta-level
  340. }*/
  341. }
  342. /**
  343. * Pass an AmbientTalk base-level object to the meta-level
  344. */
  345. public static final ATObject upObject(ATObject baseObject) {
  346. if (baseObject instanceof NATMirage) {
  347. return ((NATMirage) baseObject).getMirror();
  348. } else {
  349. return baseObject;
  350. }
  351. }
  352. /**
  353. * Returns, for a given AmbientTalk object atObj, an array of NativeMethod objects corresponding
  354. * to all non-static methods of that object's class, where each method's name is prefixed with 'base_'
  355. */
  356. public static final ATMethod[] downBaseLevelMethods(ATObject atObj) throws InterpreterException {
  357. Method[] allBaseMethods =
  358. JavaInterfaceAdaptor.allMethodsPrefixed(atObj.getClass(), Reflection._BASE_PREFIX_, false);
  359. Vector allATBaseMethods = new Vector();
  360. for (int i = 0; i < allBaseMethods.length; i++) {
  361. Method m = allBaseMethods[i];
  362. String nam = m.getName();
  363. allATBaseMethods.add(new NativeMethod(m, downBaseLevelSelector(nam), atObj));
  364. }
  365. return (ATMethod[]) allATBaseMethods.toArray(new ATMethod[allATBaseMethods.size()]);
  366. }
  367. /**
  368. * Returns, for a given AmbientTalk object natObj, an array of NativeMethod objects corresponding
  369. * to all non-static methods of that object's class, where each method's name is prefixed with 'meta_'
  370. */
  371. public static final ATMethod[] downMetaLevelMethods(ATObject natObj) throws InterpreterException {
  372. Method[] allMetaMethods =
  373. JavaInterfaceAdaptor.allMethodsPrefixed(natObj.getClass(), Reflection._META_PREFIX_, false);
  374. Vector allATMetaMethods = new Vector();
  375. for (int i = 0; i < allMetaMethods.length; i++) {
  376. Method m = allMetaMethods[i];
  377. String nam = m.getName();
  378. allATMetaMethods.add(new NativeMethod(m, downMetaLevelSelector(nam), natObj));
  379. }
  380. return (ATMethod[]) allATMetaMethods.toArray(new ATMethod[allATMetaMethods.size()]);
  381. }
  382. private static final Pattern oprCode = Pattern.compile("_op(\\w\\w\\w)_"); //'_op', 3 chars, '_'
  383. private static final Pattern symbol = Pattern.compile("\\W"); //any non-word character
  384. private static String stripPrefix(String input, String prefix) {
  385. // \A matches start of input
  386. // Backport from JDK 1.4 to 1.3
  387. // return input.replaceFirst("\\A"+prefix, "");
  388. return Pattern.compile("\\A"+prefix).matcher(new StringBuffer(input)).replaceFirst("");
  389. }
  390. private static final String oprCode2Symbol(String code) {
  391. switch (code.charAt(0)) {
  392. case 'p': if (code.equals("pls")) { return "+"; } else break;
  393. case 'm': if (code.equals("mns")) { return "-"; } else break;
  394. case 't': if (code.equals("tms")) { return "*"; } else
  395. if (code.equals("til")) { return "~"; } else break;
  396. case 'd': if (code.equals("div")) { return "/"; } else break;
  397. case 'b': if (code.equals("bsl")) { return "\\"; } else break;
  398. case 'n': if (code.equals("not")) { return "!"; } else break;
  399. case 'g': if (code.equals("gtx")) { return ">"; } else break;
  400. case 'l': if (code.equals("ltx")) { return "<"; } else break;
  401. case 'e': if (code.equals("eql")) { return "="; } else break;
  402. case 'q': if (code.equals("que")) { return "?"; } else break;
  403. case 'r': if (code.equals("rem")) { return "%"; } else break;
  404. }
  405. return "_op" + code + "_"; // no match, return original input
  406. }
  407. private static final String symbol2oprCode(String symbol) {
  408. switch (symbol.charAt(0)) {
  409. case '+': return "pls";
  410. case '-': return "mns";
  411. case '*': return "tms";
  412. case '/': return "div";
  413. case '\\': return "bsl";
  414. case '!': return "not";
  415. case '>': return "gtx";
  416. case '<': return "ltx";
  417. case '=': return "eql";
  418. case '~': return "til";
  419. case '?': return "que";
  420. case '%': return "rem";
  421. default: return symbol; // no match, return original input
  422. }
  423. }
  424. private static final String javaToAmbientTalkSelector(String jSelector) {
  425. // _op{code}_ -> operator symbol
  426. Matcher m = oprCode.matcher(new StringBuffer(jSelector));
  427. StringBuffer sb = new StringBuffer();
  428. while (m.find()) {
  429. // find every occurence of _op\w\w\w_ and convert it into a symbol
  430. m.appendReplacement(sb, oprCode2Symbol(m.group(1)));
  431. }
  432. m.appendTail(sb);
  433. // _ -> :
  434. // Backport from JDK 1.4 to 1.3
  435. // return sb.toString().replaceAll("_", ":");
  436. return Pattern.compile("_").matcher(sb).replaceAll(":");
  437. }
  438. }