PageRenderTime 37ms CodeModel.GetById 14ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/at2dist130208/src/edu/vub/at/objects/mirrors/Reflection.java

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