001: /*=============================================================================
002: * Copyright Texas Instruments 2000-2003. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2 of the License, or (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * $ProjectHeader: OSCRIPT 0.155 Fri, 20 Dec 2002 18:34:22 -0800 rclark $
019: */
020:
021: package oscript.data;
022:
023: import oscript.exceptions.*;
024: import oscript.classwrap.ClassWrapGen;
025: import oscript.util.StackFrame;
026: import oscript.util.MemberTable;
027:
028: import java.lang.reflect.*;
029: import java.util.LinkedList;
030: import java.util.Iterator;
031:
032: /**
033: * Utilities to convert between script and java types.
034: *
035: * @author Rob Clark (rob@ti.com)
036: * <!--$Format: " * @version $Revision$"$-->
037: * @version 1.29
038: */
039: public class JavaBridge {
040: private static LinkedList functionTransformList = new LinkedList();
041:
042: /*=======================================================================*/
043: /**
044: * This abstract class is implemented by transformers that understand how
045: * to transform a script object (function) to a certain type of java class.
046: * For example, this can be used to register a tranformer that can
047: * make a wrapper that implements Runnable, or ActionListener. This way
048: * script code can simply pass a script function to java code that
049: * expects to take a, for example, ActionListener.
050: */
051: public static abstract class FunctionTransformer {
052: private Class targetClass;
053:
054: /**
055: * Class Constructor
056: *
057: * @param targetClass the class to tranfrom script object to
058: */
059: public FunctionTransformer(Class targetClass) {
060: this .targetClass = targetClass;
061: }
062:
063: /**
064: * Get the type of the class that this tranformer understands how
065: * to transform to.
066: */
067: public Class getTargetClass() {
068: return targetClass;
069: }
070:
071: /**
072: * Perform the transform, and return a java object that is an
073: * instance of the class returned by {@link #getTargetClass}.
074: */
075: public abstract Object transform(Value fxn);
076: }
077:
078: /**
079: */
080: public static void registerFunctionTransformer(
081: FunctionTransformer ft) {
082: functionTransformList.add(ft);
083: }
084:
085: /* These should be split out somewhere else... but for now...
086: */
087: static {
088:
089: registerFunctionTransformer(new FunctionTransformer(
090: Runnable.class) {
091:
092: public Object transform(final Value fxn) {
093: return new Runnable() {
094:
095: public void run() {
096: fxn.callAsFunction(new Value[0]);
097: }
098:
099: };
100: }
101:
102: });
103:
104: registerFunctionTransformer(new FunctionTransformer(
105: java.awt.event.ActionListener.class) {
106:
107: public Object transform(final Value fxn) {
108: return new java.awt.event.ActionListener() {
109:
110: public void actionPerformed(
111: java.awt.event.ActionEvent evt) {
112: fxn.callAsFunction(new Value[] { JavaBridge
113: .convertToScriptObject(evt) });
114: }
115:
116: };
117: }
118:
119: });
120: }
121:
122: /*
123: * A better idea for converting args:
124: *
125: * if( parameterTypes[i].isArray() )
126: * {
127: * ... handle converting array
128: * }
129: * else if( parameterTypes[i].isPrimitive() )
130: * {
131: * ... handle primitive conversion:
132: * Long -> long, int, short, byte
133: * Double -> double, float
134: * Boolean -> boolean
135: * String -> char
136: * }
137: * else if( parameterTypes[i].isAssignableFrom(args[i].getClass()) )
138: * {
139: * ...
140: * }
141: * else if( parameterTypes[i].isAssignableFrom(args[i].castToJavaObject().getClass()) )
142: * {
143: * ...
144: * }
145: * else
146: * {
147: * for( Iterator itr=functionTransformList.iterator(); itr.hasNext(); )
148: * {
149: * if( parameterTypes[i].isAssignableFrom( itr.next().getTargetClass() ) )
150: * {
151: * ...
152: * return/break/???
153: * }
154: * }
155: *
156: * ... can't convert
157: * }
158: *
159: */
160:
161: /*=======================================================================*/
162: /**
163: * This is used by java class wrappers to convert the return type back
164: * to a java type:
165: */
166: public static Object convertToJavaObject(Value scriptObj,
167: String javaTypeStr) {
168: try {
169: return convertToJavaObject(scriptObj, JavaClassWrapper
170: .forName(javaTypeStr));
171: } catch (ClassNotFoundException e) {
172: e.printStackTrace();
173: throw new ProgrammingErrorException("class not found: "
174: + e.getMessage());
175: }
176: }
177:
178: public static Object convertToJavaObject(Value scriptObj, Class cls) {
179: Object[] javaArgs = new Object[1];
180:
181: if (convertArgs(new Class[] { cls }, javaArgs, new OArray(
182: new Value[] { scriptObj })) > 0) {
183: // conversion possible
184: return javaArgs[0];
185: }
186:
187: // conversion not possible:
188: throw PackagedScriptObjectException
189: .makeExceptionWrapper(new OUnsupportedOperationException(
190: "cannot convert to: " + cls.getName()));
191: }
192:
193: /*=======================================================================*/
194:
195: /**
196: * Abstracts {@link Method} and {@link Constructor} differences
197: */
198: public interface JavaCallableAccessor {
199: Class[] getParameterTypes(Object javaCallable);
200:
201: Object call(Object javaCallable, Object javaObject,
202: Object[] args) throws InvocationTargetException,
203: InstantiationException, IllegalAccessException;
204: }
205:
206: /**
207: * Since choosing the correct method to call, and correct constructor to
208: * call, uses the same algorithm, instead of duplicating the logic in two
209: * places, it is handled by this method. Having it in one place also
210: * makes it easier to explore optimizations in the future.
211: *
212: * @param accessor
213: * @param id the symbol (name) of the method/constructor
214: * @param javaObject the java object, to pass to {@link JavaCallableAccessor#call}
215: * @param javaCallables the candidate methods/constructors
216: * @param sf the current stack frame
217: * @param args the args
218: * @return the return value of {@link JavaCallableAccessor#call}
219: */
220: public static final Object call(JavaCallableAccessor accessor,
221: int id, Object javaObject, Object[] javaCallables,
222: StackFrame sf, MemberTable args) {
223: int alen = (args == null) ? 0 : args.length();
224: Object bestCallable = null;
225: int bestJavaArgsScore = 0;
226: Object[] bestJavaArgs = null;
227: Object[] javaArgs = null;
228:
229: // we can only use the tmpJavaArgArrays optimization once, otherwise we'll
230: // overwrite the candidate bestJavaArgs... but I think only having one
231: // constructor to choose from is the common case:
232: boolean canUseTmpJavaArgArrays = true;
233:
234: for (int i = 0; i < javaCallables.length; i++) {
235: Class[] parameterTypes = accessor
236: .getParameterTypes(javaCallables[i]);
237:
238: if (parameterTypes.length == alen) {
239: if (javaArgs == null)
240: javaArgs = new Object[alen];
241:
242: int javaArgsScore = JavaBridge.convertArgs(
243: parameterTypes, javaArgs, args);
244:
245: if (javaArgsScore > bestJavaArgsScore) {
246: bestJavaArgs = javaArgs;
247: bestJavaArgsScore = javaArgsScore;
248: bestCallable = javaCallables[i];
249:
250: canUseTmpJavaArgArrays = false;
251:
252: javaArgs = null;
253: }
254: }
255: }
256:
257: if (bestCallable != null) {
258: try {
259: return accessor.call(bestCallable, javaObject,
260: bestJavaArgs);
261: } catch (InvocationTargetException e) {
262: Throwable t = e.getTargetException();
263:
264: if (Value.DEBUG)
265: t.printStackTrace();
266:
267: throw OJavaException.makeJavaExceptionWrapper(t);
268: } catch (Throwable e) // XXX
269: {
270: if (Value.DEBUG)
271: e.printStackTrace();
272:
273: throw OJavaException.makeJavaExceptionWrapper(e);
274: }
275: } else {
276: /* if we get here, we didn't find a callable with the
277: * correct number of args:
278: */
279: LinkedList candidateList = new LinkedList();
280:
281: for (int i = 0; i < javaCallables.length; i++) {
282: Class[] parameterTypes = accessor
283: .getParameterTypes(javaCallables[i]);
284:
285: if (parameterTypes.length == alen)
286: candidateList.add(parameterTypes);
287: }
288:
289: Value name = Symbol.getSymbol(id);
290:
291: if (candidateList.size() == 0) {
292: throw PackagedScriptObjectException
293: .makeExceptionWrapper(new OIllegalArgumentException(
294: "wrong number of args!"));
295: } else {
296: for (int i = 0; i < args.length(); i++)
297: System.err.println(i + ": "
298: + args.referenceAt(i).getType() + ", "
299: + args.referenceAt(i));
300: String msg = "wrong arg types! Possible candidates:\n";
301: for (Iterator itr = candidateList.iterator(); itr
302: .hasNext();) {
303: Class[] parameterTypes = (Class[]) (itr.next());
304: msg += " " + name.castToString() + "(";
305:
306: for (int i = 0; i < parameterTypes.length; i++) {
307: if (i != 0)
308: msg += ", ";
309: msg += parameterTypes[i].getName();
310: }
311:
312: msg += ") ("
313: + JavaBridge.convertArgs(parameterTypes,
314: new Object[alen], args) + ")\n";
315: }
316:
317: throw PackagedScriptObjectException
318: .makeExceptionWrapper(new OIllegalArgumentException(
319: msg));
320: }
321: }
322: }
323:
324: /*=======================================================================*/
325: /**
326: * Utility to convert args to javaArgs of the types specified by
327: * parameterTypes. Each array should be of the same length. This
328: * will return a score of the conversion. A score of less than or
329: * equal to zero indicates that the conversion is not possible. A
330: * higher score is better.
331: */
332: public static int convertArgs(Class[] parameterTypes,
333: Object[] javaArgs, MemberTable args) {
334: int score = Integer.MAX_VALUE;
335: if ((args == null) || (args.length() == 0))
336: return score;
337:
338: int argslength = args.length();
339:
340: if ((javaArgs.length != argslength)
341: || (parameterTypes.length < argslength))
342: throw new ProgrammingErrorException("bad monkey, no banana");
343:
344: for (int i = 0; (i < argslength) && (score > 0); i++) {
345: // in case it is a reference:
346: Value arg = args.referenceAt(i).unhand();
347:
348: if (((arg == Value.NULL) || (arg == Value.UNDEFINED))
349: && !(parameterTypes[i].isPrimitive() || Value.class
350: .isAssignableFrom(parameterTypes[i]))) {
351: // null can be assigned to any non-primitive
352: javaArgs[i] = null;
353: } else if (parameterTypes[i].isArray()) {
354: try {
355: int len = arg.length();
356: Class componentType = parameterTypes[i]
357: .getComponentType();
358:
359: if (arg instanceof OString) {
360: if (componentType == Character.TYPE) {
361: // we want methods that take a String to be preferred over
362: // methods that take a char[]
363: score--;
364: javaArgs[i] = arg.castToString()
365: .toCharArray();
366: } else {
367: // don't support converting a string to any other sort of array:
368: return 0;
369: }
370: } else if ((arg instanceof OArray.OJavaArray)
371: && compatibleJavaArray(componentType, arg
372: .castToJavaObject())) {
373: javaArgs[i] = arg.castToJavaObject();
374: } else if (len > 0) {
375: Class[] arrParameterTypes = new Class[len];
376: Value[] arrArgs = new Value[len];
377:
378: arrParameterTypes[0] = componentType;
379: arrArgs[0] = arg.elementAt(OExactNumber
380: .makeExactNumber(0));
381: for (int j = 1; j < len; j++) {
382: arrParameterTypes[j] = arrParameterTypes[0];
383: arrArgs[j] = arg.elementAt(OExactNumber
384: .makeExactNumber(j));
385: }
386:
387: // primitive types need to be handled specially...
388: if (arrParameterTypes[0].isPrimitive()) {
389: // convert into temporary array:
390: Object[] tmpArr = new Object[len];
391: score -= Integer.MAX_VALUE
392: - convertArgs(arrParameterTypes,
393: tmpArr, new OArray(arrArgs));
394:
395: if (score <= 0)
396: return score;
397:
398: // now copy to final destination:
399: javaArgs[i] = Array.newInstance(
400: arrParameterTypes[0], len);
401:
402: for (int j = 0; j < len; j++)
403: Array.set(javaArgs[i], j, tmpArr[j]);
404: } else {
405: Object[] arrJavaArgs = (Object[]) (Array
406: .newInstance(arrParameterTypes[0],
407: len));
408: score -= Integer.MAX_VALUE
409: - convertArgs(arrParameterTypes,
410: arrJavaArgs, new OArray(
411: arrArgs));
412: javaArgs[i] = arrJavaArgs;
413: }
414: } else {
415: score--;
416: javaArgs[i] = Array
417: .newInstance(parameterTypes[i]
418: .getComponentType(), 0);
419: }
420: } catch (PackagedScriptObjectException e) {
421: return 0;
422: }
423: } else if (parameterTypes[i].isPrimitive()) {
424: if (parameterTypes[i] == Boolean.TYPE) {
425: try {
426: javaArgs[i] = arg.castToBoolean() ? Boolean.TRUE
427: : Boolean.FALSE;
428: } catch (PackagedScriptObjectException e) {
429: return 0;
430: }
431: } else if (parameterTypes[i] == Character.TYPE) {
432: try {
433: String str = arg.castToString();
434:
435: if ((str != null) && (str.length() == 1))
436: javaArgs[i] = new Character(str.charAt(0));
437: else
438: return 0;
439: } catch (PackagedScriptObjectException e) {
440: return 0;
441: }
442: } else if (parameterTypes[i] == Byte.TYPE) {
443: try {
444: long val = arg.castToExactNumber();
445:
446: if ((long) ((byte) val) != val)
447: return 0;
448:
449: if (!arg.bopInstanceOf(OExactNumber.TYPE)
450: .castToBoolean())
451: score--;
452:
453: javaArgs[i] = new Byte((byte) val);
454: } catch (PackagedScriptObjectException e) {
455: return 0;
456: }
457: } else if (parameterTypes[i] == Short.TYPE) {
458: try {
459: long val = arg.castToExactNumber();
460:
461: if ((long) ((short) val) != val)
462: return 0;
463:
464: if (!arg.bopInstanceOf(OExactNumber.TYPE)
465: .castToBoolean())
466: score--;
467:
468: javaArgs[i] = new Short((short) val);
469: } catch (PackagedScriptObjectException e) {
470: return 0;
471: }
472: } else if (parameterTypes[i] == Integer.TYPE) {
473: try {
474: long val = arg.castToExactNumber();
475:
476: if ((long) ((int) val) != val)
477: return 0;
478:
479: if (!arg.bopInstanceOf(OExactNumber.TYPE)
480: .castToBoolean())
481: score--;
482:
483: javaArgs[i] = new Integer((int) val);
484: } catch (PackagedScriptObjectException e) {
485: return 0;
486: }
487: } else if (parameterTypes[i] == Long.TYPE) {
488: try {
489: javaArgs[i] = new Long(arg.castToExactNumber());
490:
491: if (!arg.bopInstanceOf(OExactNumber.TYPE)
492: .castToBoolean())
493: score--;
494: } catch (PackagedScriptObjectException e) {
495: return 0;
496: }
497: } else if (parameterTypes[i] == Float.TYPE) {
498: try {
499: double val = arg.castToInexactNumber();
500:
501: if ((double) ((float) val) != val)
502: return 0;
503:
504: if (!arg.bopInstanceOf(OInexactNumber.TYPE)
505: .castToBoolean())
506: score--;
507:
508: javaArgs[i] = new Float((float) val);
509: } catch (PackagedScriptObjectException e) {
510: return 0;
511: }
512: } else if (parameterTypes[i] == Double.TYPE) {
513: try {
514: javaArgs[i] = new Double(arg
515: .castToInexactNumber());
516:
517: if (!arg.bopInstanceOf(OInexactNumber.TYPE)
518: .castToBoolean())
519: score--;
520: } catch (PackagedScriptObjectException e) {
521: return 0;
522: }
523: } else {
524: return 0;
525: }
526: } else {
527: Object obj = arg.castToJavaObject();
528:
529: // to deal with NULL/UNDEFINED:
530: if (obj == null)
531: obj = arg;
532:
533: if (parameterTypes[i].isAssignableFrom(obj.getClass())) {
534: if (parameterTypes[i] != obj.getClass())
535: score--;
536:
537: javaArgs[i] = obj;
538: } else if (parameterTypes[i].isAssignableFrom(arg
539: .getClass())) {
540: if (parameterTypes[i] != arg.getClass())
541: score--;
542:
543: javaArgs[i] = arg;
544: } else {
545: boolean transformed = false;
546:
547: for (Iterator itr = functionTransformList
548: .iterator(); itr.hasNext();) {
549: FunctionTransformer ft = (FunctionTransformer) (itr
550: .next());
551:
552: if (parameterTypes[i].isAssignableFrom(ft
553: .getTargetClass())) {
554: javaArgs[i] = ft.transform(arg);
555: transformed = true;
556: break;
557: }
558: }
559:
560: if (!transformed)
561: return 0;
562: }
563: }
564: }
565:
566: return score;
567: }
568:
569: private static final boolean compatibleJavaArray(
570: Class componentType, Object javaArr) {
571: Class t = javaArr.getClass().getComponentType();
572: if (t == null)
573: return false;
574: return componentType.isAssignableFrom(t);
575: }
576:
577: /*=======================================================================*/
578: /**
579: * Convert a java object to a script object. Some java types can be
580: * converted back to native script types, rather than need a wrapper,
581: * so this handles that conversion.
582: *
583: * @param javaObject the java object to make a wrapper for
584: */
585: public final static Value convertToScriptObject(Object javaObject) {
586: Value tmp;
587:
588: if (javaObject == null) {
589: return Value.NULL;
590: } else if ((tmp = ClassWrapGen.getScriptObject(javaObject)) != null) {
591: return tmp;
592: } else if (javaObject instanceof Number) {
593: if ((javaObject instanceof Float)
594: || (javaObject instanceof Double))
595: return OInexactNumber
596: .makeInexactNumber(((Number) javaObject)
597: .doubleValue());
598: else
599: return OExactNumber
600: .makeExactNumber(((Number) javaObject)
601: .longValue());
602: } else if (javaObject instanceof Boolean) {
603: return OBoolean.makeBoolean(((Boolean) javaObject)
604: .booleanValue());
605: } else if (javaObject instanceof String) {
606: return new OString((String) javaObject); // XXX should this be intern'd?
607: } else if (javaObject instanceof Character) {
608: return new OString(((Character) javaObject).toString()); // XXX should this be intern'd?
609: } else if (javaObject instanceof Value) {
610: return (Value) javaObject;
611: } else if (javaObject instanceof Class) {
612: return JavaClassWrapper.getClassWrapper((Class) javaObject);
613: } else if (javaObject.getClass().isArray()) {
614: return OArray.makeArray(javaObject);
615: } else {
616: return new JavaObjectWrapper(javaObject);
617: }
618: }
619:
620: public final static Value convertToScriptObject(long longVal) {
621: return OExactNumber.makeExactNumber(longVal);
622: }
623:
624: public final static Value convertToScriptObject(double doubleVal) {
625: return OInexactNumber.makeInexactNumber(doubleVal);
626: }
627:
628: public final static Value convertToScriptObject(boolean javaObject) {
629: return OBoolean.makeBoolean(javaObject);
630: }
631:
632: public final static Value convertToScriptObject(String javaObject) {
633: if (javaObject == null) {
634: return Value.NULL;
635: } else {
636: return new OString(javaObject);
637: }
638: }
639: }
640:
641: /*
642: * Local Variables:
643: * tab-width: 2
644: * indent-tabs-mode: nil
645: * mode: java
646: * c-indentation-style: java
647: * c-basic-offset: 2
648: * eval: (c-set-offset 'substatement-open '0)
649: * eval: (c-set-offset 'case-label '+)
650: * eval: (c-set-offset 'inclass '+)
651: * eval: (c-set-offset 'inline-open '0)
652: * End:
653: */
|