/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/eval/Import.java

http://ambienttalk.googlecode.com/ · Java · 376 lines · 175 code · 37 blank · 164 comment · 41 complexity · a5318b9cfe1e3c8eb5882d12b3120922 MD5 · raw file

  1. /**
  2. * AmbientTalk/2 Project
  3. * Import.java created on 8-mrt-2007 at 12:57:46
  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.eval;
  29. import edu.vub.at.exceptions.InterpreterException;
  30. import edu.vub.at.exceptions.XDuplicateSlot;
  31. import edu.vub.at.exceptions.XIllegalOperation;
  32. import edu.vub.at.exceptions.XImportConflict;
  33. import edu.vub.at.objects.ATBoolean;
  34. import edu.vub.at.objects.ATContext;
  35. import edu.vub.at.objects.ATField;
  36. import edu.vub.at.objects.ATMethod;
  37. import edu.vub.at.objects.ATObject;
  38. import edu.vub.at.objects.ATTable;
  39. import edu.vub.at.objects.grammar.ATSymbol;
  40. import edu.vub.at.objects.mirrors.NativeClosure;
  41. import edu.vub.at.objects.mirrors.PrimitiveMethod;
  42. import edu.vub.at.objects.natives.NATBoolean;
  43. import edu.vub.at.objects.natives.NATClosure;
  44. import edu.vub.at.objects.natives.NATNil;
  45. import edu.vub.at.objects.natives.NATNumber;
  46. import edu.vub.at.objects.natives.NATObject;
  47. import edu.vub.at.objects.natives.NATTable;
  48. import java.util.HashSet;
  49. import java.util.Hashtable;
  50. import java.util.Iterator;
  51. import java.util.Set;
  52. import java.util.Vector;
  53. /**
  54. * Auxiliary class that provides the implementation of the native 'import' statement.
  55. *
  56. * @author tvcutsem
  57. */
  58. public final class Import {
  59. private static HashSet _DEFAULT_EXCLUDED_SLOTS_;
  60. private synchronized static HashSet getDefaultExcludedSlots() {
  61. if (_DEFAULT_EXCLUDED_SLOTS_ == null) {
  62. _DEFAULT_EXCLUDED_SLOTS_ = new HashSet();
  63. // prepare the default names to exclude
  64. _DEFAULT_EXCLUDED_SLOTS_.add(NATObject._SUPER_NAME_); // skip 'super', present in all objects
  65. _DEFAULT_EXCLUDED_SLOTS_.add(Evaluator._CURNS_SYM_); // sip '~', present in all namespaces
  66. _DEFAULT_EXCLUDED_SLOTS_.add(NATNil._EQL_NAME_); // skip '==', present in all objects via nil
  67. _DEFAULT_EXCLUDED_SLOTS_.add(NATNil._INI_NAME_); // skip 'init', present in all objects via nil
  68. _DEFAULT_EXCLUDED_SLOTS_.add(NATNil._NEW_NAME_); // skip 'new', present in all objects via nil
  69. }
  70. return _DEFAULT_EXCLUDED_SLOTS_;
  71. }
  72. /**
  73. * Given a table of tables, of the form [ [oldname, newname], ... ], returns a hashtable
  74. * mapping the old names to the new names.
  75. */
  76. public static Hashtable preprocessAliases(ATTable aliases) throws InterpreterException {
  77. Hashtable aliasMap = new Hashtable();
  78. if (aliases != NATTable.EMPTY) {
  79. NATNumber two = NATNumber.atValue(2);
  80. // preprocess the aliases
  81. ATObject[] mappings = aliases.asNativeTable().elements_;
  82. for (int i = 0; i < mappings.length; i++) {
  83. // expecting tuples [ oldname, newname ]
  84. ATTable alias = mappings[i].asTable();
  85. aliasMap.put(alias.base_at(NATNumber.ONE).asSymbol(), alias.base_at(two).asSymbol());
  86. }
  87. }
  88. return aliasMap;
  89. }
  90. /**
  91. * Given a table of symbols, returns a hashset containing all the names.
  92. */
  93. public static HashSet preprocessExcludes(ATTable exclusions) throws InterpreterException {
  94. if (exclusions != NATTable.EMPTY) {
  95. // make a copy of the default exclusion set such that the default set is not modified
  96. HashSet exclude = (HashSet) getDefaultExcludedSlots().clone();
  97. // preprocess the exclusions
  98. ATObject[] excludedNames = exclusions.asNativeTable().elements_;
  99. for (int i = 0; i < excludedNames.length; i++) {
  100. // expecting symbols
  101. exclude.add(excludedNames[i].asSymbol());
  102. }
  103. return exclude;
  104. } else {
  105. return getDefaultExcludedSlots();
  106. }
  107. }
  108. // private static final AGSymbol _IMPORTED_OBJECT_NAME_ = AGSymbol.jAlloc("importedObject");
  109. /**
  110. * Imports fields and methods from a given source object. This operation is very
  111. * akin to a class using a trait. For each field in the trait, a new field
  112. * is created in the importing 'host' object. For each method in the trait, a method
  113. * is added to the host object whose body consists of delegating the message
  114. * to the trait object.
  115. *
  116. * The purpose of import is to:
  117. * - be able to reuse the interface of an existing object (examples are
  118. * traits or 'mixins' such as Enumerable, Comparable, Observable, ...)
  119. * - be able to access the interface of an existing object without having
  120. * to qualify access. This is especially useful when applied to namespace
  121. * objects. E.g. 'import: at.collections' allows the importer to subsequently
  122. * write Vector.new() rather than at.collections.Vector.new()
  123. *
  124. * Import is implemented as abstract grammar and not as a native 'import:' function
  125. * because it requires access to (and modifies) the lexical scope of the invoker.
  126. * Native functions (or normal AT/2 functions, for that matter) have no access to that scope.
  127. * However, if a pseudovariable 'thisContext' were available in AT/2, import could probably
  128. * be defined as a method on contexts, as follows:
  129. *
  130. * def context.import: sourceObject {
  131. * def newHost := context.lexicalScope;
  132. * def allFields := (reflect: sourceObject).listFields().base;
  133. * def allMethods := (reflect: sourceObject).listMethods().base;
  134. * allFields.each: { |field|
  135. * (reflect: newHost).addField(field)
  136. * }
  137. * allMethods.each: { |method|
  138. * (reflect: newHost).addMethod(aliasFor(method.name), `[@args],
  139. * `#sourceObject^#(method.name)(@args))
  140. * }
  141. * nil
  142. * }
  143. *
  144. * All duplicate slot exceptions, which signify that an imported method or field already
  145. * exists, are caught during import. These exceptions are bundled into an XImportConflict
  146. * exception, which can be inspected by the caller to detect the conflicting, unimported,
  147. * fields or methods.
  148. *
  149. * @param sourceObject the object from which to import fields and methods
  150. * @param ctx the runtime context during which the import is performed, the lexical scope is the object that performed the import
  151. * @param aliases a mapping from old names (ATSymbol) to new names (ATSymbol)
  152. * @param exclude a set containing slot names (ATSymbol) to disregard
  153. */
  154. public static ATObject performImport(ATObject sourceObject, ATContext ctx,
  155. Hashtable aliases, HashSet exclude) throws InterpreterException {
  156. // first, check whether sourceObject contains all aliased and excluded names
  157. StringBuffer erroneousNames = null; // lazy instantiation
  158. Set oldNames = aliases.keySet();
  159. // check all aliased symbols
  160. for (Iterator iterator = oldNames.iterator(); iterator.hasNext();) {
  161. ATSymbol name = (ATSymbol) iterator.next();
  162. if (!sourceObject.meta_respondsTo(name).asNativeBoolean().javaValue) {
  163. if (erroneousNames == null) {
  164. erroneousNames = new StringBuffer(name.toString());
  165. } else {
  166. erroneousNames.append(", " + name.toString());
  167. }
  168. }
  169. }
  170. // check all non-default excludes symbols
  171. for (Iterator iterator = exclude.iterator(); iterator.hasNext();) {
  172. ATSymbol name = (ATSymbol) iterator.next();
  173. if (!_DEFAULT_EXCLUDED_SLOTS_.contains(name) &&
  174. !sourceObject.meta_respondsTo(name).asNativeBoolean().javaValue) {
  175. if (erroneousNames == null) {
  176. erroneousNames = new StringBuffer(name.toString());
  177. } else {
  178. erroneousNames.append(", " + name.toString());
  179. }
  180. }
  181. }
  182. if (erroneousNames != null) {
  183. throw new XIllegalOperation("Undefined aliased or excluded slots during import: "+erroneousNames.toString());
  184. }
  185. ATObject hostObject = ctx.base_lexicalScope();
  186. // stores all conflicting symbols, initialized lazily
  187. Vector conflicts = null;
  188. // the alias to be used for defining the new fields or methods
  189. ATSymbol alias;
  190. // define the aliased fields
  191. ATField[] fields = NATObject.listTransitiveFields(sourceObject);
  192. for (int i = 0; i < fields.length; i++) {
  193. ATField field = fields[i];
  194. // skip excluded fields, such as the 'super' field
  195. if (!exclude.contains(field.base_name())) {
  196. // check whether the field needs to be aliased
  197. alias = (ATSymbol) aliases.get(field.base_name());
  198. if (alias == null) {
  199. // no alias, use the original name
  200. alias = field.base_name();
  201. }
  202. try {
  203. hostObject.meta_defineField(alias, field.base_readField());
  204. } catch(XDuplicateSlot e) {
  205. if (conflicts == null) {
  206. conflicts = new Vector(1);
  207. }
  208. conflicts.add(e.getSlotName());
  209. }
  210. }
  211. }
  212. // define the delegate methods
  213. ATMethod[] methods = NATObject.listTransitiveMethods(sourceObject);
  214. if (methods.length > 0) {
  215. // create the lexical scope for the delegate method invocation by hand
  216. // NATCallframe delegateScope = new NATCallframe(hostObject);
  217. // add the parameter, it is used in the generated method
  218. // delegateScope.meta_defineField(_IMPORTED_OBJECT_NAME_, sourceObject);
  219. for (int i = 0; i < methods.length; i++) {
  220. ATSymbol origMethodName = methods[i].base_name();
  221. // filter out exluded methods, such as primitive methods like '==', 'new' and 'init'
  222. if (exclude.contains(origMethodName)) {
  223. // if these primitives would not be filtered out, they would override
  224. // the primitives of the host object, which is usually unwanted and could
  225. // lead to subtle bugs w.r.t. comparison and instance creation.
  226. continue;
  227. }
  228. // check whether the method needs to be aliased
  229. alias = (ATSymbol) aliases.get(origMethodName);
  230. if (alias == null) {
  231. // no alias, use the original name
  232. alias = origMethodName;
  233. }
  234. // def alias(@args) { importedObject^origMethodName(@args) }
  235. /* ATMethod delegate = new NATMethod(alias, Evaluator._ANON_MTH_ARGS_,
  236. new AGBegin(NATTable.of(
  237. //importedObject^origMethodName(@args)@[]
  238. new AGMessageSend(_IMPORTED_OBJECT_NAME_,
  239. new AGDelegationCreation(origMethodName,
  240. Evaluator._ANON_MTH_ARGS_, NATTable.EMPTY))))); */
  241. /*
  242. * Notice that the body of the delegate method is
  243. * sourceObject^selector@args)
  244. *
  245. * In order for this code to evaluate when the method is actually invoked
  246. * on the new host object, the symbol `sourceObject should evaluate to the
  247. * object contained in the variable sourceObject.
  248. *
  249. * To ensure this binding is correct at runtime, delegate methods are
  250. * added to objects as external methods whose lexical scope is the call
  251. * frame of this method invocation The delegate methods are not added as closures,
  252. * as a closure would fix the value of 'self' too early.
  253. *
  254. * When importing into a call frame, care must be taken that imported delegate
  255. * methods are added as closures, because call frames cannot contain methods.
  256. * In this case, the delegate is wrapped in a closure whose lexical scope is again
  257. * the call frame of this primitive method invocation. The value of self is fixed
  258. * to the current value, but this is OK given that the method is added to a call frame
  259. * which is 'selfless'.
  260. */
  261. try {
  262. /*(hostObject.isCallFrame()) {
  263. NATClosure clo = new NATClosure(delegate, ctx.base_withLexicalEnvironment(delegateScope));
  264. hostObject.meta_defineField(origMethodName, clo);
  265. } else {
  266. hostObject.meta_addMethod(new DelegateMethod(delegateScope, delegate));
  267. }*/
  268. DelegateMethod delegate = new DelegateMethod(alias, origMethodName, sourceObject);
  269. if (hostObject.isCallFrame()) {
  270. NATClosure clo = new NATClosure(delegate, ctx);
  271. hostObject.meta_defineField(alias, clo);
  272. } else {
  273. hostObject.meta_addMethod(delegate);
  274. }
  275. } catch(XDuplicateSlot e) {
  276. if (conflicts == null) {
  277. conflicts = new Vector(1);
  278. }
  279. conflicts.add(e.getSlotName());
  280. }
  281. }
  282. }
  283. if (conflicts == null) {
  284. // no conflicts found
  285. return Evaluator.getNil();
  286. } else {
  287. throw new XImportConflict((ATSymbol[]) conflicts.toArray(new ATSymbol[conflicts.size()]));
  288. }
  289. }
  290. /**
  291. * A delegate-method is a pass-by-copy method.
  292. * This is allowed because the lexical scope of a delegate method only stores a reference
  293. * to the delegate object and further refers to the host object.
  294. *
  295. * The reason why delegate methods are pass-by-copy is that this allows isolates
  296. * to import other isolates without any problems: when an isolate is parameter-passed,
  297. * all of its delegate methods are passed by copy just like regular methods, which is
  298. * fine as long as the delegate object from which the methods were imported is also
  299. * a delegate.
  300. */
  301. public static final class DelegateMethod extends PrimitiveMethod {
  302. private final ATSymbol origMethodName_;
  303. private final ATObject delegate_;
  304. /**
  305. * Create a new delegatemethod:
  306. * <code>
  307. * def alias(@args) {
  308. * delegate^origMethodName(@args)
  309. * }
  310. * </code>
  311. */
  312. public DelegateMethod(ATSymbol alias, ATSymbol origMethodName, ATObject delegate) throws InterpreterException {
  313. super(alias, Evaluator._ANON_MTH_ARGS_);
  314. origMethodName_ = origMethodName;
  315. delegate_ = delegate;
  316. }
  317. public boolean isNativeDelegateMethod() { return true; }
  318. public DelegateMethod asNativeDelegateMethod() { return this; }
  319. public ATObject base_apply(ATTable args, ATContext ctx) throws InterpreterException {
  320. return delegate_.impl_invoke(ctx.base_receiver(), origMethodName_, args);
  321. }
  322. public ATBoolean base__opeql__opeql_(ATObject other) throws InterpreterException {
  323. if (other.isNativeDelegateMethod()) {
  324. final DelegateMethod m = other.asNativeDelegateMethod();
  325. return (m.origMethodName_.base__opeql__opeql_(origMethodName_).base_and_(
  326. new NativeClosure(this) {
  327. public ATObject base_apply(ATTable args) throws InterpreterException {
  328. return m.delegate_.base__opeql__opeql_(delegate_);
  329. }
  330. }));
  331. } else {
  332. return NATBoolean._FALSE_;
  333. }
  334. }
  335. public ATTable base_annotations() throws InterpreterException {
  336. return delegate_.meta_select(delegate_, origMethodName_).base_method().base_annotations();
  337. }
  338. }
  339. }