PageRenderTime 35ms CodeModel.GetById 10ms app.highlight 19ms RepoModel.GetById 2ms app.codeStats 0ms

/interpreter/tags/at2-build190607/src/edu/vub/at/objects/symbiosis/JavaClass.java

http://ambienttalk.googlecode.com/
Java | 422 lines | 223 code | 39 blank | 160 comment | 23 complexity | f56d7fe115b2487e1155df777018b91b MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * JavaClass.java created on 3-nov-2006 at 10:54:38
  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.objects.symbiosis;
 29
 30import edu.vub.at.exceptions.InterpreterException;
 31import edu.vub.at.exceptions.XArityMismatch;
 32import edu.vub.at.exceptions.XDuplicateSlot;
 33import edu.vub.at.exceptions.XSelectorNotFound;
 34import edu.vub.at.exceptions.XTypeMismatch;
 35import edu.vub.at.exceptions.XUnassignableField;
 36import edu.vub.at.exceptions.XUndefinedSlot;
 37import edu.vub.at.objects.ATBoolean;
 38import edu.vub.at.objects.ATContext;
 39import edu.vub.at.objects.ATField;
 40import edu.vub.at.objects.ATMethod;
 41import edu.vub.at.objects.ATNil;
 42import edu.vub.at.objects.ATObject;
 43import edu.vub.at.objects.ATTypeTag;
 44import edu.vub.at.objects.ATTable;
 45import edu.vub.at.objects.coercion.NativeTypeTags;
 46import edu.vub.at.objects.grammar.ATSymbol;
 47import edu.vub.at.objects.mirrors.PrimitiveMethod;
 48import edu.vub.at.objects.mirrors.Reflection;
 49import edu.vub.at.objects.natives.NATBoolean;
 50import edu.vub.at.objects.natives.NATNil;
 51import edu.vub.at.objects.natives.NATNumber;
 52import edu.vub.at.objects.natives.NATObject;
 53import edu.vub.at.objects.natives.NATTable;
 54import edu.vub.at.objects.natives.NATText;
 55import edu.vub.at.objects.natives.grammar.AGSymbol;
 56import edu.vub.at.util.logging.Logging;
 57import edu.vub.util.IdentityHashMap;
 58
 59import java.lang.ref.SoftReference;
 60
 61/**
 62 * A JavaClass instance represents a Java Class under symbiosis.
 63 * 
 64 * Java classes are treated as AmbientTalk 'singleton' objects:
 65 * 
 66 *  - cloning a Java class results in the same Java class instance
 67 *  - sending 'new' to a Java class invokes the constructor and returns a new instance of the class under symbiosis
 68 *  - all static fields and methods of the Java class are reflected under symbiosis as fields and methods of the AT object
 69 *  
 70 * A Java Class object that represents an interface can furthermore be used
 71 * as an AmbientTalk type. The type's name corresponds to the interface's full name.
 72 *  
 73 * JavaClass instances are pooled (on a per-actor basis): there should exist only one JavaClass instance
 74 * for each Java class loaded into the JVM. Because the JVM ensures that a Java class
 75 * can only be loaded once, we can use the Java class wrapped by the JavaClass instance
 76 * as a unique key to identify its corresponding JavaClass instance.
 77 *  
 78 * @author tvcutsem
 79 */
 80public final class JavaClass extends NATObject implements ATTypeTag {
 81	
 82	/**
 83	 * A thread-local hashmap pooling all of the JavaClass wrappers for
 84	 * the current actor, referring to them using SOFT references, such
 85	 * that unused wrappers can be GC-ed when running low on memory.
 86	 */
 87	private static final ThreadLocal _JAVACLASS_POOL_ = new ThreadLocal() {
 88        protected synchronized Object initialValue() {
 89            return new IdentityHashMap();
 90        }
 91	};
 92	
 93	public static final JavaClass wrapperFor(Class c) {
 94		IdentityHashMap map = (IdentityHashMap) _JAVACLASS_POOL_.get();
 95		if (map.containsKey(c)) {
 96			SoftReference ref = (SoftReference) map.get(c);
 97			JavaClass cls = (JavaClass) ref.get();
 98			if (cls != null) {
 99				return cls;
100			} else {
101				map.remove(c);
102				cls = new JavaClass(c);
103				map.put(c, new SoftReference(cls));
104				return cls;
105			}
106		} else {
107			JavaClass jc = new JavaClass(c);
108			map.put(c, new SoftReference(jc));
109			return jc;
110		}
111	}
112	
113	// primitive fields and method of a JavaClass wrapper
114	
115	private static final AGSymbol _PTS_NAME_ = AGSymbol.jAlloc("parentTypes");
116	private static final AGSymbol _TNM_NAME_ = AGSymbol.jAlloc("typeName");
117	
118	/** def isSubtypeOf(type) { nil } */
119	private static final PrimitiveMethod _PRIM_STP_ = new PrimitiveMethod(
120			AGSymbol.jAlloc("isSubtypeOf"), NATTable.atValue(new ATObject[] { AGSymbol.jAlloc("type")})) {
121		public ATObject base_apply(ATTable arguments, ATContext ctx) throws InterpreterException {
122			if (!arguments.base_getLength().equals(NATNumber.ONE)) {
123				throw new XArityMismatch("isSubtypeOf", 1, arguments.base_getLength().asNativeNumber().javaValue);
124			}
125			return ctx.base_getLexicalScope().asJavaClassUnderSymbiosis().base_isSubtypeOf(arguments.base_at(NATNumber.ONE).asTypeTag());
126		}
127	};
128	
129	private final Class wrappedClass_;
130	
131	/**
132	 * A JavaClass wrapping a class c is an object that has the lexical scope as its lexical parent
133	 * and has NIL as its dynamic parent.
134	 * 
135	 * If the JavaClass wraps a Java interface type, JavaClass instances are
136	 * also types.
137	 */
138	private JavaClass(Class wrappedClass) {
139		super(wrappedClass.isInterface() ?
140			  new ATTypeTag[] { NativeTypeTags._TYPETAG_ } :
141			  NATObject._NO_TYPETAGS_);
142		wrappedClass_ = wrappedClass;
143		
144		// add the two fields and one method needed for an ATTypeTag
145		if (wrappedClass.isInterface()) {
146			Class[] extendedInterfaces = wrappedClass_.getInterfaces();
147			ATObject[] types = new ATObject[extendedInterfaces.length];
148			for (int i = 0; i < extendedInterfaces.length; i++) {
149				types[i] = JavaClass.wrapperFor(extendedInterfaces[i]);
150			}
151			
152			try {
153				super.meta_defineField(_PTS_NAME_, NATTable.atValue(types));
154				super.meta_defineField(_TNM_NAME_, AGSymbol.jAlloc(wrappedClass_.getName()));
155				super.meta_addMethod(_PRIM_STP_);
156			} catch (InterpreterException e) {
157				Logging.Actor_LOG.fatal("Error while initializing Java Class as type tag: " + wrappedClass.getName(), e);
158			}
159		}
160	}
161	
162	public Class getWrappedClass() { return wrappedClass_; }
163	
164	public JavaClass asJavaClassUnderSymbiosis() throws XTypeMismatch { return this; }
165	
166    public ATBoolean base__opeql__opeql_(ATObject comparand) throws InterpreterException {
167        return NATBoolean.atValue(this.equals(comparand));
168    }
169	
170	public boolean equals(Object other) {
171		return ((other instanceof JavaClass) &&
172				(wrappedClass_.equals(((JavaClass) other).wrappedClass_)));
173	}
174	
175    /* ------------------------------------------------------
176     * - Symbiotic implementation of the ATObject interface -
177     * ------------------------------------------------------ */
178    
179    /**
180     * When a method is invoked upon a symbiotic Java class object, the underlying static Java method
181     * with the same name as the AmbientTalk selector is invoked. Its arguments are converted
182     * into their Java equivalents. Conversely, the result of the method invocation is converted
183     * into an AmbientTalk object.
184     */
185    public ATObject meta_invoke(ATObject receiver, ATSymbol atSelector, ATTable arguments) throws InterpreterException {
186        try {
187			String jSelector = Reflection.upSelector(atSelector);
188			return Symbiosis.symbioticInvocation(
189					this, null, wrappedClass_, jSelector, arguments.asNativeTable().elements_);
190		} catch (XSelectorNotFound e) {
191			e.catchOnlyIfSelectorEquals(atSelector);
192    	    return super.meta_invoke(receiver, atSelector, arguments);
193		}
194    }
195    
196    /**
197     * A symbiotic Java class object responds to all of the public static selectors of its Java class
198     * plus all of the per-instance selectors added to its AmbientTalk symbiont.
199     */
200    public ATBoolean meta_respondsTo(ATSymbol atSelector) throws InterpreterException {
201    	String jSelector = Reflection.upSelector(atSelector);
202    	if (Symbiosis.hasMethod(wrappedClass_, jSelector, true) ||
203    	    Symbiosis.hasField(wrappedClass_, jSelector, true)) {
204    		return NATBoolean._TRUE_;
205    	} else {
206    		return super.meta_respondsTo(atSelector);
207    	}
208    }
209    
210    /**
211     * When selecting a field from a symbiotic Java class object, if the object's class
212     * has a static field with a matching selector, it is automatically read;
213     * if it has methods corresponding to the selector, they are returned in a JavaMethod wrapper,
214     * otherwise, the fields of its AT symbiont are checked.
215     */
216    public ATObject meta_select(ATObject receiver, ATSymbol selector) throws InterpreterException {
217    	String jSelector = Reflection.upSelector(selector);
218    	try {
219   			return Symbiosis.readField(null, wrappedClass_, jSelector);
220    	} catch(XUndefinedSlot e) {
221       	    JavaMethod choices = Symbiosis.getMethods(wrappedClass_, jSelector, true);
222       	    if (choices != null) {
223       	     	return new JavaClosure(this, choices);
224       	    } else {
225       	    	return super.meta_select(receiver, selector);
226       	    }
227    	}
228    }
229    
230    /**
231     * A variable lookup is resolved by first checking whether the Java object has an appropriate static
232     * field with a matching name. If so, that field's contents are returned. If not, the AT symbiont's
233     * fields are checked.
234     */
235    public ATObject meta_lookup(ATSymbol selector) throws InterpreterException {
236        try {
237        	String jSelector = Reflection.upSelector(selector);
238      	    return Symbiosis.readField(null, wrappedClass_, jSelector);
239        } catch(XUndefinedSlot e) {
240        	return super.meta_lookup(selector);  
241        }
242    }
243    
244    /**
245     * Fields can be defined within a symbiotic Java class object. They are added
246     * to its AmbientTalk symbiont, but only if they do not clash with already
247     * existing field names.
248     */
249    public ATNil meta_defineField(ATSymbol name, ATObject value) throws InterpreterException {
250        if (Symbiosis.hasField(wrappedClass_, Reflection.upSelector(name), true)) {
251    	    throw new XDuplicateSlot(name);
252        } else {
253    	    return super.meta_defineField(name, value);
254        }
255    }
256    
257    /**
258     * Variables can be assigned within a symbiotic Java class object if that class object
259     * has a mutable static field with a matching name. Variable assignment is first
260     * resolved in the Java object and afterwards in the AT symbiont.
261     */
262    public ATNil meta_assignVariable(ATSymbol name, ATObject value) throws InterpreterException {
263        try {
264        	String jSelector = Reflection.upSelector(name);
265        	Symbiosis.writeField(null, wrappedClass_, jSelector, value);
266        	return NATNil._INSTANCE_;
267		} catch (XUnassignableField e) {
268			return super.meta_assignVariable(name, value);
269		}
270    }
271    
272    /**
273     * Fields can be assigned within a symbiotic Java class object if that class
274     * has a mutable field with a matching name. Field assignment is first resolved
275     * in the Java object and afterwards in the AT symbiont.
276     */
277    public ATNil meta_assignField(ATObject receiver, ATSymbol name, ATObject value) throws InterpreterException {
278        try {
279     	    String jSelector = Reflection.upSelector(name);
280    	    Symbiosis.writeField(null, wrappedClass_, jSelector, value);
281    	    return NATNil._INSTANCE_;	
282		} catch (XUnassignableField e) {
283			return super.meta_assignField(receiver, name, value);
284		}
285    }
286    
287	/**
288	 * Symbiotic Java class objects are singletons.
289	 */
290	public ATObject meta_clone() throws InterpreterException { return this; }
291	
292	/**
293	 * aJavaClass.new(@args) == invoke a Java constructor
294	 * AmbientTalk objects can add a custom new method to the class in order to intercept
295	 * instance creation. The original instance can then be performed by invoking the old new(@args).
296	 * 
297	 * For example, imagine we want to extend the class java.lang.Point with a 3D coordinate, e.g. a 'z' field:
298	 * <tt>
299	 * def Point := jlobby.java.awt.Point;
300	 * def oldnew := Point.new;
301	 * def Point.new(x,y,z) { // 'override' the new method
302	 *   def point := oldnew(x,y); // invokes the Java constructor
303	 *   def point.z := z; // adds a field dynamically to the new JavaObject wrapper
304	 *   point; // important! new should return the newly created instance
305	 * }
306	 * def mypoint := Point.new(1,2,3);
307	 * </tt>
308	 */
309    public ATObject meta_newInstance(ATTable initargs) throws InterpreterException {
310    	return Symbiosis.symbioticInstanceCreation(wrappedClass_, initargs.asNativeTable().elements_);
311    }
312    
313    /**
314     * Methods can be added to a symbiotic Java class object provided they do not already
315     * exist in the Java class.
316     */
317    public ATNil meta_addMethod(ATMethod method) throws InterpreterException {
318        ATSymbol name = method.base_getName();
319        if (Symbiosis.hasMethod(wrappedClass_, Reflection.upSelector(name), true)) {
320    	    throw new XDuplicateSlot(name);
321        } else {
322    	    return super.meta_addMethod(method);
323        }
324    }
325
326    /**
327     * Fields can be grabbed from a symbiotic Java class object. Fields that correspond
328     * to static fields in the Java class are returned as JavaField instances.
329     */
330    public ATField meta_grabField(ATSymbol fieldName) throws InterpreterException {
331        try {
332        	return new JavaField(null,
333        			             Symbiosis.getField(wrappedClass_, Reflection.upSelector(fieldName), true));
334        } catch(XUndefinedSlot e) {
335        	return super.meta_grabField(fieldName);
336        }
337    }
338
339    /**
340     * Methods can be grabbed from a symbiotic Java class object. Methods that correspond
341     * to static methods in the Java class are returned as JavaMethod instances.
342     */
343    public ATMethod meta_grabMethod(ATSymbol methodName) throws InterpreterException {
344        JavaMethod choices = Symbiosis.getMethods(wrappedClass_, Reflection.upSelector(methodName), true);
345        if (choices != null) {
346        	return choices;
347        } else {
348        	return super.meta_grabMethod(methodName);
349        }
350    }
351
352    /**
353     * Querying a symbiotic Java class object for its fields results in a table containing
354     * both 'native' static Java fields and the fields of its AT symbiont
355     */
356    public ATTable meta_listFields() throws InterpreterException {
357		// instance fields of the wrapped object's class
358		JavaField[] jFields = Symbiosis.getAllFields(null, wrappedClass_);
359        // fields of the AT symbiont
360    	ATObject[] symbiontFields = super.meta_listFields().asNativeTable().elements_;
361    	return NATTable.atValue(NATTable.collate(jFields, symbiontFields));
362    }
363
364    /**
365     * Querying a symbiotic Java class object for its methods results in a table containing
366     * both 'native' static Java methods and the methods of its AT symbiont
367     */
368    public ATTable meta_listMethods() throws InterpreterException {
369		// instance methods of the wrapped object's class
370		JavaMethod[] jMethods = Symbiosis.getAllMethods(wrappedClass_, true);
371        // methods of the AT symbiont
372		ATObject[] symbiontMethods = super.meta_listMethods().asNativeTable().elements_;
373		return NATTable.atValue(NATTable.collate(jMethods, symbiontMethods));
374    }
375
376	public ATBoolean meta_isCloneOf(ATObject original) throws InterpreterException {
377		return NATBoolean.atValue(this == original);
378	}
379	
380	public NATText meta_print() throws InterpreterException {
381		return NATText.atValue("<java:"+wrappedClass_.toString()+">");
382	}
383
384	/**
385     * A Java Class object remains unique within an actor.
386     */
387    public ATObject meta_resolve() throws InterpreterException {
388    	return wrapperFor(wrappedClass_);
389    }
390    
391    /* ========================
392     * == ATTypeTag Interface ==
393     * ======================== */
394    
395    /**
396     * If this class represents an interface type, parentTypes
397     * are wrappers for all interfaces extended by this Java interface type
398     */
399	public ATTable base_getSuperTypes() throws InterpreterException {
400		return super.meta_select(this, _PTS_NAME_).asTable();
401	}
402
403	public ATSymbol base_getTypeName() throws InterpreterException {
404		return super.meta_select(this, _TNM_NAME_).asSymbol();
405	}
406
407	/**
408	 * A Java interface type used as a type can only be a subtype of another
409	 * Java interface type used as a type, and only if this type is assignable
410	 * to the other type.
411	 */
412	public ATBoolean base_isSubtypeOf(ATTypeTag other) throws InterpreterException {
413		if (other instanceof JavaClass) {
414			JavaClass otherClass = (JavaClass) other;
415			// wrappedClass <: otherClass <=> otherClass >= wrappedClass
416			return NATBoolean.atValue(otherClass.wrappedClass_.isAssignableFrom(wrappedClass_));
417		} else {
418			return NATBoolean._FALSE_;
419		}
420	}
421	
422}