2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / NativeJavaObject.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
2  *\r
3  * The contents of this file are subject to the Netscape Public\r
4  * License Version 1.1 (the "License"); you may not use this file\r
5  * except in compliance with the License. You may obtain a copy of\r
6  * the License at http://www.mozilla.org/NPL/\r
7  *\r
8  * Software distributed under the License is distributed on an "AS\r
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
10  * implied. See the License for the specific language governing\r
11  * rights and limitations under the License.\r
12  *\r
13  * The Original Code is Rhino code, released\r
14  * May 6, 1999.\r
15  *\r
16  * The Initial Developer of the Original Code is Netscape\r
17  * Communications Corporation.  Portions created by Netscape are\r
18  * Copyright (C) 1997-2000 Netscape Communications Corporation. All\r
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s): \r
22  * Norris Boyd\r
23  * Igor Bukanov\r
24  * Frank Mitchell\r
25  * Mike Shaver\r
26  *\r
27  * Alternatively, the contents of this file may be used under the\r
28  * terms of the GNU Public License (the "GPL"), in which case the\r
29  * provisions of the GPL are applicable instead of those above.\r
30  * If you wish to allow use of your version of this file only\r
31  * under the terms of the GPL and not to allow others to use your\r
32  * version of this file under the NPL, indicate your decision by\r
33  * deleting the provisions above and replace them with the notice\r
34  * and other provisions required by the GPL.  If you do not delete\r
35  * the provisions above, a recipient may use your version of this\r
36  * file under either the NPL or the GPL.\r
37  */\r
38 \r
39 package org.mozilla.javascript;\r
40 \r
41 import java.lang.reflect.*;\r
42 import java.util.Hashtable;\r
43 import java.util.Enumeration;\r
44 \r
45 /**\r
46  * This class reflects non-Array Java objects into the JavaScript environment.  It\r
47  * reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly\r
48  * overloaded) methods.<p>\r
49  *\r
50  * @author Mike Shaver\r
51  * @see NativeJavaArray\r
52  * @see NativeJavaPackage\r
53  * @see NativeJavaClass\r
54  */\r
55 \r
56 public class NativeJavaObject implements Scriptable, Wrapper {\r
57 \r
58     public NativeJavaObject(Scriptable scope, Object javaObject, \r
59                             JavaMembers members) \r
60     {\r
61         this.parent = scope;\r
62         this.javaObject = javaObject;\r
63         this.members = members;\r
64     }\r
65     \r
66     public NativeJavaObject(Scriptable scope, Object javaObject, \r
67                             Class staticType) \r
68     {\r
69         this.parent = scope;\r
70         this.javaObject = javaObject;\r
71         Class dynamicType = javaObject != null ? javaObject.getClass()\r
72             : staticType;\r
73         members = JavaMembers.lookupClass(scope, dynamicType, staticType);\r
74         fieldAndMethods = members.getFieldAndMethodsObjects(this, javaObject, false);\r
75     }\r
76     \r
77     public boolean has(String name, Scriptable start) {\r
78         return members.has(name, false);\r
79     }\r
80         \r
81     public boolean has(int index, Scriptable start) {\r
82         return false;\r
83     }\r
84         \r
85     public Object get(String name, Scriptable start) {\r
86         if (fieldAndMethods != null) {\r
87             Object result = fieldAndMethods.get(name);\r
88             if (result != null) {\r
89                 return result;\r
90             }\r
91         }\r
92         // TODO: passing 'this' as the scope is bogus since it has \r
93         //  no parent scope\r
94         return members.get(this, name, javaObject, false);\r
95     }\r
96 \r
97     public Object get(int index, Scriptable start) {\r
98         throw members.reportMemberNotFound(Integer.toString(index));\r
99     }\r
100     \r
101     public void put(String name, Scriptable start, Object value) {\r
102         // We could be asked to modify the value of a property in the \r
103         // prototype. Since we can't add a property to a Java object,\r
104         // we modify it in the prototype rather than copy it down.\r
105         if (prototype == null || members.has(name, false))\r
106             members.put(this, name, javaObject, value, false);\r
107         else\r
108             prototype.put(name, prototype, value);\r
109     }\r
110 \r
111     public void put(int index, Scriptable start, Object value) {\r
112         throw members.reportMemberNotFound(Integer.toString(index));\r
113     }\r
114 \r
115     public boolean hasInstance(Scriptable value) {\r
116         // This is an instance of a Java class, so always return false\r
117         return false;\r
118     }\r
119     \r
120     public void delete(String name) {\r
121     }\r
122     \r
123     public void delete(int index) {\r
124     }\r
125     \r
126     public Scriptable getPrototype() {\r
127         if (prototype == null && javaObject.getClass() == ScriptRuntime.StringClass) {\r
128             return ScriptableObject.getClassPrototype(parent, "String");\r
129         }\r
130         return prototype;\r
131     }\r
132 \r
133     /**\r
134      * Sets the prototype of the object.\r
135      */\r
136     public void setPrototype(Scriptable m) {\r
137         prototype = m;\r
138     }\r
139 \r
140     /**\r
141      * Returns the parent (enclosing) scope of the object.\r
142      */\r
143     public Scriptable getParentScope() {\r
144         return parent;\r
145     }\r
146 \r
147     /**\r
148      * Sets the parent (enclosing) scope of the object.\r
149      */\r
150     public void setParentScope(Scriptable m) {\r
151         parent = m;\r
152     }\r
153 \r
154     public Object[] getIds() {\r
155         return members.getIds(false);\r
156     }\r
157     \r
158     public static Object wrap(Scriptable scope, Object obj, Class staticType) \r
159     {\r
160         if (obj == null)\r
161             return obj;\r
162         Context cx = Context.getCurrentContext();\r
163         if (cx != null && cx.wrapHandler != null) {\r
164             Object result = cx.wrapHandler.wrap(scope, obj, staticType);\r
165             if (result != null)\r
166                 return result;\r
167         }\r
168         Class cls = obj.getClass();\r
169         if (staticType != null && staticType.isPrimitive()) {\r
170             if (staticType == Void.TYPE)\r
171                 return Undefined.instance;\r
172             if (staticType == Character.TYPE)\r
173                 return new Integer((int) ((Character) obj).charValue());\r
174             return obj;\r
175         }\r
176         if (cls.isArray())\r
177             return NativeJavaArray.wrap(scope, obj);\r
178         if (obj instanceof Scriptable)\r
179             return obj;\r
180         if (Context.useJSObject && jsObjectClass != null && \r
181             staticType != jsObjectClass && jsObjectClass.isInstance(obj)) \r
182         {\r
183             try {\r
184                 return jsObjectGetScriptable.invoke(obj, ScriptRuntime.emptyArgs);\r
185             }\r
186             catch (InvocationTargetException e) {\r
187                 // Just abandon conversion from JSObject\r
188             }\r
189             catch (IllegalAccessException e) {\r
190                 // Just abandon conversion from JSObject\r
191             }\r
192         }\r
193         return new NativeJavaObject(scope, obj, staticType);\r
194     }\r
195 \r
196     public Object unwrap() {\r
197         return javaObject;\r
198     }\r
199 \r
200     public String getClassName() {\r
201         return "JavaObject";\r
202     }\r
203 \r
204     Function getConverter(String converterName) {\r
205         Object converterFunction = get(converterName, this);\r
206         if (converterFunction instanceof Function) {\r
207             return (Function) converterFunction;\r
208         }\r
209         return null;\r
210     }\r
211 \r
212     Object callConverter(Function converterFunction)\r
213         throws JavaScriptException\r
214     {\r
215         Function f = (Function) converterFunction;\r
216         return f.call(Context.getContext(), f.getParentScope(),\r
217                       this, ScriptRuntime.emptyArgs);\r
218     }\r
219 \r
220     Object callConverter(String converterName)\r
221         throws JavaScriptException\r
222     {\r
223         Function converter = getConverter(converterName);\r
224         if (converter == null) {\r
225             return javaObject.toString();\r
226         }\r
227         return callConverter(converter);\r
228     }\r
229 \r
230     public Object getDefaultValue(Class hint) {\r
231         if (hint == null || hint == ScriptRuntime.StringClass)\r
232             return javaObject.toString();\r
233         try {\r
234             if (hint == ScriptRuntime.BooleanClass)\r
235                 return callConverter("booleanValue");\r
236             if (hint == ScriptRuntime.NumberClass) {\r
237                 return callConverter("doubleValue");\r
238             }\r
239             // fall through to error message\r
240         } catch (JavaScriptException jse) {\r
241             // fall through to error message\r
242         }\r
243         throw Context.reportRuntimeError0("msg.default.value");\r
244     }\r
245 \r
246 \r
247     /**\r
248      * Determine whether we can/should convert between the given type and the\r
249      * desired one.  This should be superceded by a conversion-cost calculation\r
250      * function, but for now I'll hide behind precedent.\r
251      */\r
252     public static boolean canConvert(Object fromObj, Class to) {\r
253         int weight = NativeJavaObject.getConversionWeight(fromObj, to);\r
254 \r
255         return (weight < CONVERSION_NONE);\r
256     }\r
257 \r
258     static final int JSTYPE_UNDEFINED   = 0; // undefined type\r
259     static final int JSTYPE_NULL        = 1; // null\r
260     static final int JSTYPE_BOOLEAN     = 2; // boolean\r
261     static final int JSTYPE_NUMBER      = 3; // number\r
262     static final int JSTYPE_STRING      = 4; // string\r
263     static final int JSTYPE_JAVA_CLASS  = 5; // JavaClass\r
264     static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject\r
265     static final int JSTYPE_JAVA_ARRAY  = 7; // JavaArray\r
266     static final int JSTYPE_OBJECT      = 8; // Scriptable\r
267 \r
268     public static final byte CONVERSION_TRIVIAL      = 1;\r
269     public static final byte CONVERSION_NONTRIVIAL   = 0;\r
270     public static final byte CONVERSION_NONE         = 99;\r
271 \r
272     /**\r
273      * Derive a ranking based on how "natural" the conversion is.\r
274      * The special value CONVERSION_NONE means no conversion is possible, \r
275      * and CONVERSION_NONTRIVIAL signals that more type conformance testing \r
276      * is required.\r
277      * Based on \r
278      * <a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">\r
279      * "preferred method conversions" from Live Connect 3</a>\r
280      */\r
281     public static int getConversionWeight(Object fromObj, Class to) {\r
282         int fromCode = NativeJavaObject.getJSTypeCode(fromObj);\r
283 \r
284         int result = CONVERSION_NONE;\r
285 \r
286         switch (fromCode) {\r
287 \r
288         case JSTYPE_UNDEFINED:\r
289             if (to == ScriptRuntime.StringClass || \r
290                 to == ScriptRuntime.ObjectClass) {\r
291                 result = 1;\r
292             }\r
293             break;\r
294 \r
295         case JSTYPE_NULL:\r
296             if (!to.isPrimitive()) {\r
297                 result = 1;\r
298             }\r
299             break;\r
300 \r
301         case JSTYPE_BOOLEAN:\r
302             // "boolean" is #1\r
303             if (to == Boolean.TYPE) {\r
304                 result = 1;\r
305             }\r
306             else if (to == ScriptRuntime.BooleanClass) {\r
307                 result = 2;\r
308             }\r
309             else if (to == ScriptRuntime.ObjectClass) {\r
310                 result = 3;\r
311             }\r
312             else if (to == ScriptRuntime.StringClass) {\r
313                 result = 4;\r
314             }\r
315             break;\r
316 \r
317         case JSTYPE_NUMBER:\r
318             if (to.isPrimitive()) {\r
319                 if (to == Double.TYPE) {\r
320                     result = 1;\r
321                 }\r
322                 else if (to != Boolean.TYPE) {\r
323                     result = 1 + NativeJavaObject.getSizeRank(to);\r
324                 }\r
325             }\r
326             else {\r
327                 if (to == ScriptRuntime.StringClass) {\r
328                     // native numbers are #1-8\r
329                     result = 9;\r
330                 }\r
331                 else if (to == ScriptRuntime.ObjectClass) {\r
332                     result = 10;\r
333                 }\r
334                 else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) {\r
335                     // "double" is #1\r
336                     result = 2;\r
337                 }\r
338             }\r
339             break;\r
340 \r
341         case JSTYPE_STRING:\r
342             if (to == ScriptRuntime.StringClass) {\r
343                 result = 1;\r
344             }\r
345             else if (to == ScriptRuntime.ObjectClass) {\r
346                 result = 2;\r
347             }\r
348             else if (to.isPrimitive() && to != Boolean.TYPE) {\r
349                 if (to == Character.TYPE) {\r
350                     result = 3;\r
351                 }\r
352                 else {\r
353                     result = 4;\r
354                 }\r
355             }\r
356             break;\r
357 \r
358         case JSTYPE_JAVA_CLASS:\r
359             if (to == ScriptRuntime.ClassClass) {\r
360                 result = 1;\r
361             }\r
362             else if (Context.useJSObject && jsObjectClass != null && \r
363                 jsObjectClass.isAssignableFrom(to)) {\r
364                 result = 2;\r
365             }\r
366             else if (to == ScriptRuntime.ObjectClass) {\r
367                 result = 3;\r
368             }\r
369             else if (to == ScriptRuntime.StringClass) {\r
370                 result = 4;\r
371             }\r
372             break;\r
373 \r
374         case JSTYPE_JAVA_OBJECT:\r
375         case JSTYPE_JAVA_ARRAY:\r
376             if (to == ScriptRuntime.StringClass) {\r
377                 result = 2;\r
378             }\r
379             else if (to.isPrimitive() && to != Boolean.TYPE) {\r
380                 result = \r
381                     (fromCode == JSTYPE_JAVA_ARRAY) ?\r
382                     CONVERSION_NONTRIVIAL :\r
383                     2 + NativeJavaObject.getSizeRank(to);\r
384             }\r
385             else {\r
386                 Object javaObj = fromObj;\r
387                 if (javaObj instanceof Wrapper) {\r
388                     javaObj = ((Wrapper)javaObj).unwrap();\r
389                 }\r
390                 if (to.isInstance(javaObj)) {\r
391                     result = CONVERSION_NONTRIVIAL;\r
392                 }\r
393             }\r
394             break;\r
395 \r
396         case JSTYPE_OBJECT:\r
397             // Other objects takes #1-#3 spots\r
398             if (Context.useJSObject && jsObjectClass != null && \r
399                 jsObjectClass.isAssignableFrom(to)) {\r
400                 result = 1;\r
401             }\r
402             else if (fromObj instanceof NativeArray && to.isArray()) {\r
403                 // This is a native array conversion to a java array\r
404                 // Array conversions are all equal, and preferable to object \r
405                 // and string conversion, per LC3.\r
406                 result = 1;\r
407             }\r
408             else if (to == ScriptRuntime.ObjectClass) {\r
409                 result = 2;\r
410             }\r
411             else if (to == ScriptRuntime.StringClass) {\r
412                 result = 3;\r
413             }\r
414             else if (to.isPrimitive() || to != Boolean.TYPE) {\r
415                 result = 3 + NativeJavaObject.getSizeRank(to);\r
416             }\r
417             break;\r
418         }\r
419 \r
420         return result;\r
421     \r
422     }\r
423 \r
424     static int getSizeRank(Class aType) {\r
425         if (aType == Double.TYPE) {\r
426             return 1;\r
427         }\r
428         else if (aType == Float.TYPE) {\r
429             return 2;\r
430         }\r
431         else if (aType == Long.TYPE) {\r
432             return 3;\r
433         }\r
434         else if (aType == Integer.TYPE) {\r
435             return 4;\r
436         }\r
437         else if (aType == Short.TYPE) {\r
438             return 5;\r
439         }\r
440         else if (aType == Character.TYPE) {\r
441             return 6;\r
442         }\r
443         else if (aType == Byte.TYPE) {\r
444             return 7;\r
445         }\r
446         else if (aType == Boolean.TYPE) {\r
447             return CONVERSION_NONE;\r
448         }\r
449         else {\r
450             return 8;\r
451         }\r
452     }\r
453 \r
454     static int getJSTypeCode(Object value) {\r
455         if (value == null) {\r
456             return JSTYPE_NULL;\r
457         }\r
458         else if (value == Undefined.instance) {\r
459             return JSTYPE_UNDEFINED;\r
460         }\r
461         else if (value instanceof Scriptable) {\r
462             if (value instanceof NativeJavaClass) {\r
463                 return JSTYPE_JAVA_CLASS;\r
464             }\r
465             else if (value instanceof NativeJavaArray) {\r
466                 return JSTYPE_JAVA_ARRAY;\r
467             }\r
468             else if (value instanceof NativeJavaObject) {\r
469                 return JSTYPE_JAVA_OBJECT;\r
470             }\r
471             else {\r
472                 return JSTYPE_OBJECT;\r
473             }\r
474         }\r
475         else {\r
476             Class valueClass = value.getClass();\r
477 \r
478             if (valueClass == ScriptRuntime.StringClass) {\r
479                 return JSTYPE_STRING;\r
480             }\r
481             else if (valueClass == ScriptRuntime.BooleanClass) {\r
482                 return JSTYPE_BOOLEAN;\r
483             }\r
484             else if (value instanceof Number) {\r
485                 return JSTYPE_NUMBER;\r
486             }\r
487             else if (valueClass == ScriptRuntime.ClassClass) {\r
488                 return JSTYPE_JAVA_CLASS;\r
489             }\r
490             else if (valueClass.isArray()) {\r
491                 return JSTYPE_JAVA_ARRAY;\r
492             }\r
493             else {\r
494                 return JSTYPE_JAVA_OBJECT;\r
495             }\r
496         }\r
497     }\r
498 \r
499     /**\r
500      * Type-munging for field setting and method invocation.\r
501      * Conforms to LC3 specification\r
502      */\r
503     public static Object coerceType(Class type, Object value) {\r
504         if (value != null && value.getClass() == type) {\r
505             return value;\r
506         }\r
507         \r
508         switch (NativeJavaObject.getJSTypeCode(value)) {\r
509 \r
510         case JSTYPE_NULL:\r
511             // raise error if type.isPrimitive()\r
512             if (type.isPrimitive()) {\r
513                 reportConversionError(value, type);\r
514             }\r
515             return null;\r
516 \r
517         case JSTYPE_UNDEFINED:\r
518             if (type == ScriptRuntime.StringClass || \r
519                 type == ScriptRuntime.ObjectClass) {\r
520                 return "undefined";\r
521             }\r
522             else {\r
523                 reportConversionError("undefined", type);\r
524             }\r
525             break;\r
526 \r
527         case JSTYPE_BOOLEAN:\r
528             // Under LC3, only JS Booleans can be coerced into a Boolean value\r
529             if (type == Boolean.TYPE || \r
530                 type == ScriptRuntime.BooleanClass || \r
531                 type == ScriptRuntime.ObjectClass) {\r
532                 return value;\r
533             }\r
534             else if (type == ScriptRuntime.StringClass) {\r
535                 return value.toString();\r
536             }\r
537             else {\r
538                 reportConversionError(value, type);\r
539             }\r
540             break;\r
541 \r
542         case JSTYPE_NUMBER:\r
543             if (type == ScriptRuntime.StringClass) {\r
544                 return ScriptRuntime.toString(value);\r
545             }\r
546             else if (type == ScriptRuntime.ObjectClass) {\r
547                 return coerceToNumber(Double.TYPE, value);\r
548             }\r
549             else if ((type.isPrimitive() && type != Boolean.TYPE) || \r
550                      ScriptRuntime.NumberClass.isAssignableFrom(type)) {\r
551                 return coerceToNumber(type, value);\r
552             }\r
553             else {\r
554                 reportConversionError(value, type);\r
555             }\r
556             break;\r
557 \r
558         case JSTYPE_STRING:\r
559             if (type == ScriptRuntime.StringClass ||\r
560                 type == ScriptRuntime.ObjectClass) {\r
561                 return value;\r
562             }\r
563             else if (type == Character.TYPE || \r
564                      type == ScriptRuntime.CharacterClass) {\r
565                 // Special case for converting a single char string to a \r
566                 // character\r
567                 // Placed here because it applies *only* to JS strings, \r
568                 // not other JS objects converted to strings\r
569                 if (((String)value).length() == 1) {\r
570                     return new Character(((String)value).charAt(0));\r
571                 }\r
572                 else {\r
573                     return coerceToNumber(type, value);\r
574                 }\r
575             }\r
576             else if ((type.isPrimitive() && type != Boolean.TYPE) || \r
577                      ScriptRuntime.NumberClass.isAssignableFrom(type)) {\r
578                 return coerceToNumber(type, value);\r
579             }\r
580             else {\r
581                 reportConversionError(value, type);\r
582             }\r
583             break;\r
584 \r
585         case JSTYPE_JAVA_CLASS:\r
586             if (Context.useJSObject && jsObjectClass != null && \r
587                 (type == ScriptRuntime.ObjectClass || \r
588                  jsObjectClass.isAssignableFrom(type))) {\r
589                 return coerceToJSObject(type, (Scriptable)value);\r
590             }\r
591             else {\r
592                 if (value instanceof Wrapper) {\r
593                     value = ((Wrapper)value).unwrap();\r
594                 }\r
595 \r
596                 if (type == ScriptRuntime.ClassClass ||\r
597                     type == ScriptRuntime.ObjectClass) {\r
598                     return value;\r
599                 }\r
600                 else if (type == ScriptRuntime.StringClass) {\r
601                     return value.toString();\r
602                 }\r
603                 else {\r
604                     reportConversionError(value, type);\r
605                 }\r
606             }\r
607             break;\r
608 \r
609         case JSTYPE_JAVA_OBJECT:\r
610         case JSTYPE_JAVA_ARRAY:\r
611             if (type.isPrimitive()) {\r
612                 if (type == Boolean.TYPE) {\r
613                     reportConversionError(value, type);\r
614                 }\r
615                 return coerceToNumber(type, value);\r
616             }\r
617             else {\r
618                 if (value instanceof Wrapper) {\r
619                     value = ((Wrapper)value).unwrap();\r
620                 }\r
621                 if (type == ScriptRuntime.StringClass) {\r
622                     return value.toString();\r
623                 }\r
624                 else {\r
625                     if (type.isInstance(value)) {\r
626                         return value;\r
627                     }\r
628                     else {\r
629                         reportConversionError(value, type);\r
630                     }\r
631                 }\r
632             }\r
633             break;\r
634 \r
635         case JSTYPE_OBJECT:\r
636             if (Context.useJSObject && jsObjectClass != null && \r
637                 (type == ScriptRuntime.ObjectClass || \r
638                  jsObjectClass.isAssignableFrom(type))) {\r
639                 return coerceToJSObject(type, (Scriptable)value);\r
640             }\r
641             else if (type == ScriptRuntime.StringClass) {\r
642                 return ScriptRuntime.toString(value);\r
643             }\r
644             else if (type.isPrimitive()) {\r
645                 if (type == Boolean.TYPE) {\r
646                     reportConversionError(value, type);\r
647                 }\r
648                 return coerceToNumber(type, value);\r
649             }\r
650             else if (type.isInstance(value)) {\r
651                 return value;\r
652             }\r
653             else if (type.isArray() && value instanceof NativeArray) {\r
654                 // Make a new java array, and coerce the JS array components \r
655                 // to the target (component) type.\r
656                 NativeArray array = (NativeArray) value;\r
657                 long length = array.jsGet_length();\r
658                 Class arrayType = type.getComponentType();\r
659                 Object Result = Array.newInstance(arrayType, (int)length);\r
660                 for (int i = 0 ; i < length ; ++i) {\r
661                     try  {\r
662                         Array.set(Result, i, coerceType(arrayType, \r
663                                                         array.get(i, array)));\r
664                     }\r
665                     catch (EvaluatorException ee) {\r
666                         reportConversionError(value, type);\r
667                     }\r
668                 }\r
669 \r
670                 return Result;\r
671             }\r
672             else if (value instanceof Wrapper) {\r
673                 value = ((Wrapper)value).unwrap();\r
674                 if (type.isInstance(value))\r
675                     return value;\r
676                 reportConversionError(value, type);\r
677             }\r
678             else {\r
679                 reportConversionError(value, type);\r
680             }\r
681             break;\r
682         }\r
683 \r
684         return value;\r
685     }\r
686 \r
687     static Object coerceToJSObject(Class type, Scriptable value) {\r
688         // If JSObject compatibility is enabled, and the method wants it,\r
689         // wrap the Scriptable value in a JSObject.\r
690 \r
691         if (ScriptRuntime.ScriptableClass.isAssignableFrom(type))\r
692             return value;\r
693 \r
694         try {\r
695             Object ctorArgs[] = { value };\r
696             return jsObjectCtor.newInstance(ctorArgs);\r
697         } catch (InstantiationException instEx) {\r
698             throw new EvaluatorException("error generating JSObject wrapper for " +\r
699                                          value);\r
700         } catch (IllegalArgumentException argEx) {\r
701             throw new EvaluatorException("JSObject constructor doesn't want [Scriptable]!");\r
702         } catch (InvocationTargetException e) {\r
703             throw WrappedException.wrapException(e.getTargetException());\r
704         } catch (IllegalAccessException accessEx) {\r
705             throw new EvaluatorException("JSObject constructor is protected/private!");\r
706         }\r
707     }\r
708 \r
709     static Object coerceToNumber(Class type, Object value) {\r
710         Class valueClass = value.getClass();\r
711 \r
712         // Character\r
713         if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {\r
714             if (valueClass == ScriptRuntime.CharacterClass) {\r
715                 return value;\r
716             }\r
717             return new Character((char)toInteger(value, \r
718                                                  ScriptRuntime.CharacterClass,\r
719                                                  (double)Character.MIN_VALUE,\r
720                                                  (double)Character.MAX_VALUE));\r
721         }\r
722 \r
723         // Double, Float\r
724         if (type == ScriptRuntime.ObjectClass || \r
725             type == ScriptRuntime.DoubleClass || type == Double.TYPE) {\r
726             return valueClass == ScriptRuntime.DoubleClass\r
727                 ? value\r
728                 : new Double(toDouble(value));\r
729         }\r
730 \r
731         if (type == ScriptRuntime.FloatClass || type == Float.TYPE) {\r
732             if (valueClass == ScriptRuntime.FloatClass) {\r
733                 return value;\r
734             }\r
735             else {\r
736                 double number = toDouble(value);\r
737                 if (Double.isInfinite(number) || Double.isNaN(number)\r
738                     || number == 0.0) {\r
739                     return new Float((float)number);\r
740                 }\r
741                 else {\r
742                     double absNumber = Math.abs(number);\r
743                     if (absNumber < (double)Float.MIN_VALUE) {\r
744                         return new Float((number > 0.0) ? +0.0 : -0.0);\r
745                     }\r
746                     else if (absNumber > (double)Float.MAX_VALUE) {\r
747                         return new Float((number > 0.0) ?\r
748                                          Float.POSITIVE_INFINITY : \r
749                                          Float.NEGATIVE_INFINITY);\r
750                     }\r
751                     else {\r
752                         return new Float((float)number);\r
753                     }\r
754                 }\r
755             }\r
756         }\r
757 \r
758         // Integer, Long, Short, Byte\r
759         if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) {\r
760             if (valueClass == ScriptRuntime.IntegerClass) {\r
761                 return value;\r
762             }\r
763             else {\r
764                 return new Integer((int)toInteger(value, \r
765                                                   ScriptRuntime.IntegerClass,\r
766                                                   (double)Integer.MIN_VALUE,\r
767                                                   (double)Integer.MAX_VALUE));\r
768             }\r
769         }\r
770 \r
771         if (type == ScriptRuntime.LongClass || type == Long.TYPE) {\r
772             if (valueClass == ScriptRuntime.LongClass) {\r
773                 return value;\r
774             }\r
775             else {\r
776                 /* Long values cannot be expressed exactly in doubles.\r
777                  * We thus use the largest and smallest double value that \r
778                  * has a value expressible as a long value. We build these \r
779                  * numerical values from their hexidecimal representations\r
780                  * to avoid any problems caused by attempting to parse a\r
781                  * decimal representation.\r
782                  */\r
783                 final double max = Double.longBitsToDouble(0x43dfffffffffffffL);\r
784                 final double min = Double.longBitsToDouble(0xc3e0000000000000L);\r
785                 return new Long(toInteger(value, \r
786                                           ScriptRuntime.LongClass,\r
787                                           min,\r
788                                           max));\r
789             }\r
790         }\r
791 \r
792         if (type == ScriptRuntime.ShortClass || type == Short.TYPE) {\r
793             if (valueClass == ScriptRuntime.ShortClass) {\r
794                 return value;\r
795             }\r
796             else {\r
797                 return new Short((short)toInteger(value, \r
798                                                   ScriptRuntime.ShortClass,\r
799                                                   (double)Short.MIN_VALUE,\r
800                                                   (double)Short.MAX_VALUE));\r
801             }\r
802         }\r
803 \r
804         if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) {\r
805             if (valueClass == ScriptRuntime.ByteClass) {\r
806                 return value;\r
807             }\r
808             else {\r
809                 return new Byte((byte)toInteger(value, \r
810                                                 ScriptRuntime.ByteClass,\r
811                                                 (double)Byte.MIN_VALUE,\r
812                                                 (double)Byte.MAX_VALUE));\r
813             }\r
814         }\r
815 \r
816         return new Double(toDouble(value));\r
817     }\r
818 \r
819 \r
820     static double toDouble(Object value) {\r
821         if (value instanceof Number) {\r
822             return ((Number)value).doubleValue();\r
823         }\r
824         else if (value instanceof String) {\r
825             return ScriptRuntime.toNumber((String)value);\r
826         }\r
827         else if (value instanceof Scriptable) {\r
828             if (value instanceof Wrapper) {\r
829                 // XXX: optimize tail-recursion?\r
830                 return toDouble(((Wrapper)value).unwrap());\r
831             }\r
832             else {\r
833                 return ScriptRuntime.toNumber(value);\r
834             }\r
835         }\r
836         else {\r
837             Method meth;\r
838             try {\r
839                 meth = value.getClass().getMethod("doubleValue", null);\r
840             }\r
841             catch (NoSuchMethodException e) {\r
842                 meth = null;\r
843             }\r
844             catch (SecurityException e) {\r
845                 meth = null;\r
846             }\r
847             if (meth != null) {\r
848                 try {\r
849                     return ((Number)meth.invoke(value, null)).doubleValue();\r
850                 }\r
851                 catch (IllegalAccessException e) {\r
852                     // XXX: ignore, or error message?\r
853                     reportConversionError(value, Double.TYPE);\r
854                 }\r
855                 catch (InvocationTargetException e) {\r
856                     // XXX: ignore, or error message?\r
857                     reportConversionError(value, Double.TYPE);\r
858                 }\r
859             }\r
860             return ScriptRuntime.toNumber(value.toString());\r
861         }\r
862     }\r
863 \r
864     static long toInteger(Object value, Class type, double min, double max) {\r
865         double d = toDouble(value);\r
866 \r
867         if (Double.isInfinite(d) || Double.isNaN(d)) {\r
868             // Convert to string first, for more readable message\r
869             reportConversionError(ScriptRuntime.toString(value), type);\r
870         }\r
871 \r
872         if (d > 0.0) {\r
873             d = Math.floor(d);\r
874         }\r
875         else {\r
876             d = Math.ceil(d);\r
877         }\r
878 \r
879         if (d < min || d > max) {\r
880             // Convert to string first, for more readable message\r
881             reportConversionError(ScriptRuntime.toString(value), type);\r
882         }\r
883         return (long)d;\r
884     }\r
885 \r
886     static void reportConversionError(Object value, Class type) {\r
887         throw Context.reportRuntimeError2\r
888             ("msg.conversion.not.allowed", \r
889              value.toString(), NativeJavaMethod.javaSignature(type));\r
890     }\r
891 \r
892     public static void initJSObject() {\r
893         // if netscape.javascript.JSObject is in the CLASSPATH, enable JSObject\r
894         // compatability wrappers\r
895         jsObjectClass = null;\r
896         try {\r
897             jsObjectClass = Class.forName("netscape.javascript.JSObject");\r
898             Class ctorParms[] = { ScriptRuntime.ScriptableClass };\r
899             jsObjectCtor = jsObjectClass.getConstructor(ctorParms);\r
900             jsObjectGetScriptable = jsObjectClass.getMethod("getScriptable", \r
901                                                             new Class[0]);\r
902         } catch (ClassNotFoundException classEx) {\r
903             // jsObjectClass already null\r
904         } catch (NoSuchMethodException methEx) {\r
905             // jsObjectClass already null\r
906         }\r
907     }\r
908         \r
909     /**\r
910      * The prototype of this object.\r
911      */\r
912     protected Scriptable prototype;\r
913 \r
914     /**\r
915      * The parent scope of this object.\r
916      */\r
917     protected Scriptable parent;\r
918 \r
919     protected Object javaObject;\r
920     protected JavaMembers members;\r
921     private Hashtable fieldAndMethods;\r
922     static Class jsObjectClass;\r
923     static Constructor jsObjectCtor;\r
924     static Method jsObjectGetScriptable;\r
925 }\r
926 \r