PageRenderTime 52ms CodeModel.GetById 1ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

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