001: /* Copyright 2007 David N. Welton - DedaSys LLC - http://www.dedasys.com
002:
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014: */
015:
016: package org.hecl.java;
017:
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.Field;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022: import java.lang.reflect.InvocationTargetException;
023:
024: import java.util.Enumeration;
025: import java.util.Hashtable;
026: import java.util.Vector;
027:
028: import org.hecl.DoubleThing;
029: import org.hecl.HeclException;
030: import org.hecl.IntThing;
031: import org.hecl.ListThing;
032: import org.hecl.LongThing;
033: import org.hecl.ObjectThing;
034: import org.hecl.Thing;
035:
036: /**
037: * The <code>Reflector</code> class maps between Java types and Hecl
038: * types in order to make it possible to call Java methods from Hecl.
039: *
040: * @author <a href="mailto:davidw@dedasys.com">David N. Welton</a>
041: * @version 1.0
042: */
043: public class Reflector {
044: private Class forclass;
045: private Method[] methods;
046: private Hashtable methodnames = null;
047: private Hashtable constnames;
048: private Hashtable fieldnames;
049:
050: /**
051: * Creates a new <code>Reflector</code> instance.
052: *
053: * @param classname a <code>String</code> value describing the full name
054: * (including package) of a Java class.
055: * @exception HeclException if an error occurs
056: */
057: public Reflector(String classname) throws HeclException {
058: try {
059: forclass = Class.forName(classname);
060: methods = forclass.getMethods();
061: constnames = new Hashtable();
062: fieldnames = new Hashtable();
063:
064: for (Field f : forclass.getFields()) {
065: int mod = f.getModifiers();
066: StringBuffer msg = new StringBuffer("");
067: String name = f.getName();
068: fieldnames.put(name.toLowerCase(), f);
069: if (Modifier.isPublic(mod) && Modifier.isFinal(mod)
070: && Modifier.isStatic(mod)) {
071: Class type = f.getType();
072: if (type == boolean.class) {
073: constnames.put(name, IntThing.create(f
074: .getBoolean(forclass)));
075: } else if (type == double.class) {
076: constnames.put(name, DoubleThing.create(f
077: .getDouble(forclass)));
078: } else if (type == float.class) {
079: constnames.put(name, DoubleThing.create(f
080: .getFloat(forclass)));
081: } else if (type == int.class) {
082: constnames.put(name, IntThing.create(f
083: .getInt(forclass)));
084: } else if (type == long.class) {
085: constnames.put(name, LongThing.create(f
086: .getLong(forclass)));
087: } else if (type == String.class) {
088: constnames.put(name, new Thing((String) f
089: .get(forclass)));
090: } else {
091: constnames.put(name, ObjectThing.create(f
092: .get(forclass)));
093: }
094: }
095: }
096: } catch (Exception e) {
097: throw new HeclException(e.toString());
098: }
099: }
100:
101: /**
102: * The <code>fillMethods</code> method is called in a "lazy" way
103: * to fill in the method hash table. This makes startup time a
104: * lot faster - these are only filled in the first time they're
105: * needed.
106: *
107: */
108: private void fillMethods() {
109: methodnames = new Hashtable();
110: /* We could also do getDeclaredMethods and do the
111: * "subclassing" some other way. */
112: for (Method m : methods) {
113: Vector<Method> v = null;
114: String name = m.getName().toLowerCase();
115: if (methodnames.containsKey(name)) {
116: v = (Vector) methodnames.get(name);
117: } else {
118: v = new Vector<Method>();
119: }
120: v.add(m);
121: methodnames.put(name, v);
122: }
123: }
124:
125: /**
126: * The <code>instantiate</code> method is called to create an
127: * instance of a class (new, in other words).
128: *
129: * @param argv a <code>Thing</code> value that is mapped onto Java
130: * parameters and passed to the appropriate constructor for the
131: * class.
132: * @return a <code>Thing</code> value
133: * @exception HeclException if an error occurs
134: */
135: public Thing instantiate(Thing[] argv) throws HeclException {
136:
137: Object[] args = new Object[0];
138: Constructor selected = null;
139: try {
140: Constructor[] constructors = forclass.getConstructors();
141:
142: if (constructors == null) {
143: throw new HeclException(forclass.toString()
144: + " has no constructors!");
145: }
146:
147: for (Constructor c : constructors) {
148: Class[] javaparams = c.getParameterTypes();
149:
150: if (javaparams.length != argv.length) {
151: continue;
152: }
153:
154: args = mapParams(javaparams, argv, 0);
155: if (args != null) {
156: selected = c;
157: break;
158: }
159: }
160: if (selected == null) {
161: throw new HeclException(
162: "Couldn't find a constructor for class:"
163: + forclass.getName());
164: }
165: return ObjectThing.create(selected.newInstance(args));
166: } catch (InvocationTargetException e) {
167: String msg = "Problem invoking " + forclass.getName()
168: + " constructor " + selected.getName()
169: + " with arguments: ";
170: for (Thing t : argv) {
171: msg += t.toString() + " ";
172: }
173: msg += " (Translated to:) ";
174: for (Object eo : args) {
175: msg += eo.toString() + " ";
176: }
177: msg += " " + e.getTargetException().toString();
178: throw new HeclException(msg);
179: } catch (Exception e) {
180: throw new HeclException("Reflector instantiate error :"
181: + e.toString());
182: }
183: }
184:
185: /**
186: * The <code>getField</code> method returns the value of an
187: * instance's field.
188: *
189: * @param target an <code>Object</code> value
190: * @param name a <code>String</code> value
191: * @return a <code>Thing</code> value
192: * @exception HeclException if an error occurs
193: */
194: public Thing getField(Object target, String name)
195: throws HeclException {
196: Thing retval = null;
197: Field f = (Field) fieldnames.get(name.toLowerCase());
198: if (f == null) {
199: throw new HeclException("No field matches " + name
200: + " for class " + forclass.getName() + " "
201: + fieldnames.toString());
202: }
203:
204: try {
205: Class type = f.getType();
206: if (type == boolean.class) {
207: retval = IntThing.create(f.getBoolean(target));
208: } else if (type == double.class) {
209: retval = DoubleThing.create(f.getDouble(target));
210: } else if (type == float.class) {
211: retval = DoubleThing.create(f.getFloat(target));
212: } else if (type == int.class) {
213: retval = IntThing.create(f.getInt(target));
214: } else if (type == long.class) {
215: retval = LongThing.create(f.getLong(target));
216: } else if (type == String.class) {
217: retval = new Thing((String) f.get(target));
218: } else {
219: retval = ObjectThing.create(f.get(target));
220: }
221: } catch (Exception e) {
222: e.printStackTrace();
223: throw new HeclException("Problem fetching field " + name
224: + " : " + e.toString());
225: }
226: return retval;
227: }
228:
229: /**
230: * <code>getConstField</code> fetches a constant field value.
231: *
232: * @param name a <code>String</code> value
233: * @return a <code>Thing</code> value
234: * @exception HeclException if an error occurs
235: */
236: public Thing getConstField(String name) throws HeclException {
237:
238: Thing result = (Thing) constnames.get(name);
239: if (result == null) {
240: throw new HeclException("No field '" + name + "'");
241: }
242: return result;
243: }
244:
245: /**
246: * The <code>evaluate</code> method takes a target object to
247: * operate on, a methodname, and some Hecl values, and attempts to
248: * find and call a Java method with the supplied values.
249: *
250: * @param o an <code>Object</code> value
251: * @param cmd a <code>String</code> value
252: * @param argv a <code>Thing</code> value
253: * @return a <code>Thing</code> value
254: * @exception HeclException if an error occurs
255: */
256: public Thing evaluate(Object o, String cmd, Thing[] argv)
257: throws HeclException {
258:
259: Object[] args = new Object[0];
260: Method selected = null;
261:
262: if (methodnames == null) {
263: fillMethods();
264: }
265:
266: try {
267: Vector<Method> v = ((Vector) methodnames.get(cmd
268: .toLowerCase()));
269:
270: if (v == null) {
271: throw new HeclException("Method " + cmd
272: + " not found for class" + forclass.toString());
273: }
274:
275: Method[] methods = v.toArray(new Method[v.size()]);
276: /* Match the signatures with the correct number first. */
277: Class[] javaparams = null;
278: for (Method m : methods) {
279: javaparams = m.getParameterTypes();
280: if (javaparams.length != argv.length - 2) {
281: continue;
282: }
283:
284: args = mapParams(javaparams, argv, 2);
285: if (args != null) {
286: selected = m;
287: break;
288: }
289: }
290: if (selected == null) {
291: String msg = "No method matched " + cmd + " for class "
292: + forclass.getName()
293: + " last javaparams tried: ";
294: if (javaparams != null) {
295: for (Class c : javaparams) {
296: msg += c.getSimpleName() + " ";
297: }
298: }
299: throw new HeclException(msg);
300: }
301: Object retval = selected.invoke(o, args);
302: return mapRetval(selected, retval);
303: } catch (InvocationTargetException e) {
304: String msg = "Problem invoking " + forclass.getName() + " "
305: + cmd + "/" + selected.getName()
306: + " with arguments: ";
307: for (Thing t : argv) {
308: msg += t.toString() + " ";
309: }
310: msg += " (Translated to:) ";
311: for (Object eo : args) {
312: msg += eo.toString() + " ";
313: }
314: msg += " " + e.getTargetException().toString();
315: throw new HeclException(msg);
316: } catch (Exception e) {
317: throw new HeclException("Reflector evaluate error :"
318: + e.toString());
319: }
320: }
321:
322: /**
323: * The <code>mapParams</code> method is where the magic happens -
324: * it maps Hecl types/values onto Java types/values.
325: *
326: * @param outparams a <code>Class</code> value
327: * @param argv a <code>Thing</code> value
328: * @param offset an <code>int</code> value - where to start
329: * looking in argv.
330: * @return an <code>Object[]</code> value
331: * @exception HeclException if an error occurs
332: */
333: protected Object[] mapParams(Class[] outparams, Thing[] argv,
334: int offset) throws HeclException {
335:
336: if (outparams.length != argv.length - offset) {
337: /* No match */
338: return null;
339: }
340:
341: Object[] outobjs = new Object[outparams.length];
342: Class c = null;
343: for (int i = 0; i < outparams.length; i++) {
344: Thing inparam = argv[i + offset];
345: Class outparam = outparams[i];
346: String javaclassname = outparam.getSimpleName();
347:
348: /* Tweak inparam according to the constant table we've
349: * been passed. */
350: String val = inparam.toString();
351: if (constnames.containsKey(val)) {
352: inparam = (Thing) constnames.get(val);
353: }
354: String heclparmt = inparam.getVal().thingclass();
355:
356: if (outparam == boolean.class || outparam == Boolean.class) {
357: if (heclparmt.equals("int")) {
358: outobjs[i] = IntThing.get(inparam) != 0;
359: } else {
360: outobjs = null;
361: }
362: } else if (outparam == int.class
363: || outparam == Integer.class) {
364: if (heclparmt.equals("int")) {
365: outobjs[i] = IntThing.get(inparam);
366: } else {
367: outobjs = null;
368: }
369: } else if (outparam == long.class) {
370: if (heclparmt.equals("long")) {
371: outobjs[i] = LongThing.get(inparam);
372: } else {
373: outobjs = null;
374: }
375: } else if (outparam == CharSequence.class
376: || outparam == String.class) {
377: if (heclparmt.equals("string")) {
378: outobjs[i] = inparam.toString();
379: } else {
380: outobjs = null;
381: }
382: } else if (javaclassname.equals("int[]")) {
383: Vector v = ListThing.get(inparam);
384: int[] ints = new int[v.size()];
385: for (int j = 0; j < v.size(); j++) {
386: ints[j] = IntThing.get((Thing) v.elementAt(j));
387: }
388: outobjs[i] = ints;
389: } else if (javaclassname.equals("Object[]")) {
390: Thing[] things = ListThing.getArray(inparam);
391: Object[] objects = new Object[things.length];
392: int j = 0;
393: for (Thing t : things) {
394: /* This is a bit of a hack. If they're objects,
395: * move them on through as objects, otherwise, as
396: * strings. */
397: if (t.getVal().thingclass().equals("object")) {
398: objects[j] = ObjectThing.get(t);
399: } else {
400: objects[j] = t.toString();
401: }
402: j++;
403: }
404: outobjs[i] = objects;
405: } else if (outparam == Thing.class) {
406: /* We can use this to pass Things around directly, to
407: * classes that support it. */
408: outobjs[i] = inparam;
409: } else if (heclparmt.equals("object")) {
410: /* We are getting an ObjectThing from Hecl... */
411: outobjs[i] = ObjectThing.get(inparam);
412: } else if (outparam == Object.class) {
413: /* We're not getting an ObjectThing from Hecl, but
414: * Java can take any Object. Give it Things directly.
415: * This is sort of a last resort as more specific is
416: * better. */
417: outobjs[i] = inparam;
418: } else {
419: /* No match, return null. */
420: outobjs = null;
421: }
422: }
423: return outobjs;
424: }
425:
426: /**
427: * The <code>mapRetval</code> method is the "opposite" of the
428: * mapParams method - it maps a returned Java value onto a Hecl
429: * Thing, which it then returns.
430: *
431: * @param m a <code>Method</code> value
432: * @param o an <code>Object</code> value
433: * @return a <code>Thing</code> value
434: */
435: private Thing mapRetval(Method m, Object o) {
436: Class rtype = m.getReturnType();
437: String rtypename = rtype.getSimpleName();
438: if (o == null) {
439: return null;
440: } else if (rtype == void.class) {
441: return null;
442: } else if (rtype == int.class) {
443: return IntThing.create(((Integer) o).intValue());
444: } else if (rtype == boolean.class) {
445: boolean val = ((Boolean) o).equals(Boolean.TRUE);
446: return IntThing.create(val ? 1 : 0);
447: } else if (rtype == long.class) {
448: return LongThing.create(((Long) o).longValue());
449: } else if (rtype == String.class || rtype == CharSequence.class) {
450: return new Thing((String) o);
451: } else if (rtypename.equals("String[]")) {
452: Vector v = new Vector();
453: String[] retval = (String[]) o;
454: for (String s : retval) {
455: v.add(new Thing(s));
456: }
457: return ListThing.create(v);
458: } else if (rtypename.equals("int[]")) {
459: Vector v = new Vector();
460: int[] retval = (int[]) o;
461: for (int i : retval) {
462: v.add(IntThing.create(i));
463: }
464: return ListThing.create(v);
465: } else if (rtype == Object.class) {
466: if (o.getClass() == Thing.class) {
467: /* If we've managed to stash a thing somewhere. */
468: return (Thing) o;
469: }
470: }
471: return ObjectThing.create(o);
472: }
473:
474: /**
475: * The <code>methods</code> method returns a Hecl list of method
476: * signatures in the form methodName type type type.
477: *
478: * @return a <code>Thing</code> value
479: * @exception HeclException if an error occurs
480: */
481: public Thing methods() throws HeclException {
482: Vector retval = new Vector();
483:
484: if (methodnames == null) {
485: fillMethods();
486: }
487:
488: for (Enumeration e = methodnames.keys(); e.hasMoreElements();) {
489: Vector signature = new Vector();
490: String key = (String) e.nextElement();
491: signature.add(new Thing(key));
492: Vector<Method> v = (Vector) methodnames.get(key);
493: Method[] methods = v.toArray(new Method[v.size()]);
494: Class[] javaparams = null;
495: for (Method m : methods) {
496: javaparams = m.getParameterTypes();
497: for (Class c : javaparams) {
498: signature.add(new Thing(c.getSimpleName()));
499: }
500: }
501: retval.add(ListThing.create(signature));
502: }
503: return ListThing.create(retval);
504:
505: }
506: }
|