PageRenderTime 69ms CodeModel.GetById 13ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://ambienttalk.googlecode.com/
Java | 478 lines | 214 code | 29 blank | 235 comment | 36 complexity | 2dc536a6aa09fdec1683de0e5916d71c 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 edu.vub.at.exceptions.InterpreterException;
 31import edu.vub.at.exceptions.XIllegalArgument;
 32import edu.vub.at.objects.ATMethod;
 33import edu.vub.at.objects.ATObject;
 34import edu.vub.at.objects.ATTable;
 35import edu.vub.at.objects.grammar.ATSymbol;
 36import edu.vub.at.objects.natives.NATTable;
 37import edu.vub.at.objects.natives.grammar.AGSymbol;
 38import edu.vub.at.objects.symbiosis.JavaConstructor;
 39import edu.vub.at.objects.symbiosis.Symbiosis;
 40import edu.vub.at.util.logging.Logging;
 41import edu.vub.util.Regexp;
 42
 43import java.lang.reflect.Method;
 44import java.util.Vector;
 45
 46import org.apache.regexp.RE;
 47import org.apache.regexp.REProgram;
 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 = new RE(colon).subst(atSelector.base_text().asNativeText().javaValue, "_");
144
145		// operator symbol -> _op{code}_
146		// find every occurence of a non-word character and convert it into a symbol
147        return Regexp.replaceAll(new RE(symbol), nam, new Regexp.StringCallable() {
148        	public String call(String match) {
149        		String oprCode = symbol2oprCode(match);
150    			// only add the _op prefix and _ postfix if the code has been found...
151        		return (oprCode.length() == 3) ? "_op" + oprCode  + "_" : oprCode;
152        	}
153        });
154	}
155	
156	/**
157	 * Transforms an AmbientTalk selector into a Java-level selector prefixed with base_.
158	 */
159	public static final String upBaseLevelSelector(ATSymbol atSelector) throws InterpreterException {
160		return Reflection._BASE_PREFIX_ + upSelector(atSelector);
161	}
162
163	/**
164	 * Transforms an AmbientTalk selector into a Java-level selector prefixed with meta_.
165	 */
166	public static final String upMetaLevelSelector(ATSymbol atSelector) throws InterpreterException {
167		return Reflection._META_PREFIX_ + upSelector(atSelector);
168	}
169		
170	/**
171	 * Constructs an AmbientTalk ATMethod from a Java method.
172	 * Given an object obj and a String sel, it is checked whether obj has a method
173	 * named sel. If so, the corresponding Java method is wrapped in a NativeMethod.
174	 * If not, the downing fails.
175	 *
176	 * @param natObject the native AmbientTalk object in whose class the method should be found
177	 * @param jSelector a selector which should yield a method in natObject
178	 * @param origName the original AmbientTalk name of the method
179	 * @return a reified method wrapping the Java method
180	 * 
181	 * Example:
182	 *  eval "(reflect: tbl).getMethod('at')" where tbl is a NATTable
183	 *  => downMethod(aNATTable, "base_at")
184	 *  => NATTable must have a method named base_at
185	 *  
186	 * Callers should use the more specialised 'downBaseLevelMethod' and 'downMetaLevelMethod'
187	 * methods to specify the prefix of the method to be found
188	 */
189	public static final ATMethod downMethod(ATObject natObject, String jSelector, ATSymbol origName) throws InterpreterException {
190		return new NativeMethod(JavaInterfaceAdaptor.getNativeATMethod(natObject.getClass(), natObject, jSelector, origName),
191				                origName,
192				                natObject);
193	}
194	
195	public static final ATMethod downBaseLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
196		return downMethod(natObject, upBaseLevelSelector(atSelector), atSelector);
197	}
198	
199	public static final ATMethod downMetaLevelMethod(ATObject natObject, ATSymbol atSelector) throws InterpreterException {
200		return downMethod(natObject, upMetaLevelSelector(atSelector), atSelector);
201	}
202
203	/**
204	 * downInvocation takes an implicit Java invocation and turns it into an explicit
205	 * AmbientTalk invocation process. This happens when Java code sends normal
206	 * Java messages to AmbientTalk objects (wrapped by a mirage).
207	 * 
208	 * @param atRcvr the AmbientTalk object having received the Java method invocation
209	 * @param jSelector the Java selector, to be converted to an AmbientTalk selector
210	 * @param jArgs the arguments to the Java method invocation (normally all args are ATObjects)
211	 * jArgs may be null, indicating that there are no arguments
212	 * @return the return value of the AmbientTalk method invoked via the java invocation.
213	 * 
214	 * Example:
215	 *  in Java: "tbl.base_at(1)" where tbl is an ATTable coercer wrapping aNATObject
216	 *  => downInvocation(aNATObject, "base_at", ATObject[] { ATNumber(1) })
217	 *  => aNATObject must implement a method named "at"
218	 *  
219	 * Depending on the prefix of the invoked Java method selector, the following translation should occur:
220	 *  - obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
221	 *  - obj.base_selector() => obj.meta_invokeField(obj, selector)
222	 *  - obj.meta_selector(args) => obj.meta_selector(args)
223	 *  - obj.selector(args) => either obj.selector(args) if selector is understood natively
224	 *                          or     obj.meta_invoke(obj, selector, args) otherwise
225	 *  - obj.selector() => obj.meta_invokeField(obj, selector)
226	 */
227	public static final ATObject downInvocation(ATObject atRcvr, Method jMethod, ATObject[] jArgs) throws InterpreterException {
228		String jSelector = jMethod.getName();
229		if (jArgs == null) { jArgs = NATTable.EMPTY.elements_; }
230		
231		if (jSelector.startsWith(Reflection._BASE_PREFIX_)) {
232			if (jArgs.length == 0) {
233				// obj.base_selector() => obj.meta_invokeField(obj, selector)
234				return atRcvr.meta_invokeField(atRcvr, downBaseLevelSelector(jSelector));
235			} else {
236				// obj.base_selector(args) => obj.meta_invoke(obj, selector, args)
237				return atRcvr.impl_invoke(atRcvr, downBaseLevelSelector(jSelector), NATTable.atValue(jArgs));	
238			}
239		} else if (jSelector.startsWith(Reflection._META_PREFIX_)) {
240			// obj.meta_selector(args) => obj.meta_selector(args)
241			return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
242		} else {
243			// atRcvr can respond to the given method natively
244			if (jMethod.getDeclaringClass().isInstance(atRcvr)) {
245				return JavaInterfaceAdaptor.invokeNativeATMethod(jMethod, atRcvr, jArgs);
246			} else {
247				if (jArgs.length == 0) {
248				    // obj.selector() => obj.meta_invokeField(obj, selector)
249				    return atRcvr.meta_invokeField(atRcvr, downSelector(jSelector));
250				} else {
251				    // obj.selector(args) => obj.meta_invoke(obj, selector, args)
252				    return atRcvr.impl_invoke(atRcvr, downSelector(jSelector), NATTable.atValue(jArgs));	
253				}
254			}
255		}
256	}
257
258	/**
259	 * upInvocation takes an explicit AmbientTalk method invocation and turns it into an
260	 * implicitly performed Java invocation.
261	 * 
262	 * Depending on whether the AmbientTalk invocation happens at the base-level or the meta-level
263	 * (i.e. the receiver denotes a base-level object or a mirror), the jSelector parameter will have
264	 * a different prefix.
265	 * 
266	 * @param atOrigRcvr the original AmbientTalk object that received the invocation
267	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
268	 * @param atArgs the arguments to the AmbientTalk method invocation
269	 * @return the return value of the Java method invoked via the java invocation.
270	 * 
271	 * Example:
272	 *  eval "tbl.at(1)" where tbl is a NATTable
273	 *  => upInvocation(aNATTable, "base_at", ATObject[] { ATNumber(1) })
274	 *  => NATTable must have a method named base_at
275	 * 
276	 * Example:
277	 *  eval "(reflect: tbl).invoke(tbl, "at", [1])" where tbl is a NATTable
278	 *  => upInvocation(aNATTable, "meta_invoke", ATObject[] { aNATTable, ATSymbol('at'), ATTable([ATNumber(1)]) })
279	 *  => NATTable must have a method named meta_invoke
280	 */
281	public static final ATObject upInvocation(ATObject atOrigRcvr, String jSelector, ATSymbol atSelector, ATTable atArgs) throws InterpreterException {
282		return JavaInterfaceAdaptor.invokeNativeATMethod(
283				    atOrigRcvr.getClass(),
284				    atOrigRcvr,
285					jSelector,
286					atSelector, atArgs.asNativeTable().elements_);
287	}
288	
289	/**
290	 * upRespondsTo transforms an explicit AmbientTalk respondsTo meta-level request
291	 * into an implicit check whether the given jRcvr java object has a method
292	 * corresponding to the given selector, prefixed with base_
293	 * 
294	 * @param jRcvr the Java object being queried for a certain selector
295	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
296	 * @return a boolean indicating whether the jRcvr implements a method corresponding to base_ + atSelector
297	 * 
298	 * Example:
299	 *  eval "(reflect: [1,2,3]).respondsTo("at")" where the receiver of repondsTo is a NATTable
300	 *  => upRespondsTo(aNATTable, "at")
301	 *  => NATTable must have a method named base_at
302	 */
303	public static final boolean upRespondsTo(ATObject jRcvr,String jSelector) throws InterpreterException {
304		return JavaInterfaceAdaptor.hasApplicableJavaMethod(
305				jRcvr.getClass(),
306				jSelector);
307	}
308	
309	/**
310	 * upMethodSelection takes an explicit AmbientTalk field selection and checks whether 
311	 * a Java method exists that matches the selector. If so, this method is wrapped in a 
312	 * NativeClosure and returned.
313	 * 
314	 * @param atOrigRcvr the original AmbientTalk object that received the selection
315	 * @param jSelector the selector of the message to be invoked, converted to a Java selector
316	 * @return a closure wrapping the method selected via the AmbientTalk selection.
317	 * 
318	 * Example:
319	 *  eval "[1,2,3].at"
320	 *  => upSelection(aNATTable, "at")
321	 *  => either NATTable must have a method base_at, which is then wrapped
322	 */
323	public static final NativeMethod upMethodSelection(ATObject atOrigRcvr, String jSelector, ATSymbol origSelector) throws InterpreterException {
324		Method m = JavaInterfaceAdaptor.getNativeATMethod(atOrigRcvr.getClass(), atOrigRcvr, jSelector, origSelector);
325		return new NativeMethod(m, origSelector, atOrigRcvr);
326	}
327	
328	/**
329	 * upInstanceCreation takes an explicit AmbientTalk 'new' invocation and turns it into an
330	 * implicit Java instance creation by calling a constructor. The initargs are upped as well
331	 * and are passed as arguments to the constructor.
332	 * 
333	 * @param jRcvr the Java object having received the call to new
334	 * @param atInitargs the arguments to the constructor
335	 * @return a new instance of a Java class
336	 * @throws InterpreterException
337	 */
338	public static final ATObject upInstanceCreation(ATObject jRcvr, ATTable atInitargs) throws InterpreterException {
339		ATObject[] args = atInitargs.asNativeTable().elements_;
340		return JavaInterfaceAdaptor.createNativeATObject(jRcvr.getClass(), args);
341	}
342	
343	public static final ATObject upExceptionCreation(InterpreterException jRcvr, ATTable atInitargs) throws InterpreterException {
344		ATObject[] args = atInitargs.asNativeTable().elements_;
345		return Symbiosis.symbioticInstanceCreation(new JavaConstructor(jRcvr.getClass()), args);
346	}
347
348	/**
349	 * Pass an AmbientTalk meta-level object into the base-level
350	 */
351	public static final ATObject downObject(ATObject metaObject) throws InterpreterException {
352		return metaObject;
353		/*if (metaObject.meta_isTaggedAs(NativeTypeTags._MIRROR_).asNativeBoolean().javaValue) {
354			return metaObject.meta_select(metaObject, OBJMirrorRoot._BASE_NAME_);
355		} else {
356			return metaObject; // most native objects represent both the object at the base and at the meta-level
357		}*/
358	}
359	
360	/**
361	 * Pass an AmbientTalk base-level object to the meta-level
362	 */
363	public static final ATObject upObject(ATObject baseObject) {
364		if (baseObject instanceof NATMirage) {
365			return ((NATMirage) baseObject).getMirror();
366		} else {
367			return baseObject;
368		}
369	}
370	
371	/**
372	 * Returns, for a given AmbientTalk object atObj, an array of NativeMethod objects corresponding
373	 * to all non-static methods of that object's class, where each method's name is prefixed with 'base_'
374	 */
375	public static final ATMethod[] downBaseLevelMethods(ATObject atObj) throws InterpreterException {
376		Method[] allBaseMethods =
377			JavaInterfaceAdaptor.allMethodsPrefixed(atObj.getClass(), Reflection._BASE_PREFIX_, false);
378		Vector allATBaseMethods = new Vector();
379		for (int i = 0; i < allBaseMethods.length; i++) {
380			Method m = allBaseMethods[i];
381			String nam = m.getName();
382			allATBaseMethods.add(new NativeMethod(m, downBaseLevelSelector(nam), atObj));
383		}
384		return (ATMethod[]) allATBaseMethods.toArray(new ATMethod[allATBaseMethods.size()]);
385	}
386	
387	/**
388	 * Returns, for a given AmbientTalk object natObj, an array of NativeMethod objects corresponding
389	 * to all non-static methods of that object's class, where each method's name is prefixed with 'meta_'
390	 */
391	public static final ATMethod[] downMetaLevelMethods(ATObject natObj) throws InterpreterException {
392		Method[] allMetaMethods =
393			JavaInterfaceAdaptor.allMethodsPrefixed(natObj.getClass(), Reflection._META_PREFIX_, false);
394		Vector allATMetaMethods = new Vector();
395		for (int i = 0; i < allMetaMethods.length; i++) {
396			Method m = allMetaMethods[i];
397			String nam = m.getName();
398			allATMetaMethods.add(new NativeMethod(m, downMetaLevelSelector(nam), natObj));
399		}
400		return (ATMethod[]) allATMetaMethods.toArray(new ATMethod[allATMetaMethods.size()]);
401	}
402	
403	private static final REProgram oprCode = Regexp.compile("_op(\\w\\w\\w)_"); //'_op', 3 chars, '_'
404	private static final REProgram symbol = Regexp.compile("\\W"); //any non-word character
405	private static final REProgram underScore = Regexp.compile("_");
406	private static final REProgram colon = Regexp.compile(":");
407	
408	private static String stripPrefix(String input, String prefix) {
409		// ^ matches start of input
410	    // Backport from JDK 1.4 to 1.3
411        // return input.replaceFirst("\\A"+prefix, "");
412		return new RE(Regexp.compile("^"+prefix)).subst(input, "", RE.REPLACE_FIRSTONLY);
413		// return Pattern.compile("\\A"+prefix).matcher(new StringBuffer(input)).replaceFirst("");
414	}
415	
416	private static final String oprCode2Symbol(String code) {
417		switch (code.charAt(0)) {
418		  case 'p': if (code.equals("pls")) { return "+"; } else break;
419		  case 'm': if (code.equals("mns")) { return "-"; } else break;
420		  case 't': if (code.equals("tms")) { return "*"; } else
421			        if (code.equals("til")) { return "~"; } else break;
422		  case 'd': if (code.equals("div")) { return "/"; } else break;
423		  case 'b': if (code.equals("bsl")) { return "\\"; } else break;
424		  case 'n': if (code.equals("not")) { return "!"; } else break;
425		  case 'g': if (code.equals("gtx")) { return ">"; } else break;
426		  case 'l': if (code.equals("ltx")) { return "<"; } else break;
427		  case 'e': if (code.equals("eql")) { return "="; } else break;
428		  case 'q': if (code.equals("que")) { return "?"; } else break;
429		  case 'r': if (code.equals("rem")) { return "%"; } else break;
430		}
431		return "_op" + code + "_"; // no match, return original input
432	}
433	
434	private static final String symbol2oprCode(String symbol) {
435		switch (symbol.charAt(0)) {
436		  case '+': return "pls";
437		  case '-': return "mns";
438		  case '*': return "tms";
439		  case '/': return "div";
440		  case '\\': return "bsl";
441		  case '!': return "not";
442		  case '>': return "gtx";
443		  case '<': return "ltx";
444		  case '=': return "eql";
445		  case '~': return "til";
446		  case '?': return "que";
447		  case '%': return "rem";
448		  default: return symbol; // no match, return original input
449		}	
450	}
451	
452	private static final String javaToAmbientTalkSelector(String jSelector) {
453        // find every occurence of _op\w\w\w_ and convert it into a symbol
454		try {
455			final RE codePattern = new RE(oprCode);
456			
457			String oprcodesReplaced = Regexp.replaceAll(codePattern, jSelector, new Regexp.StringCallable() {
458				public String call(String match) {
459					// match == _op{code}_
460					
461					// CAREFUL: we are mutating the same RE object used by the replaceAll function
462					// itself! This is normally not a problem if we do not depend on the codePattern's
463					// stateful methods
464					codePattern.match(match);
465					
466					// _op{code}_ -> operator symbol
467					return oprCode2Symbol(codePattern.getParen(1));
468				}
469			});
470	        // finally, replace all "_" by ":"
471	        return new RE(underScore).subst(oprcodesReplaced,":");
472		} catch (InterpreterException e) { // all this to make compiler happy
473			Logging.VirtualMachine_LOG.fatal("unexpected exception: " + e.getMessage(), e);
474			throw new RuntimeException("Unexpected exception: " + e.getMessage());
475		}
476	}
477	
478}