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

http://ambienttalk.googlecode.com/ · Java · 436 lines · 214 code · 42 blank · 180 comment · 56 complexity · ba5be4d615996c7cbd711b23bafc733f 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.coercion.NativeTypeTags;
  40. import edu.vub.at.objects.grammar.ATSymbol;
  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 edu.vub.at.objects.natives.NATText;
  49. import edu.vub.at.objects.natives.grammar.AGSymbol;
  50. import java.util.Collection;
  51. import java.util.HashSet;
  52. import java.util.Hashtable;
  53. import java.util.Iterator;
  54. import java.util.Map;
  55. import java.util.Set;
  56. import java.util.Vector;
  57. /**
  58. * Auxiliary class that provides the implementation of the native 'import' statement.
  59. *
  60. * @author tvcutsem
  61. */
  62. public final class Import {
  63. private static HashSet _DEFAULT_EXCLUDED_SLOTS_;
  64. private synchronized static HashSet getDefaultExcludedSlots() {
  65. if (_DEFAULT_EXCLUDED_SLOTS_ == null) {
  66. _DEFAULT_EXCLUDED_SLOTS_ = new HashSet();
  67. // prepare the default names to exclude
  68. _DEFAULT_EXCLUDED_SLOTS_.add(NATObject._SUPER_NAME_); // skip 'super', present in all objects
  69. _DEFAULT_EXCLUDED_SLOTS_.add(Evaluator._CURNS_SYM_); // skip '~', present in all namespaces
  70. }
  71. return _DEFAULT_EXCLUDED_SLOTS_;
  72. }
  73. /**
  74. * Given a table of tables, of the form [ [oldname, newname], ... ], returns a hashtable
  75. * mapping the old names to the new names.
  76. */
  77. public static Hashtable preprocessAliases(ATTable aliases) throws InterpreterException {
  78. Hashtable aliasMap = new Hashtable();
  79. if (aliases != NATTable.EMPTY) {
  80. NATNumber two = NATNumber.atValue(2);
  81. // preprocess the aliases
  82. ATObject[] mappings = aliases.asNativeTable().elements_;
  83. for (int i = 0; i < mappings.length; i++) {
  84. // expecting tuples [ oldname, newname ]
  85. ATTable alias = mappings[i].asTable();
  86. aliasMap.put(alias.base_at(NATNumber.ONE).asSymbol(), alias.base_at(two).asSymbol());
  87. }
  88. }
  89. return aliasMap;
  90. }
  91. /**
  92. * Given a table of symbols, returns a hashset containing all the names.
  93. */
  94. public static HashSet preprocessExcludes(ATTable exclusions) throws InterpreterException {
  95. if (exclusions != NATTable.EMPTY) {
  96. // make a copy of the default exclusion set such that the default set is not modified
  97. HashSet exclude = (HashSet) getDefaultExcludedSlots().clone();
  98. // preprocess the exclusions
  99. ATObject[] excludedNames = exclusions.asNativeTable().elements_;
  100. for (int i = 0; i < excludedNames.length; i++) {
  101. // expecting symbols
  102. exclude.add(excludedNames[i].asSymbol());
  103. }
  104. return exclude;
  105. } else {
  106. return getDefaultExcludedSlots();
  107. }
  108. }
  109. // private static final AGSymbol _IMPORTED_OBJECT_NAME_ = AGSymbol.jAlloc("importedObject");
  110. /**
  111. * Imports fields and methods from a given source object. This operation is very
  112. * akin to a class using a trait. For each field in the trait, a new field
  113. * is created in the importing 'host' object. For each method in the trait, a method
  114. * is added to the host object whose body consists of delegating the message
  115. * to the trait object.
  116. *
  117. * The purpose of import is to:
  118. * - be able to reuse the interface of an existing object (examples are
  119. * traits or 'mixins' such as Enumerable, Comparable, Observable, ...)
  120. * - be able to access the interface of an existing object without having
  121. * to qualify access. This is especially useful when applied to namespace
  122. * objects. E.g. 'import: at.collections' allows the importer to subsequently
  123. * write Vector.new() rather than at.collections.Vector.new()
  124. *
  125. * Import is implemented as abstract grammar and not as a native 'import:' function
  126. * because it requires access to (and modifies) the lexical scope of the invoker.
  127. * Native functions (or normal AT/2 functions, for that matter) have no access to that scope.
  128. * However, if a pseudovariable 'thisContext' were available in AT/2, import could probably
  129. * be defined as a method on contexts, as follows:
  130. *
  131. * def context.import: sourceObject {
  132. * def newHost := context.lexicalScope;
  133. * def allFields := (reflect: sourceObject).listFields().base;
  134. * def allMethods := (reflect: sourceObject).listMethods().base;
  135. * allFields.each: { |field|
  136. * (reflect: newHost).addField(field)
  137. * }
  138. * allMethods.each: { |method|
  139. * (reflect: newHost).addMethod(aliasFor(method.name), `[@args],
  140. * `#sourceObject^#(method.name)(@args))
  141. * }
  142. * nil
  143. * }
  144. *
  145. * All duplicate slot exceptions, which signify that an imported method or field already
  146. * exists, are caught during import. These exceptions are bundled into an XImportConflict
  147. * exception, which can be inspected by the caller to detect the conflicting, unimported,
  148. * fields or methods.
  149. *
  150. * @param sourceObject the object from which to import fields and methods
  151. * @param ctx the runtime context during which the import is performed, the lexical scope is the object that performed the import
  152. * @param aliases a mapping from old names (ATSymbol) to new names (ATSymbol)
  153. * @param exclude a set containing slot names (ATSymbol) to disregard
  154. */
  155. public static ATObject performImport(ATObject sourceObject, ATContext ctx,
  156. Hashtable aliases, HashSet exclude) throws InterpreterException {
  157. // first, check whether sourceObject contains all aliased and excluded names
  158. StringBuffer erroneousNames = null; // lazy instantiation
  159. Set oldNames = aliases.keySet();
  160. // check all aliased symbols
  161. for (Iterator iterator = oldNames.iterator(); iterator.hasNext();) {
  162. ATSymbol name = (ATSymbol) iterator.next();
  163. if (!sourceObject.meta_respondsTo(name).asNativeBoolean().javaValue) {
  164. if (erroneousNames == null) {
  165. erroneousNames = new StringBuffer(name.toString());
  166. } else {
  167. erroneousNames.append(", " + name.toString());
  168. }
  169. }
  170. }
  171. // check all non-default excludes symbols
  172. for (Iterator iterator = exclude.iterator(); iterator.hasNext();) {
  173. ATSymbol name = (ATSymbol) iterator.next();
  174. if (!_DEFAULT_EXCLUDED_SLOTS_.contains(name) &&
  175. !sourceObject.meta_respondsTo(name).asNativeBoolean().javaValue) {
  176. if (erroneousNames == null) {
  177. erroneousNames = new StringBuffer(name.toString());
  178. } else {
  179. erroneousNames.append(", " + name.toString());
  180. }
  181. }
  182. }
  183. if (erroneousNames != null) {
  184. throw new XIllegalOperation("Undefined aliased or excluded slots during import: "+erroneousNames.toString());
  185. }
  186. ATObject hostObject = ctx.base_lexicalScope();
  187. // stores all conflicting symbols, initialized lazily
  188. Vector conflicts = null;
  189. // remember all the aliases I still have to define
  190. Hashtable aliasedNamesToDefine = (Hashtable) aliases.clone();
  191. // the alias to be used for defining the new fields or methods
  192. ATSymbol alias;
  193. // define the aliased fields
  194. ATField[] fields = NATObject.listTransitiveFields(sourceObject);
  195. for (int i = 0; i < fields.length; i++) {
  196. ATField field = fields[i];
  197. // skip excluded fields, such as the 'super' field
  198. if (!exclude.contains(field.base_name())) {
  199. // check whether the field needs to be aliased
  200. alias = (ATSymbol) aliases.get(field.base_name());
  201. if (alias == null) {
  202. // no alias, use the original name
  203. alias = field.base_name();
  204. } else {
  205. // processed an aliased field, remove name from the aliases left to process
  206. aliasedNamesToDefine.remove(field.base_name());
  207. }
  208. try {
  209. hostObject.meta_defineField(alias, field.base_readField());
  210. } catch(XDuplicateSlot e) {
  211. if (conflicts == null) {
  212. conflicts = new Vector(1);
  213. }
  214. conflicts.add(e.getSlotName());
  215. }
  216. }
  217. }
  218. // define the delegate methods
  219. ATMethod[] methods = NATObject.listTransitiveMethods(sourceObject);
  220. // if (methods.length > 0) {
  221. // create the lexical scope for the delegate method invocation by hand
  222. // NATCallframe delegateScope = new NATCallframe(hostObject);
  223. // add the parameter, it is used in the generated method
  224. // delegateScope.meta_defineField(_IMPORTED_OBJECT_NAME_, sourceObject);
  225. for (int i = 0; i < methods.length; i++) {
  226. ATSymbol origMethodName = methods[i].base_name();
  227. // filter out exluded methods
  228. if (exclude.contains(origMethodName)) {
  229. continue;
  230. }
  231. // automatically filter out methods tagged as @Required
  232. if (methods[i].meta_isTaggedAs(NativeTypeTags._REQUIRED_).asNativeBoolean().javaValue) {
  233. continue;
  234. }
  235. // check whether the method needs to be aliased
  236. alias = (ATSymbol) aliases.get(origMethodName);
  237. if (alias == null) {
  238. // no alias, use the original name
  239. alias = origMethodName;
  240. } else {
  241. // processed an aliased method, remove name from the aliases left to process
  242. // so that we don't have to manually add it later
  243. aliasedNamesToDefine.remove(origMethodName);
  244. }
  245. // def alias(@args) { importedObject^origMethodName(@args) }
  246. /* ATMethod delegate = new NATMethod(alias, Evaluator._ANON_MTH_ARGS_,
  247. new AGBegin(NATTable.of(
  248. //importedObject^origMethodName(@args)@[]
  249. new AGMessageSend(_IMPORTED_OBJECT_NAME_,
  250. new AGDelegationCreation(origMethodName,
  251. Evaluator._ANON_MTH_ARGS_, NATTable.EMPTY))))); */
  252. /*
  253. * Notice that the body of the delegate method is
  254. * sourceObject^selector@args)
  255. *
  256. * In order for this code to evaluate when the method is actually invoked
  257. * on the new host object, the symbol `sourceObject should evaluate to the
  258. * object contained in the variable sourceObject.
  259. *
  260. * To ensure this binding is correct at runtime, delegate methods are
  261. * added to objects as external methods whose lexical scope is the call
  262. * frame of this method invocation The delegate methods are not added as closures,
  263. * as a closure would fix the value of 'self' too early.
  264. *
  265. * When importing into a call frame, care must be taken that imported delegate
  266. * methods are added as closures, because call frames cannot contain methods.
  267. * In this case, the delegate is wrapped in a closure whose lexical scope is again
  268. * the call frame of this primitive method invocation. The value of self is fixed
  269. * to the current value, but this is OK given that the method is added to a call frame
  270. * which is 'selfless'.
  271. */
  272. DelegateMethod delegate = new DelegateMethod(alias, origMethodName, sourceObject);
  273. try {
  274. if (hostObject.isCallFrame()) {
  275. NATClosure clo = new NATClosure(delegate, ctx);
  276. hostObject.meta_defineField(alias, clo);
  277. } else {
  278. hostObject.meta_addMethod(delegate);
  279. }
  280. } catch(XDuplicateSlot e) {
  281. // check whether the added method equals the already present method
  282. // if they are equal, we do not need to raise a conflict
  283. if (!hostObject.meta_grabMethod(alias).equals(delegate)) {
  284. if (conflicts == null) {
  285. conflicts = new Vector(1);
  286. }
  287. conflicts.add(e.getSlotName());
  288. }
  289. }
  290. // } // end if
  291. }
  292. /*
  293. * Now, define delegate methods for all aliased names mentioned in the import statement
  294. * which were not added automatically because they were not present in either the
  295. * listTransitiveFields or listTransitiveMethods arrays. This may happen in situations such as:
  296. *
  297. * import T alias init := initFoo; // where T does not explicitly define 'init'
  298. *
  299. * In this case, listTransitiveMethods does not include 'init' (because it is inherited from nil)
  300. * so it will simply be skipped and no delegate will be generated for it.
  301. */
  302. if (!aliasedNamesToDefine.isEmpty()) {
  303. // not all aliases have been processed
  304. Set entries = aliasedNamesToDefine.entrySet();
  305. for (Iterator it = entries.iterator(); it.hasNext();) {
  306. Map.Entry entry = (Map.Entry) it.next();
  307. ATSymbol origName = (ATSymbol) entry.getKey();
  308. ATSymbol aliasedName = (ATSymbol) entry.getValue();
  309. DelegateMethod delegate = new DelegateMethod(aliasedName, origName, sourceObject);
  310. try {
  311. if (hostObject.isCallFrame()) {
  312. NATClosure clo = new NATClosure(delegate, ctx);
  313. hostObject.meta_defineField(aliasedName, clo);
  314. } else {
  315. hostObject.meta_addMethod(delegate);
  316. }
  317. } catch(XDuplicateSlot e) {
  318. // check whether the added method equals the already present method
  319. // if they are equal, we do not need to raise a conflict
  320. if (!hostObject.meta_grabMethod(aliasedName).equals(delegate)) {
  321. if (conflicts == null) {
  322. conflicts = new Vector(1);
  323. }
  324. conflicts.add(e.getSlotName());
  325. }
  326. }
  327. }
  328. }
  329. if (conflicts == null) {
  330. // no conflicts found
  331. return Evaluator.getNil();
  332. } else {
  333. throw new XImportConflict((ATSymbol[]) conflicts.toArray(new ATSymbol[conflicts.size()]));
  334. }
  335. }
  336. /**
  337. * A delegate-method is a pass-by-copy method.
  338. * This is allowed because the lexical scope of a delegate method only stores a reference
  339. * to the delegate object and further refers to the host object.
  340. *
  341. * The reason why delegate methods are pass-by-copy is that this allows isolates
  342. * to import other isolates without any problems: when an isolate is parameter-passed,
  343. * all of its delegate methods are passed by copy just like regular methods, which is
  344. * fine as long as the delegate object from which the methods were imported is also
  345. * a delegate.
  346. */
  347. public static final class DelegateMethod extends PrimitiveMethod {
  348. private final ATSymbol origMethodName_;
  349. private final ATObject delegate_;
  350. /**
  351. * Create a new delegatemethod:
  352. * <code>
  353. * def alias(@args) {
  354. * delegate^origMethodName(@args)
  355. * }
  356. * </code>
  357. */
  358. public DelegateMethod(ATSymbol alias, ATSymbol origMethodName, ATObject delegate) throws InterpreterException {
  359. super(alias, Evaluator._ANON_MTH_ARGS_);
  360. origMethodName_ = origMethodName;
  361. delegate_ = delegate;
  362. }
  363. public boolean isNativeDelegateMethod() { return true; }
  364. public DelegateMethod asNativeDelegateMethod() { return this; }
  365. public ATObject base_apply(ATTable args, ATContext ctx) throws InterpreterException {
  366. return delegate_.impl_invoke(ctx.base_receiver(), origMethodName_, args);
  367. }
  368. public ATBoolean base__opeql__opeql_(ATObject other) throws InterpreterException {
  369. if (other.isNativeDelegateMethod()) {
  370. DelegateMethod m = other.asNativeDelegateMethod();
  371. if (m.origMethodName_.equals(origMethodName_) && m.delegate_.equals(delegate_)) {
  372. return NATBoolean._TRUE_;
  373. } else {
  374. // if the names and/or delegate does not match, the methods may still be equal
  375. // after dereferencing one of the two delegates (since equality is defined
  376. // asymmetrically, we need to first check whether A == B and if that fails whether B == A)
  377. if (this.base__opeql__opeql_(m.delegate_.meta_grabMethod(m.origMethodName_)).asNativeBoolean().javaValue) {
  378. return NATBoolean._TRUE_;
  379. } else {
  380. return m.base__opeql__opeql_(delegate_.meta_grabMethod(origMethodName_));
  381. }
  382. }
  383. } else {
  384. return NATBoolean._FALSE_;
  385. }
  386. }
  387. public ATTable base_annotations() throws InterpreterException {
  388. return delegate_.meta_select(delegate_, origMethodName_).base_method().base_annotations();
  389. }
  390. public NATText meta_print() throws InterpreterException {
  391. return NATText.atValue("<delegate meth:"+super.base_name()+">");
  392. }
  393. }
  394. }