2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / NativeJavaMethod.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-1999 Netscape Communications Corporation. All\r
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s): \r
22  * Norris Boyd\r
23  * Frank Mitchell\r
24  * Mike Shaver\r
25  * \r
26  * Alternatively, the contents of this file may be used under the\r
27  * terms of the GNU Public License (the "GPL"), in which case the\r
28  * provisions of the GPL are applicable instead of those above.\r
29  * If you wish to allow use of your version of this file only\r
30  * under the terms of the GPL and not to allow others to use your\r
31  * version of this file under the NPL, indicate your decision by\r
32  * deleting the provisions above and replace them with the notice\r
33  * and other provisions required by the GPL.  If you do not delete\r
34  * the provisions above, a recipient may use your version of this\r
35  * file under either the NPL or the GPL.\r
36  */\r
37 \r
38 package org.mozilla.javascript;\r
39 \r
40 import java.lang.reflect.*;\r
41 \r
42 /**\r
43  * This class reflects Java methods into the JavaScript environment.  It\r
44  * handles overloading of methods, and method/field name conflicts.\r
45  * All NativeJavaMethods behave as JSRef `bound' methods, in that they\r
46  * always operate on the object underlying the original NativeJavaObject\r
47  * parent regardless of any reparenting that may occur.\r
48  *\r
49  * @author Mike Shaver\r
50  * @see NativeJavaArray\r
51  * @see NativeJavaPackage\r
52  * @see NativeJavaClass\r
53  */\r
54 \r
55 public class NativeJavaMethod extends NativeFunction implements Function {\r
56 \r
57     public NativeJavaMethod() {\r
58         this.functionName = null;\r
59     }\r
60 \r
61     public NativeJavaMethod(Method[] methods) {\r
62         this.methods = methods;\r
63         this.functionName = methods[0].getName();\r
64     }\r
65 \r
66     public NativeJavaMethod(Method method, String name) {\r
67         this.methods = new Method[1];\r
68         this.methods[0] = method;\r
69         this.functionName = name;\r
70     }\r
71 \r
72     public void add(Method method) {\r
73         if (functionName == null) {\r
74             functionName = method.getName();\r
75         } else if (!functionName.equals(method.getName())) {\r
76             throw new RuntimeException("internal method name mismatch");\r
77         }                \r
78         // XXX a more intelligent growth algorithm would be nice\r
79         int len = methods == null ? 0 : methods.length;\r
80         Method[] newMeths = new Method[len + 1];\r
81         for (int i = 0; i < len; i++)\r
82             newMeths[i] = methods[i];\r
83         newMeths[len] = method;\r
84         methods = newMeths;\r
85     }\r
86 \r
87     static String scriptSignature(Object value) {\r
88         if (value == null) {\r
89             return "null";\r
90         }\r
91         else {\r
92             Class type = value.getClass();\r
93             if (type == ScriptRuntime.UndefinedClass)\r
94                 return "undefined";\r
95             if (type == ScriptRuntime.BooleanClass)\r
96                 return "boolean";\r
97             if (type == ScriptRuntime.StringClass)\r
98                 return "string";\r
99             if (ScriptRuntime.NumberClass.isAssignableFrom(type))\r
100                 return "number";\r
101             if (value instanceof Wrapper) {\r
102                 return ((Wrapper)value).unwrap().getClass().getName();\r
103             }\r
104             if (value instanceof Scriptable) {\r
105                 if (value instanceof Function)\r
106                     return "function";\r
107                 return "object";\r
108             }\r
109             return javaSignature(type);\r
110         }\r
111     }\r
112 \r
113     static String scriptSignature(Object[] values) {\r
114         StringBuffer sig = new StringBuffer();\r
115         for (int i = 0; i < values.length; i++) {\r
116             if (i != 0)\r
117                 sig.append(',');\r
118             sig.append(scriptSignature(values[i]));\r
119         }\r
120         return sig.toString();\r
121     }\r
122 \r
123     static String javaSignature(Class type) {\r
124         if (type == null) {\r
125             return "null";\r
126         }\r
127         else if (type.isArray()) {\r
128             return javaSignature(type.getComponentType()) + "[]";\r
129         }\r
130         return type.getName();\r
131     }\r
132 \r
133     static String javaSignature(Class[] types) {\r
134         StringBuffer sig = new StringBuffer();\r
135         for (int i = 0; i < types.length; i++) {\r
136             if (i != 0)\r
137                 sig.append(',');\r
138             sig.append(javaSignature(types[i]));\r
139         }\r
140         return sig.toString();\r
141     }\r
142 \r
143     static String signature(Member member) {\r
144         Class paramTypes[];\r
145 \r
146         if (member instanceof Method) {\r
147             paramTypes = ((Method) member).getParameterTypes();\r
148             return member.getName() + "(" + javaSignature(paramTypes) + ")";\r
149         }\r
150         else {\r
151             paramTypes = ((Constructor) member).getParameterTypes();\r
152             return "(" + javaSignature(paramTypes) + ")";\r
153         }\r
154     }\r
155     \r
156     public String decompile(Context cx, int indent, boolean justbody) {\r
157         StringBuffer sb = new StringBuffer();\r
158         if (!justbody) {\r
159             sb.append("function ");\r
160             sb.append(getFunctionName());\r
161             sb.append("() {");\r
162         }\r
163         sb.append("/*\n");\r
164         toString(sb);\r
165         sb.append(justbody ? "*/\n" : "*/}\n");\r
166         return sb.toString();\r
167     }\r
168     \r
169     public String toString() {\r
170         StringBuffer sb = new StringBuffer();\r
171         toString(sb);\r
172         return sb.toString();\r
173     }\r
174 \r
175     private void toString(StringBuffer sb) {\r
176         for (int i=0; i < methods.length; i++) {\r
177             sb.append(javaSignature(methods[i].getReturnType()));\r
178             sb.append(' ');\r
179             sb.append(signature(methods[i]));\r
180             sb.append('\n');\r
181         }\r
182     }\r
183 \r
184     public Object call(Context cx, Scriptable scope, Scriptable thisObj,\r
185                        Object[] args)\r
186         throws JavaScriptException\r
187     {\r
188         // Find a method that matches the types given.\r
189         if (methods.length == 0) {\r
190             throw new RuntimeException("No methods defined for call");\r
191         }\r
192 \r
193         Method meth = (Method) findFunction(methods, args);\r
194         if (meth == null) {\r
195             Class c = methods[0].getDeclaringClass();\r
196             String sig = c.getName() + "." + functionName + "(" +\r
197                          scriptSignature(args) + ")";\r
198             throw Context.reportRuntimeError1("msg.java.no_such_method", sig);\r
199         }\r
200 \r
201         // OPT: already retrieved in findFunction, so we should inline that\r
202         // OPT: or pass it back somehow\r
203         Class paramTypes[] = meth.getParameterTypes();\r
204 \r
205         // First, we marshall the args.\r
206         for (int i = 0; i < args.length; i++) {\r
207             args[i] = NativeJavaObject.coerceType(paramTypes[i], args[i]);\r
208         }\r
209         Object javaObject;\r
210         if (Modifier.isStatic(meth.getModifiers())) {\r
211             javaObject = null;  // don't need an object\r
212         } else {\r
213             Scriptable o = thisObj;\r
214             while (!(o instanceof Wrapper)) {\r
215                 o = o.getPrototype();\r
216                 if (o == null) {\r
217                     throw Context.reportRuntimeError1(\r
218                         "msg.nonjava.method", functionName);\r
219                 }\r
220             }\r
221             javaObject = ((Wrapper) o).unwrap();        \r
222         }\r
223         try {\r
224             if (debug) {\r
225                 printDebug("Calling ", meth, args);\r
226             }\r
227 \r
228             Object retval = meth.invoke(javaObject, args);\r
229             Class staticType = meth.getReturnType();\r
230 \r
231             if (debug) {\r
232                 Class actualType = (retval == null) ? null : retval.getClass();\r
233                 System.err.println(" ----- Returned " + retval + \r
234                                    " actual = " + actualType +\r
235                                    " expect = " + staticType);\r
236             }\r
237 \r
238             Object wrapped = NativeJavaObject.wrap(scope, retval, staticType);\r
239 \r
240             if (debug) {\r
241                 Class actualType = (wrapped == null) ? null : wrapped.getClass();\r
242                 System.err.println(" ----- Wrapped as " + wrapped + \r
243                                    " class = " + actualType);\r
244             }\r
245 \r
246             if (wrapped == Undefined.instance)\r
247                 return wrapped;\r
248             if (wrapped == null && staticType == Void.TYPE)\r
249                 return Undefined.instance;\r
250             return wrapped;\r
251         } catch (IllegalAccessException accessEx) {\r
252             throw Context.reportRuntimeError(accessEx.getMessage());\r
253         } catch (InvocationTargetException e) {\r
254             throw JavaScriptException.wrapException(scope, e);\r
255         }\r
256     }\r
257 \r
258     /** \r
259      * Find the correct function to call given the set of methods\r
260      * or constructors and the arguments.\r
261      * If no function can be found to call, return null.\r
262      */\r
263     static Member findFunction(Member[] methodsOrCtors, Object[] args) {\r
264         if (methodsOrCtors.length == 0)\r
265             return null;\r
266         boolean hasMethods = methodsOrCtors[0] instanceof Method;\r
267         if (Context.useJSObject && \r
268             NativeJavaObject.jsObjectClass != null) \r
269         {\r
270             try {\r
271                 for (int i = 0; i < args.length; i++) {\r
272                     if (NativeJavaObject.jsObjectClass.isInstance(args[i]))\r
273                         args[i] = NativeJavaObject.jsObjectGetScriptable.invoke(\r
274                                     args[i], ScriptRuntime.emptyArgs);\r
275                 }\r
276             }\r
277             catch (InvocationTargetException e) {\r
278                 // Just abandon conversion from JSObject\r
279             }\r
280             catch (IllegalAccessException e) {\r
281                 // Just abandon conversion from JSObject\r
282             }\r
283         }\r
284 \r
285         Member  bestFit = null;\r
286         Class[] bestFitTypes = null;\r
287 \r
288         java.util.Vector ambiguousMethods = null;\r
289 \r
290         for (int i = 0; i < methodsOrCtors.length; i++) {\r
291             Member member = methodsOrCtors[i];\r
292             Class paramTypes[] = hasMethods\r
293                                  ? ((Method) member).getParameterTypes()\r
294                                  : ((Constructor) member).getParameterTypes();\r
295             if (paramTypes.length != args.length) {\r
296                 continue;\r
297             }\r
298             if (bestFitTypes == null) {\r
299                 int j;\r
300                 for (j = 0; j < paramTypes.length; j++) {\r
301                     if (!NativeJavaObject.canConvert(args[j], paramTypes[j])) {\r
302                         if (debug) printDebug("Rejecting (args can't convert) ", \r
303                                               member, args);\r
304                         break;\r
305                     }\r
306                 }\r
307                 if (j == paramTypes.length) {\r
308                     if (debug) printDebug("Found ", member, args);\r
309                     bestFit = member;\r
310                     bestFitTypes = paramTypes;\r
311                 }\r
312             }\r
313             else {\r
314                 int preference = \r
315                     NativeJavaMethod.preferSignature(args, \r
316                                                      paramTypes, \r
317                                                      bestFitTypes);\r
318                 if (preference == PREFERENCE_AMBIGUOUS) {\r
319                     if (debug) printDebug("Deferring ", member, args);\r
320                     // add to "ambiguity list"\r
321                     if (ambiguousMethods == null)\r
322                         ambiguousMethods = new java.util.Vector();\r
323                     ambiguousMethods.addElement(member);\r
324                 }\r
325                 else if (preference == PREFERENCE_FIRST_ARG) {\r
326                     if (debug) printDebug("Substituting ", member, args);\r
327                     bestFit = member;\r
328                     bestFitTypes = paramTypes;\r
329                 }\r
330                 else {\r
331                     if (preference == PREFERENCE_EQUAL &&\r
332                         Modifier.isStatic(bestFit.getModifiers()) &&\r
333                         bestFit.getDeclaringClass().isAssignableFrom(\r
334                             member.getDeclaringClass()))                                          \r
335                     {\r
336                         // On some JVMs, Class.getMethods will return all\r
337                         // static methods of the class heirarchy, even if\r
338                         // a derived class's parameters match exactly.\r
339                         // We want to call the dervied class's method.\r
340                         if (debug) printDebug("Rejecting (overridden static)",\r
341                                               member, args);\r
342                         bestFit = member;\r
343                         bestFitTypes = paramTypes;\r
344                     } else {\r
345                         if (debug) printDebug("Rejecting ", member, args);\r
346                     }\r
347                 }\r
348             }\r
349         }\r
350         \r
351         if (ambiguousMethods == null)\r
352             return bestFit;\r
353 \r
354         // Compare ambiguous methods with best fit, in case \r
355         // the current best fit removes the ambiguities.\r
356         for (int i = ambiguousMethods.size() - 1; i >= 0 ; i--) {\r
357             Member member = (Member)ambiguousMethods.elementAt(i);\r
358             Class paramTypes[] = hasMethods\r
359                                  ? ((Method) member).getParameterTypes()\r
360                                  : ((Constructor) member).getParameterTypes();\r
361             int preference = \r
362                 NativeJavaMethod.preferSignature(args, \r
363                                                  paramTypes, \r
364                                                  bestFitTypes);\r
365 \r
366             if (preference == PREFERENCE_FIRST_ARG) {\r
367                 if (debug) printDebug("Substituting ", member, args);\r
368                 bestFit = member;\r
369                 bestFitTypes = paramTypes;\r
370                 ambiguousMethods.removeElementAt(i);\r
371             }\r
372             else if (preference == PREFERENCE_SECOND_ARG) {\r
373                 if (debug) printDebug("Rejecting ", member, args);\r
374                 ambiguousMethods.removeElementAt(i);\r
375             }\r
376             else {\r
377                 if (debug) printDebug("UNRESOLVED: ", member, args);\r
378             }\r
379         }\r
380 \r
381         if (ambiguousMethods.size() > 0) {\r
382             // PENDING: report remaining ambiguity\r
383             StringBuffer buf = new StringBuffer();\r
384             boolean isCtor = (bestFit instanceof Constructor);\r
385 \r
386             ambiguousMethods.addElement(bestFit);\r
387 \r
388             for (int i = 0; i < ambiguousMethods.size(); i++) {\r
389                 if (i != 0) {\r
390                     buf.append(", ");\r
391                 }\r
392                 Member member = (Member)ambiguousMethods.elementAt(i);\r
393                 if (!isCtor) {\r
394                     Class rtnType = ((Method)member).getReturnType();\r
395                     buf.append(rtnType);\r
396                     buf.append(' ');\r
397                 }\r
398                 buf.append(NativeJavaMethod.signature(member));\r
399             }\r
400 \r
401             String errMsg;\r
402             if (isCtor) {\r
403                 Object errArgs[] = { \r
404                     bestFit.getName(), \r
405                     NativeJavaMethod.scriptSignature(args),\r
406                     buf.toString()\r
407                 };\r
408                 errMsg = \r
409                     Context.getMessage("msg.constructor.ambiguous", errArgs);\r
410             }\r
411             else {\r
412                 Object errArgs[] = { \r
413                     bestFit.getDeclaringClass().getName(), \r
414                     bestFit.getName(), \r
415                     NativeJavaMethod.scriptSignature(args),\r
416                     buf.toString()\r
417                 };\r
418                 errMsg = Context.getMessage("msg.method.ambiguous", errArgs);\r
419             }\r
420 \r
421             throw \r
422                 Context.reportRuntimeError(errMsg);\r
423         }\r
424 \r
425         return bestFit;\r
426     }\r
427     \r
428     /** Types are equal */\r
429     static final int PREFERENCE_EQUAL      = 0;\r
430     static final int PREFERENCE_FIRST_ARG  = 1;\r
431     static final int PREFERENCE_SECOND_ARG = 2;\r
432     /** No clear "easy" conversion */\r
433     static final int PREFERENCE_AMBIGUOUS  = 3;\r
434 \r
435     /**\r
436      * Determine which of two signatures is the closer fit.\r
437      * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, \r
438      * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS.\r
439      */\r
440     public static int preferSignature(Object[] args, \r
441                                       Class[] sig1, Class[] sig2) \r
442     {\r
443         int preference = 0;\r
444 \r
445         for (int j = 0; j < args.length; j++) {\r
446             Class type1 = sig1[j];\r
447             Class type2 = sig2[j];\r
448 \r
449             if (type1 == type2) {\r
450                 continue;\r
451             }\r
452 \r
453             preference |=\r
454                 NativeJavaMethod.preferConversion(args[j], \r
455                                                   type1,\r
456                                                   type2);\r
457 \r
458             if (preference == PREFERENCE_AMBIGUOUS) {\r
459                 break;\r
460             }\r
461         }\r
462         return preference;\r
463     }\r
464 \r
465 \r
466     /**\r
467      * Determine which of two types is the easier conversion.\r
468      * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, \r
469      * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS.\r
470      */\r
471     public static int preferConversion(Object fromObj, \r
472                                        Class toClass1, Class toClass2) {\r
473 \r
474         int rank1  = \r
475             NativeJavaObject.getConversionWeight(fromObj, toClass1);\r
476         int rank2 = \r
477             NativeJavaObject.getConversionWeight(fromObj, toClass2);\r
478 \r
479         if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL && \r
480             rank2 == NativeJavaObject.CONVERSION_NONTRIVIAL) {\r
481 \r
482             if (toClass1.isAssignableFrom(toClass2)) {\r
483                 return PREFERENCE_SECOND_ARG;\r
484             }\r
485             else if (toClass2.isAssignableFrom(toClass1)) {\r
486                 return PREFERENCE_FIRST_ARG;\r
487             }\r
488         }\r
489         else {\r
490             if (rank1 < rank2) {\r
491                 return PREFERENCE_FIRST_ARG;\r
492             }\r
493             else if (rank1 > rank2) {\r
494                 return PREFERENCE_SECOND_ARG;\r
495             }\r
496         }\r
497         return PREFERENCE_AMBIGUOUS;\r
498     }\r
499 \r
500     Method[] getMethods() {\r
501         return methods; \r
502     }\r
503 \r
504     private static final boolean debug = false;\r
505 \r
506     private static void printDebug(String msg, Member member, Object[] args) {\r
507         if (debug) {\r
508             System.err.println(" ----- " + msg + \r
509                                member.getDeclaringClass().getName() +\r
510                                "." + signature(member) +\r
511                                " for arguments (" + scriptSignature(args) + ")");\r
512         }\r
513     }\r
514 \r
515     Method methods[];\r
516 }\r
517 \r