PageRenderTime 40ms CodeModel.GetById 16ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

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