/interpreter/tags/at2dist170907/src/edu/vub/at/eval/Import.java

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