PageRenderTime 32ms CodeModel.GetById 11ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/at2-build270707/src/edu/vub/at/objects/mirrors/Reflection.java

http://ambienttalk.googlecode.com/
Java | 470 lines | 206 code | 28 blank | 236 comment | 37 complexity | f581cf8848ca312c37918fc577ed6d73 MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * Reflection.java created on 10-aug-2006 at 16:19:17
  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.mirrors;
 29
 30import java.lang.reflect.Method;
 31import java.util.Vector;
 32
 33import edu.vub.at.exceptions.InterpreterException;
 34import edu.vub.at.exceptions.XArityMismatch;
 35import edu.vub.at.exceptions.XIllegalArgument;
 36import edu.vub.at.exceptions.XSelectorNotFound;
 37import edu.vub.at.exceptions.XUndefinedSlot;
 38import edu.vub.at.objects.ATField;
 39import edu.vub.at.objects.ATMethod;
 40import edu.vub.at.objects.ATObject;
 41import edu.vub.at.objects.ATTable;
 42import edu.vub.at.objects.grammar.ATSymbol;
 43import edu.vub.at.objects.natives.NATTable;
 44import edu.vub.at.objects.natives.grammar.AGSymbol;
 45import edu.vub.at.objects.symbiosis.Symbiosis;
 46import edu.vub.util.Matcher;
 47import edu.vub.util.Pattern;
 48
 49/**
 50 * Reflection is an auxiliary class meant to serve as a repository for methods
 51 * related to 'up' and 'down' Java values properly into and out of the AmbientTalk base level.
 52 * 
 53 * Keep the following mental picture in mind:
 54 * 
 55 *                ^        Java = implementation-level          |
 56 *  to deify      |        Java AT objects = meta-level         | to reify
 57 *  (= to up)     | ------------------------------------------  | (= to down)
 58 *  (= to absorb) |         AmbientTalk = base-level            v (= to reflect)
 59 * 
 60 * Although deification and reification are more accurate terms, we will use 'up' and 'down'
 61 * because they are the clearest terminology, and clarity matters.
 62 * 
 63 * In this class, the following conventions hold:
 64 *  - methods start with either 'up' or 'down', denoting whether they deify or reify something
 65 *  - arguments start with either 'j' or 'at', denoting whether they represent Java or AmbientTalk values
 66 *  With 'java values' is meant 'java objects representing mirrors'
 67 * 
 68 * @author tvc
 69 */
 70public final class Reflection {
 71	
 72	private static final String _BASE_PREFIX_ = "base_";
 73	private static final String _META_PREFIX_ = "meta_";
 74	
 75	/**
 76	 * A selector passed from the Java to the AmbientTalk level undergoes the following transformations:
 77	 * 
 78	 * - any pattern of the form _op{code}_ is transformed to a symbol corresponding to the operator code
 79	 *  Operator codes are:
 80	 *   pls -> +
 81	 *   mns -> -
 82	 *   tms -> *
 83	 *   div -> /
 84	 *   bsl -> \
 85	 *   not -> !
 86	 *   gtx -> >
 87	 *   ltx -> <
 88	 *   eql -> =
 89	 *   til -> ~
 90	 *   que -> ?
 91	 *   rem -> %
 92	 * - any underscores (_) are replaced by colons (:)
 93	 */
 94	public static final ATSymbol downSelector(String jSelector) {
 95		return AGSymbol.jAlloc(javaToAmbientTalkSelector(jSelector));
 96	}
 97	
 98	/**
 99	 * Transforms a Java selector prefixed with base_ into an AmbientTalk selector without the prefix.
100	 */
101	public static final ATSymbol downBaseLevelSelector(String jSelector) throws InterpreterException {
102		if (jSelector.startsWith(Reflection._BASE_PREFIX_)) {
103			return downSelector(stripPrefix(jSelector, Reflection._BASE_PREFIX_));
104		} else if (jSelector.startsWith(Reflection._META_PREFIX_)) {
105			return downSelector(stripPrefix(jSelector, Reflection._META_PREFIX_));
106		} else {
107			throw new XIllegalArgument("Illegal base level selector to down: " + jSelector);
108		}
109	}
110	
111	/**
112	 * Transforms a Java selector prefixed with meta_ into an AmbientTalk selector without the prefix.
113	 */
114	public static final ATSymbol downMetaLevelSelector(String jSelector) throws InterpreterException {
115		if (jSelector.startsWith(Reflection._META_PREFIX_)) {
116			return downSelector(stripPrefix(jSelector, Reflection._META_PREFIX_));
117		} else {
118			throw new XIllegalArgument("Illegal meta level selector to down: " + jSelector);
119		}
120	}
121	
122	/**
123	 * A selector passed from the AmbientTalk to the Java level undergoes the following transformations:
124	 * 
125	 * - any colons (:) are replaced by underscores (_)
126	 * - any operator symbol is replaced by _op{code}_ where code is generated as follows:
127	 *  Operator codes are:
128	 *   + -> pls
129	 *   - -> mns
130	 *   * -> tms
131	 *   / -> div
132	 *   \ -> bsl
133	 *   ! -> not
134	 *   > -> gtx
135	 *   < -> ltx
136	 *   = -> eql
137	 *   ~ -> til
138	 *   ? -> que
139	 *   % -> rem
140	 */
141	public static final String upSelector(ATSymbol atSelector) throws InterpreterException {
142		// : -> _
143		String nam = atSelector.base_text().asNativeText().javaValue;
144        // Backport from JDK 1.4 to 1.3
145		// nam = nam.replaceAll(":", "_");
146        nam = Pattern.compile(":").matcher(new StringBuffer(nam)).replaceAll("_");
147        
148		// operator symbol -> _op{code}_
149		Matcher m = symbol.matcher(new StringBuffer(nam));
150		StringBuffer sb = new StringBuffer();
151		while (m.find()) {
152			// find every occurence of a non-word character and convert it into a symbol
153			String oprCode = symbol2oprCode(m.group(0));
154			// only add the _op prefix and _ postfix if the code has been found...
155			m.appendReplacement(sb, (oprCode.length() == 3) ? "_op" + oprCode  + "_" : oprCode);
156		}
157		m.appendTail(sb);
158		return sb.toString();
159	}
160	
161	/**
162	 * Transforms an AmbientTalk selector into a Java-level selector prefixed with base_.
163	 */
164	public static final String upBaseLevelSelector(ATSymbol atSelector) throws InterpreterException {
165		return Reflection._BASE_PREFIX_ + upSelector(atSelector);
166	}
167
168	/**
169	 * Transforms an AmbientTalk selector into a Java-level selector prefixed with meta_.
170	 */
171	public static final String upMetaLevelSelector(ATSymbol atSelector) throws InterpreterException {
172		return Reflection._META_PREFIX_ + upSelector(atSelector);
173	}
174		
175	/**
176	 * Constructs an AmbientTalk ATMethod from a Java method.
177	 * Given an object obj and a String sel, it is checked whether obj has a method
178	 * named sel. If so, the corresponding Java method is wrapped in a NativeMethod.
179	 * If not, the downing fails.
180	 *
181	 * @param natObject the native AmbientTalk object in whose class the method should be found
182	 * @param jSelector a selector which should yield a method in natObject
183	 * @param origName the original AmbientTalk name of the method
184	 * @return a reified method wrapping the Java method
185	 * 
186	 * Example:
187	 *  eval "(reflect: tbl).getMethod('at')" where tbl is a NATTable
188	 *  => downMethod(aNATTable, "base_at")
189	 *  => NATTable must have a method named base_at
190	 *  
191	 * Callers should use the more specialised 'downBaseLevelMethod' and 'downMetaLevelMethod'
192	 * methods to specify the prefix of the method to be found
193	 */
194	public static final ATMethod downMethod(ATObject natObject, String jSelector, ATSymbol origName) throws InterpreterException {
195		return new NativeMethod(JavaInterfaceAdaptor.getNativeATMethod(natObject.getClass(), natObject, jSelector, origName),
196				                origName,
197				                natObject);
198	}
199	
200	public static final ATMethod downBaseLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
201		return downMethod(natObject, upBaseLevelSelector(atSelector), atSelector);
202	}
203	
204	public static final ATMethod downMetaLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
205		return downMethod(natObject, upMetaLevelSelector(atSelector), atSelector);
206	}
207
208	/**
209	 * downInvocation takes an implicit Java invocation and turns it into an explicit
210	 * AmbientTalk invocation process. This happens when Java code sends normal
211	 * Java messages to AmbientTalk objects (wrapped by a mirage).
212	 * 
213	 * @param atRcvr the AmbientTalk object having received the Java method invocation
214	 * @param jSelector the Java selector, to be converted to an AmbientTalk selector
215	 * @param jArgs the arguments to the Java method invocation (normally all args are ATObjects)
216	 * jArgs may be null, indicating that there are no arguments
217	 * @return the return value of the AmbientTalk method invoked via the java invocation.
218	 * 
219	 * Example:
220	 *  in Java: "tbl.base_at(1)" where tbl is an ATTable coercer wrapping aNATObject
221	 *  => downInvocation(aNATObject, "base_at", ATObject[] { ATNumber(1) })
222	 *  => aNATObject must implement a method named "at"
223	 *  
224	 * Depending on the prefix of the invoked Java method selector, the following translation should occur:
225	 *  - obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
226	 *  - obj.base_selector() => obj.meta_invokeField(obj, selector)
227	 *  - obj.meta_selector(args) => obj.meta_selector(args)
228	 *  - obj.selector(args) => either obj.selector(args) if selector is understood natively
229	 *                          or     obj.meta_invoke(obj, selector, args) otherwise
230	 *  - obj.selector() => obj.meta_invokeField(obj, selector)
231	 */
232	public static final ATObject downInvocation(ATObject atRcvr, Method jMethod, ATObject[] jArgs) throws InterpreterException {
233		String jSelector = jMethod.getName();
234		if (jArgs == null) { jArgs = NATTable.EMPTY.elements_; }
235		
236		if (jSelector.startsWith(Reflection._BASE_PREFIX_)) {
237			if (jArgs.length == 0) {
238				// obj.base_selector() => obj.meta_invokeField(obj, selector)
239				return atRcvr.meta_invokeField(atRcvr, downBaseLevelSelector(jSelector));
240			} else {
241				// obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
242				return atRcvr.meta_invoke(atRcvr, downBaseLevelSelector(jSelector), NATTable.atValue(jArgs));	
243			}
244		} else if (jSelector.startsWith(Reflection._META_PREFIX_)) {
245			// obj.meta_selector(args) => obj.meta_selector(args)
246			return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
247		} else {
248			// atRcvr can respond to the given method natively
249			if (jMethod.getDeclaringClass().isInstance(atRcvr)) {
250				return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
251			} else {
252				if (jArgs.length == 0) {
253				    // obj.selector() => obj.meta_invokeField(obj, selector)
254				    return atRcvr.meta_invokeField(atRcvr, downSelector(jSelector));
255				} else {
256				    // obj.selector(args) => obj.meta_invoke(obj, selector, args)
257				    return atRcvr.meta_invoke(atRcvr, downSelector(jSelector), NATTable.atValue(jArgs));	
258				}
259			}
260		}
261	}
262
263	/**
264	 * upInvocation takes an explicit AmbientTalk method invocation and turns it into an
265	 * implicitly performed Java invocation.
266	 * 
267	 * Depending on whether the AmbientTalk invocation happens at the base-level or the meta-level
268	 * (i.e. the receiver denotes a base-level object or a mirror), the jSelector parameter will have
269	 * a different prefix.
270	 * 
271	 * @param atOrigRcvr the original AmbientTalk object that received the invocation
272	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
273	 * @param atArgs the arguments to the AmbientTalk method invocation
274	 * @return the return value of the Java method invoked via the java invocation.
275	 * 
276	 * Example:
277	 *  eval "tbl.at(1)" where tbl is a NATTable
278	 *  => upInvocation(aNATTable, "base_at", ATObject[] { ATNumber(1) })
279	 *  => NATTable must have a method named base_at
280	 * 
281	 * Example:
282	 *  eval "(reflect: tbl).invoke(tbl, "at", [1])" where tbl is a NATTable
283	 *  => upInvocation(aNATTable, "meta_invoke", ATObject[] { aNATTable, ATSymbol('at'), ATTable([ATNumber(1)]) })
284	 *  => NATTable must have a method named meta_invoke
285	 */
286	public static final ATObject upInvocation(ATObject atOrigRcvr, String jSelector, ATSymbol atSelector, ATTable atArgs) throws InterpreterException {
287		return JavaInterfaceAdaptor.invokeNativeATMethod(
288				    atOrigRcvr.getClass(),
289				    atOrigRcvr,
290					jSelector,
291					atSelector, atArgs.asNativeTable().elements_);
292	}
293	
294	/**
295	 * upRespondsTo transforms an explicit AmbientTalk respondsTo meta-level request
296	 * into an implicit check whether the given jRcvr java object has a method
297	 * corresponding to the given selector, prefixed with base_
298	 * 
299	 * @param jRcvr the Java object being queried for a certain selector
300	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
301	 * @return a boolean indicating whether the jRcvr implements a method corresponding to base_ + atSelector
302	 * 
303	 * Example:
304	 *  eval "(reflect: [1,2,3]).respondsTo("at")" where the receiver of repondsTo is a NATTable
305	 *  => upRespondsTo(aNATTable, "at")
306	 *  => NATTable must have a method named base_at
307	 */
308	public static final boolean upRespondsTo(ATObject jRcvr,String jSelector) throws InterpreterException {
309		return JavaInterfaceAdaptor.hasApplicableJavaMethod(
310				jRcvr.getClass(),
311				jSelector);
312	}
313	
314	/**
315	 * upMethodSelection takes an explicit AmbientTalk field selection and checks whether 
316	 * a Java method exists that matches the selector. If so, this method is wrapped in a 
317	 * NativeClosure and returned.
318	 * 
319	 * @param atOrigRcvr the original AmbientTalk object that received the selection
320	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
321	 * @return a closure wrapping the method selected via the AmbientTalk selection.
322	 * 
323	 * Example:
324	 *  eval "[1,2,3].at"
325	 *  => upSelection(aNATTable, "at")
326	 *  => either NATTable must have a method base_at, which is then wrapped
327	 */
328	public static final NativeMethod upMethodSelection(ATObject atOrigRcvr, String jSelector, ATSymbol origSelector) throws InterpreterException {
329		Method m = JavaInterfaceAdaptor.getNativeATMethod(atOrigRcvr.getClass(), atOrigRcvr, jSelector, origSelector);
330		return new NativeMethod(m, origSelector, atOrigRcvr);
331	}
332	
333	/**
334	 * upInstanceCreation takes an explicit AmbientTalk 'new' invocation and turns it into an
335	 * implicit Java instance creation by calling a constructor. The initargs are upped as well
336	 * and are passed as arguments to the constructor.
337	 * 
338	 * @param jRcvr the Java object having received the call to new
339	 * @param atInitargs the arguments to the constructor
340	 * @return a new instance of a Java class
341	 * @throws InterpreterException
342	 */
343	public static final ATObject upInstanceCreation(ATObject jRcvr, ATTable atInitargs) throws InterpreterException {
344		ATObject[] args = atInitargs.asNativeTable().elements_;
345		return JavaInterfaceAdaptor.createNativeATObject(jRcvr.getClass(), args);
346	}
347	
348	public static final ATObject upExceptionCreation(InterpreterException jRcvr, ATTable atInitargs) throws InterpreterException {
349		ATObject[] args = atInitargs.asNativeTable().elements_;
350		return Symbiosis.symbioticInstanceCreation(jRcvr.getClass(), args);
351	}
352
353	/**
354	 * Pass an AmbientTalk meta-level object into the base-level
355	 */
356	public static final ATObject downObject(ATObject metaObject) throws InterpreterException {
357		return metaObject;
358		/*if (metaObject.meta_isTaggedAs(NativeTypeTags._MIRROR_).asNativeBoolean().javaValue) {
359			return metaObject.meta_select(metaObject, OBJMirrorRoot._BASE_NAME_);
360		} else {
361			return metaObject; // most native objects represent both the object at the base and at the meta-level
362		}*/
363	}
364	
365	/**
366	 * Pass an AmbientTalk base-level object to the meta-level
367	 */
368	public static final ATObject upObject(ATObject baseObject) {
369		if (baseObject instanceof NATMirage) {
370			return ((NATMirage) baseObject).getMirror();
371		} else {
372			return baseObject;
373		}
374	}
375	
376	/**
377	 * Returns, for a given AmbientTalk object atObj, an array of NativeMethod objects corresponding
378	 * to all non-static methods of that object's class, where each method's name is prefixed with 'base_'
379	 */
380	public static final ATMethod[] downBaseLevelMethods(ATObject atObj) throws InterpreterException {
381		Method[] allBaseMethods =
382			JavaInterfaceAdaptor.allMethodsPrefixed(atObj.getClass(), Reflection._BASE_PREFIX_, false);
383		Vector allATBaseMethods = new Vector();
384		for (int i = 0; i < allBaseMethods.length; i++) {
385			Method m = allBaseMethods[i];
386			String nam = m.getName();
387			allATBaseMethods.add(new NativeMethod(m, downBaseLevelSelector(nam), atObj));
388		}
389		return (ATMethod[]) allATBaseMethods.toArray(new ATMethod[allATBaseMethods.size()]);
390	}
391	
392	/**
393	 * Returns, for a given AmbientTalk object natObj, an array of NativeMethod objects corresponding
394	 * to all non-static methods of that object's class, where each method's name is prefixed with 'meta_'
395	 */
396	public static final ATMethod[] downMetaLevelMethods(ATObject natObj) throws InterpreterException {
397		Method[] allMetaMethods =
398			JavaInterfaceAdaptor.allMethodsPrefixed(natObj.getClass(), Reflection._META_PREFIX_, false);
399		Vector allATMetaMethods = new Vector();
400		for (int i = 0; i < allMetaMethods.length; i++) {
401			Method m = allMetaMethods[i];
402			String nam = m.getName();
403			allATMetaMethods.add(new NativeMethod(m, downMetaLevelSelector(nam), natObj));
404		}
405		return (ATMethod[]) allATMetaMethods.toArray(new ATMethod[allATMetaMethods.size()]);
406	}
407	
408	private static final Pattern oprCode = Pattern.compile("_op(\\w\\w\\w)_"); //'_op', 3 chars, '_'
409	private static final Pattern symbol = Pattern.compile("\\W"); //any non-word character
410	
411	private static String stripPrefix(String input, String prefix) {
412		// \A matches start of input
413	    // Backport from JDK 1.4 to 1.3
414        // return input.replaceFirst("\\A"+prefix, "");
415		return Pattern.compile("\\A"+prefix).matcher(new StringBuffer(input)).replaceFirst("");
416	}
417	
418	private static final String oprCode2Symbol(String code) {
419		switch (code.charAt(0)) {
420		  case 'p': if (code.equals("pls")) { return "+"; } else break;
421		  case 'm': if (code.equals("mns")) { return "-"; } else break;
422		  case 't': if (code.equals("tms")) { return "*"; } else
423			        if (code.equals("til")) { return "~"; } else break;
424		  case 'd': if (code.equals("div")) { return "/"; } else break;
425		  case 'b': if (code.equals("bsl")) { return "\\"; } else break;
426		  case 'n': if (code.equals("not")) { return "!"; } else break;
427		  case 'g': if (code.equals("gtx")) { return ">"; } else break;
428		  case 'l': if (code.equals("ltx")) { return "<"; } else break;
429		  case 'e': if (code.equals("eql")) { return "="; } else break;
430		  case 'q': if (code.equals("que")) { return "?"; } else break;
431		  case 'r': if (code.equals("rem")) { return "%"; } else break;
432		}
433		return "_op" + code + "_"; // no match, return original input
434	}
435	
436	private static final String symbol2oprCode(String symbol) {
437		switch (symbol.charAt(0)) {
438		  case '+': return "pls";
439		  case '-': return "mns";
440		  case '*': return "tms";
441		  case '/': return "div";
442		  case '\\': return "bsl";
443		  case '!': return "not";
444		  case '>': return "gtx";
445		  case '<': return "ltx";
446		  case '=': return "eql";
447		  case '~': return "til";
448		  case '?': return "que";
449		  case '%': return "rem";
450		  default: return symbol; // no match, return original input
451		}	
452	}
453	
454	private static final String javaToAmbientTalkSelector(String jSelector) {
455		// _op{code}_ -> operator symbol
456		Matcher m = oprCode.matcher(new StringBuffer(jSelector));
457		StringBuffer sb = new StringBuffer();
458		while (m.find()) {
459             // find every occurence of _op\w\w\w_ and convert it into a symbol
460			m.appendReplacement(sb, oprCode2Symbol(m.group(1)));
461		}
462		m.appendTail(sb);
463		
464		// _ -> :
465        // Backport from JDK 1.4 to 1.3
466		// return sb.toString().replaceAll("_", ":");
467        return Pattern.compile("_").matcher(sb).replaceAll(":");
468	}
469	
470}