1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
\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
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
13 * The Original Code is Rhino code, released
\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
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
38 package org.mozilla.javascript;
\r
40 import java.lang.reflect.*;
\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
49 * @author Mike Shaver
\r
50 * @see NativeJavaArray
\r
51 * @see NativeJavaPackage
\r
52 * @see NativeJavaClass
\r
55 public class NativeJavaMethod extends NativeFunction implements Function {
\r
57 public NativeJavaMethod() {
\r
58 this.functionName = null;
\r
61 public NativeJavaMethod(Method[] methods) {
\r
62 this.methods = methods;
\r
63 this.functionName = methods[0].getName();
\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
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
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
87 static String scriptSignature(Object value) {
\r
88 if (value == null) {
\r
92 Class type = value.getClass();
\r
93 if (type == ScriptRuntime.UndefinedClass)
\r
95 if (type == ScriptRuntime.BooleanClass)
\r
97 if (type == ScriptRuntime.StringClass)
\r
99 if (ScriptRuntime.NumberClass.isAssignableFrom(type))
\r
101 if (value instanceof Wrapper) {
\r
102 return ((Wrapper)value).unwrap().getClass().getName();
\r
104 if (value instanceof Scriptable) {
\r
105 if (value instanceof Function)
\r
109 return javaSignature(type);
\r
113 static String scriptSignature(Object[] values) {
\r
114 StringBuffer sig = new StringBuffer();
\r
115 for (int i = 0; i < values.length; i++) {
\r
118 sig.append(scriptSignature(values[i]));
\r
120 return sig.toString();
\r
123 static String javaSignature(Class type) {
\r
124 if (type == null) {
\r
127 else if (type.isArray()) {
\r
128 return javaSignature(type.getComponentType()) + "[]";
\r
130 return type.getName();
\r
133 static String javaSignature(Class[] types) {
\r
134 StringBuffer sig = new StringBuffer();
\r
135 for (int i = 0; i < types.length; i++) {
\r
138 sig.append(javaSignature(types[i]));
\r
140 return sig.toString();
\r
143 static String signature(Member member) {
\r
144 Class paramTypes[];
\r
146 if (member instanceof Method) {
\r
147 paramTypes = ((Method) member).getParameterTypes();
\r
148 return member.getName() + "(" + javaSignature(paramTypes) + ")";
\r
151 paramTypes = ((Constructor) member).getParameterTypes();
\r
152 return "(" + javaSignature(paramTypes) + ")";
\r
156 public String decompile(Context cx, int indent, boolean justbody) {
\r
157 StringBuffer sb = new StringBuffer();
\r
159 sb.append("function ");
\r
160 sb.append(getFunctionName());
\r
165 sb.append(justbody ? "*/\n" : "*/}\n");
\r
166 return sb.toString();
\r
169 public String toString() {
\r
170 StringBuffer sb = new StringBuffer();
\r
172 return sb.toString();
\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
179 sb.append(signature(methods[i]));
\r
184 public Object call(Context cx, Scriptable scope, Scriptable thisObj,
\r
186 throws JavaScriptException
\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
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
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
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
210 if (Modifier.isStatic(meth.getModifiers())) {
\r
211 javaObject = null; // don't need an object
\r
213 Scriptable o = thisObj;
\r
214 while (!(o instanceof Wrapper)) {
\r
215 o = o.getPrototype();
\r
217 throw Context.reportRuntimeError1(
\r
218 "msg.nonjava.method", functionName);
\r
221 javaObject = ((Wrapper) o).unwrap();
\r
225 printDebug("Calling ", meth, args);
\r
228 Object retval = meth.invoke(javaObject, args);
\r
229 Class staticType = meth.getReturnType();
\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
238 Object wrapped = NativeJavaObject.wrap(scope, retval, staticType);
\r
241 Class actualType = (wrapped == null) ? null : wrapped.getClass();
\r
242 System.err.println(" ----- Wrapped as " + wrapped +
\r
243 " class = " + actualType);
\r
246 if (wrapped == Undefined.instance)
\r
248 if (wrapped == null && staticType == Void.TYPE)
\r
249 return Undefined.instance;
\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
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
263 static Member findFunction(Member[] methodsOrCtors, Object[] args) {
\r
264 if (methodsOrCtors.length == 0)
\r
266 boolean hasMethods = methodsOrCtors[0] instanceof Method;
\r
267 if (Context.useJSObject &&
\r
268 NativeJavaObject.jsObjectClass != null)
\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
277 catch (InvocationTargetException e) {
\r
278 // Just abandon conversion from JSObject
\r
280 catch (IllegalAccessException e) {
\r
281 // Just abandon conversion from JSObject
\r
285 Member bestFit = null;
\r
286 Class[] bestFitTypes = null;
\r
288 java.util.Vector ambiguousMethods = null;
\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
298 if (bestFitTypes == null) {
\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
307 if (j == paramTypes.length) {
\r
308 if (debug) printDebug("Found ", member, args);
\r
310 bestFitTypes = paramTypes;
\r
315 NativeJavaMethod.preferSignature(args,
\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
325 else if (preference == PREFERENCE_FIRST_ARG) {
\r
326 if (debug) printDebug("Substituting ", member, args);
\r
328 bestFitTypes = paramTypes;
\r
331 if (preference == PREFERENCE_EQUAL &&
\r
332 Modifier.isStatic(bestFit.getModifiers()) &&
\r
333 bestFit.getDeclaringClass().isAssignableFrom(
\r
334 member.getDeclaringClass()))
\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
343 bestFitTypes = paramTypes;
\r
345 if (debug) printDebug("Rejecting ", member, args);
\r
351 if (ambiguousMethods == null)
\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
362 NativeJavaMethod.preferSignature(args,
\r
366 if (preference == PREFERENCE_FIRST_ARG) {
\r
367 if (debug) printDebug("Substituting ", member, args);
\r
369 bestFitTypes = paramTypes;
\r
370 ambiguousMethods.removeElementAt(i);
\r
372 else if (preference == PREFERENCE_SECOND_ARG) {
\r
373 if (debug) printDebug("Rejecting ", member, args);
\r
374 ambiguousMethods.removeElementAt(i);
\r
377 if (debug) printDebug("UNRESOLVED: ", member, args);
\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
386 ambiguousMethods.addElement(bestFit);
\r
388 for (int i = 0; i < ambiguousMethods.size(); i++) {
\r
392 Member member = (Member)ambiguousMethods.elementAt(i);
\r
394 Class rtnType = ((Method)member).getReturnType();
\r
395 buf.append(rtnType);
\r
398 buf.append(NativeJavaMethod.signature(member));
\r
403 Object errArgs[] = {
\r
404 bestFit.getName(),
\r
405 NativeJavaMethod.scriptSignature(args),
\r
409 Context.getMessage("msg.constructor.ambiguous", errArgs);
\r
412 Object errArgs[] = {
\r
413 bestFit.getDeclaringClass().getName(),
\r
414 bestFit.getName(),
\r
415 NativeJavaMethod.scriptSignature(args),
\r
418 errMsg = Context.getMessage("msg.method.ambiguous", errArgs);
\r
422 Context.reportRuntimeError(errMsg);
\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
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
440 public static int preferSignature(Object[] args,
\r
441 Class[] sig1, Class[] sig2)
\r
443 int preference = 0;
\r
445 for (int j = 0; j < args.length; j++) {
\r
446 Class type1 = sig1[j];
\r
447 Class type2 = sig2[j];
\r
449 if (type1 == type2) {
\r
454 NativeJavaMethod.preferConversion(args[j],
\r
458 if (preference == PREFERENCE_AMBIGUOUS) {
\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
471 public static int preferConversion(Object fromObj,
\r
472 Class toClass1, Class toClass2) {
\r
475 NativeJavaObject.getConversionWeight(fromObj, toClass1);
\r
477 NativeJavaObject.getConversionWeight(fromObj, toClass2);
\r
479 if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL &&
\r
480 rank2 == NativeJavaObject.CONVERSION_NONTRIVIAL) {
\r
482 if (toClass1.isAssignableFrom(toClass2)) {
\r
483 return PREFERENCE_SECOND_ARG;
\r
485 else if (toClass2.isAssignableFrom(toClass1)) {
\r
486 return PREFERENCE_FIRST_ARG;
\r
490 if (rank1 < rank2) {
\r
491 return PREFERENCE_FIRST_ARG;
\r
493 else if (rank1 > rank2) {
\r
494 return PREFERENCE_SECOND_ARG;
\r
497 return PREFERENCE_AMBIGUOUS;
\r
500 Method[] getMethods() {
\r
504 private static final boolean debug = false;
\r
506 private static void printDebug(String msg, Member member, Object[] args) {
\r
508 System.err.println(" ----- " + msg +
\r
509 member.getDeclaringClass().getName() +
\r
510 "." + signature(member) +
\r
511 " for arguments (" + scriptSignature(args) + ")");
\r