PageRenderTime 60ms CodeModel.GetById 31ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

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

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