PageRenderTime 53ms CodeModel.GetById 18ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 0ms

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

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