001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Norris Boyd
026: * Frank Mitchell
027: * Mike Shaver
028: * Ulrike Mueller <umueller@demandware.com>
029: *
030: * Alternatively, the contents of this file may be used under the terms of
031: * the GNU General Public License Version 2 or later (the "GPL"), in which
032: * case the provisions of the GPL are applicable instead of those above. If
033: * you wish to allow use of your version of this file only under the terms of
034: * the GPL and not to allow others to use your version of this file under the
035: * MPL, indicate your decision by deleting the provisions above and replacing
036: * them with the notice and other provisions required by the GPL. If you do
037: * not delete the provisions above, a recipient may use your version of this
038: * file under either the MPL or the GPL.
039: *
040: * ***** END LICENSE BLOCK ***** */
041:
042: package org.mozilla.javascript;
043:
044: import java.lang.reflect.*;
045:
046: /**
047: * This class reflects Java methods into the JavaScript environment and
048: * handles overloading of methods.
049: *
050: * @author Mike Shaver
051: * @see NativeJavaArray
052: * @see NativeJavaPackage
053: * @see NativeJavaClass
054: */
055:
056: public class NativeJavaMethod extends BaseFunction {
057: static final long serialVersionUID = -3440381785576412928L;
058:
059: NativeJavaMethod(MemberBox[] methods) {
060: this .functionName = methods[0].getName();
061: this .methods = methods;
062: }
063:
064: NativeJavaMethod(MemberBox method, String name) {
065: this .functionName = name;
066: this .methods = new MemberBox[] { method };
067: }
068:
069: public NativeJavaMethod(Method method, String name) {
070: this (new MemberBox(method), name);
071: }
072:
073: public String getFunctionName() {
074: return functionName;
075: }
076:
077: static String scriptSignature(Object[] values) {
078: StringBuffer sig = new StringBuffer();
079: for (int i = 0; i != values.length; ++i) {
080: Object value = values[i];
081:
082: String s;
083: if (value == null) {
084: s = "null";
085: } else if (value instanceof Boolean) {
086: s = "boolean";
087: } else if (value instanceof String) {
088: s = "string";
089: } else if (value instanceof Number) {
090: s = "number";
091: } else if (value instanceof Scriptable) {
092: if (value instanceof Undefined) {
093: s = "undefined";
094: } else if (value instanceof Wrapper) {
095: Object wrapped = ((Wrapper) value).unwrap();
096: s = wrapped.getClass().getName();
097: } else if (value instanceof Function) {
098: s = "function";
099: } else {
100: s = "object";
101: }
102: } else {
103: s = JavaMembers.javaSignature(value.getClass());
104: }
105:
106: if (i != 0) {
107: sig.append(',');
108: }
109: sig.append(s);
110: }
111: return sig.toString();
112: }
113:
114: String decompile(int indent, int flags) {
115: StringBuffer sb = new StringBuffer();
116: boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
117: if (!justbody) {
118: sb.append("function ");
119: sb.append(getFunctionName());
120: sb.append("() {");
121: }
122: sb.append("/*\n");
123: sb.append(toString());
124: sb.append(justbody ? "*/\n" : "*/}\n");
125: return sb.toString();
126: }
127:
128: public String toString() {
129: StringBuffer sb = new StringBuffer();
130: for (int i = 0, N = methods.length; i != N; ++i) {
131: Method method = methods[i].method();
132: sb
133: .append(JavaMembers.javaSignature(method
134: .getReturnType()));
135: sb.append(' ');
136: sb.append(method.getName());
137: sb.append(JavaMembers
138: .liveConnectSignature(methods[i].argTypes));
139: sb.append('\n');
140: }
141: return sb.toString();
142: }
143:
144: public Object call(Context cx, Scriptable scope,
145: Scriptable this Obj, Object[] args) {
146: // Find a method that matches the types given.
147: if (methods.length == 0) {
148: throw new RuntimeException("No methods defined for call");
149: }
150:
151: int index = findFunction(cx, methods, args);
152: if (index < 0) {
153: Class c = methods[0].method().getDeclaringClass();
154: String sig = c.getName() + '.' + getFunctionName() + '('
155: + scriptSignature(args) + ')';
156: throw Context.reportRuntimeError1(
157: "msg.java.no_such_method", sig);
158: }
159:
160: MemberBox meth = methods[index];
161: Class[] argTypes = meth.argTypes;
162:
163: if (meth.vararg) {
164: // marshall the explicit parameters
165: Object[] newArgs = new Object[argTypes.length];
166: for (int i = 0; i < argTypes.length - 1; i++) {
167: newArgs[i] = Context.jsToJava(args[i], argTypes[i]);
168: }
169:
170: Object varArgs;
171:
172: // Handle special situation where a single variable parameter
173: // is given and it is a Java or ECMA array or is null.
174: if (args.length == argTypes.length
175: && (args[args.length - 1] == null
176: || args[args.length - 1] instanceof NativeArray || args[args.length - 1] instanceof NativeJavaArray)) {
177: // convert the ECMA array into a native array
178: varArgs = Context.jsToJava(args[args.length - 1],
179: argTypes[argTypes.length - 1]);
180: } else {
181: // marshall the variable parameters
182: Class componentType = argTypes[argTypes.length - 1]
183: .getComponentType();
184: varArgs = Array.newInstance(componentType, args.length
185: - argTypes.length + 1);
186: for (int i = 0; i < Array.getLength(varArgs); i++) {
187: Object value = Context.jsToJava(
188: args[argTypes.length - 1 + i],
189: componentType);
190: Array.set(varArgs, i, value);
191: }
192: }
193:
194: // add varargs
195: newArgs[argTypes.length - 1] = varArgs;
196: // replace the original args with the new one
197: args = newArgs;
198: } else {
199: // First, we marshall the args.
200: Object[] origArgs = args;
201: for (int i = 0; i < args.length; i++) {
202: Object arg = args[i];
203: Object coerced = Context.jsToJava(arg, argTypes[i]);
204: if (coerced != arg) {
205: if (origArgs == args) {
206: args = args.clone();
207: }
208: args[i] = coerced;
209: }
210: }
211: }
212: Object javaObject;
213: if (meth.isStatic()) {
214: javaObject = null; // don't need an object
215: } else {
216: Scriptable o = this Obj;
217: Class c = meth.getDeclaringClass();
218: for (;;) {
219: if (o == null) {
220: throw Context.reportRuntimeError3(
221: "msg.nonjava.method", getFunctionName(),
222: ScriptRuntime.toString(this Obj), c
223: .getName());
224: }
225: if (o instanceof Wrapper) {
226: javaObject = ((Wrapper) o).unwrap();
227: if (c.isInstance(javaObject)) {
228: break;
229: }
230: }
231: o = o.getPrototype();
232: }
233: }
234: if (debug) {
235: printDebug("Calling ", meth, args);
236: }
237:
238: Object retval = meth.invoke(javaObject, args);
239: Class staticType = meth.method().getReturnType();
240:
241: if (debug) {
242: Class actualType = (retval == null) ? null : retval
243: .getClass();
244: System.err.println(" ----- Returned " + retval
245: + " actual = " + actualType + " expect = "
246: + staticType);
247: }
248:
249: Object wrapped = cx.getWrapFactory().wrap(cx, scope, retval,
250: staticType);
251: if (debug) {
252: Class actualType = (wrapped == null) ? null : wrapped
253: .getClass();
254: System.err.println(" ----- Wrapped as " + wrapped
255: + " class = " + actualType);
256: }
257:
258: if (wrapped == null && staticType == Void.TYPE) {
259: wrapped = Undefined.instance;
260: }
261: return wrapped;
262: }
263:
264: /**
265: * Find the index of the correct function to call given the set of methods
266: * or constructors and the arguments.
267: * If no function can be found to call, return -1.
268: */
269: static int findFunction(Context cx, MemberBox[] methodsOrCtors,
270: Object[] args) {
271: if (methodsOrCtors.length == 0) {
272: return -1;
273: } else if (methodsOrCtors.length == 1) {
274: MemberBox member = methodsOrCtors[0];
275: Class[] argTypes = member.argTypes;
276: int alength = argTypes.length;
277:
278: if (member.vararg) {
279: alength--;
280: if (alength > args.length) {
281: return -1;
282: }
283: } else {
284: if (alength != args.length) {
285: return -1;
286: }
287: }
288: for (int j = 0; j != alength; ++j) {
289: if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
290: if (debug)
291: printDebug("Rejecting (args can't convert) ",
292: member, args);
293: return -1;
294: }
295: }
296: if (debug)
297: printDebug("Found ", member, args);
298: return 0;
299: }
300:
301: int firstBestFit = -1;
302: int[] extraBestFits = null;
303: int extraBestFitsCount = 0;
304:
305: search: for (int i = 0; i < methodsOrCtors.length; i++) {
306: MemberBox member = methodsOrCtors[i];
307: Class[] argTypes = member.argTypes;
308: int alength = argTypes.length;
309: if (member.vararg) {
310: alength--;
311: if (alength > args.length) {
312: continue search;
313: }
314: } else {
315: if (alength != args.length) {
316: continue search;
317: }
318: }
319: for (int j = 0; j < alength; j++) {
320: if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
321: if (debug)
322: printDebug("Rejecting (args can't convert) ",
323: member, args);
324: continue search;
325: }
326: }
327: if (firstBestFit < 0) {
328: if (debug)
329: printDebug("Found first applicable ", member, args);
330: firstBestFit = i;
331: } else {
332: // Compare with all currently fit methods.
333: // The loop starts from -1 denoting firstBestFit and proceed
334: // until extraBestFitsCount to avoid extraBestFits allocation
335: // in the most common case of no ambiguity
336: int betterCount = 0; // number of times member was prefered over
337: // best fits
338: int worseCount = 0; // number of times best fits were prefered
339: // over member
340: for (int j = -1; j != extraBestFitsCount; ++j) {
341: int bestFitIndex;
342: if (j == -1) {
343: bestFitIndex = firstBestFit;
344: } else {
345: bestFitIndex = extraBestFits[j];
346: }
347: MemberBox bestFit = methodsOrCtors[bestFitIndex];
348: if (cx
349: .hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
350: && (bestFit.member().getModifiers() & Modifier.PUBLIC) != (member
351: .member().getModifiers() & Modifier.PUBLIC)) {
352: // When FEATURE_ENHANCED_JAVA_ACCESS gives us access
353: // to non-public members, continue to prefer public
354: // methods in overloading
355: if ((bestFit.member().getModifiers() & Modifier.PUBLIC) == 0)
356: ++betterCount;
357: else
358: ++worseCount;
359: } else {
360: int preference = preferSignature(args,
361: argTypes, member.vararg,
362: bestFit.argTypes, bestFit.vararg);
363: if (preference == PREFERENCE_AMBIGUOUS) {
364: break;
365: } else if (preference == PREFERENCE_FIRST_ARG) {
366: ++betterCount;
367: } else if (preference == PREFERENCE_SECOND_ARG) {
368: ++worseCount;
369: } else {
370: if (preference != PREFERENCE_EQUAL)
371: Kit.codeBug();
372: // This should not happen in theory
373: // but on some JVMs, Class.getMethods will return all
374: // static methods of the class heirarchy, even if
375: // a derived class's parameters match exactly.
376: // We want to call the dervied class's method.
377: if (bestFit.isStatic()
378: && bestFit
379: .getDeclaringClass()
380: .isAssignableFrom(
381: member
382: .getDeclaringClass())) {
383: // On some JVMs, Class.getMethods will return all
384: // static methods of the class heirarchy, even if
385: // a derived class's parameters match exactly.
386: // We want to call the dervied class's method.
387: if (debug)
388: printDebug(
389: "Substituting (overridden static)",
390: member, args);
391: if (j == -1) {
392: firstBestFit = i;
393: } else {
394: extraBestFits[j] = i;
395: }
396: } else {
397: if (debug)
398: printDebug(
399: "Ignoring same signature member ",
400: member, args);
401: }
402: continue search;
403: }
404: }
405: }
406: if (betterCount == 1 + extraBestFitsCount) {
407: // member was prefered over all best fits
408: if (debug)
409: printDebug("New first applicable ", member,
410: args);
411: firstBestFit = i;
412: extraBestFitsCount = 0;
413: } else if (worseCount == 1 + extraBestFitsCount) {
414: // all best fits were prefered over member, ignore it
415: if (debug)
416: printDebug(
417: "Rejecting (all current bests better) ",
418: member, args);
419: } else {
420: // some ambiguity was present, add member to best fit set
421: if (debug)
422: printDebug("Added to best fit set ", member,
423: args);
424: if (extraBestFits == null) {
425: // Allocate maximum possible array
426: extraBestFits = new int[methodsOrCtors.length - 1];
427: }
428: extraBestFits[extraBestFitsCount] = i;
429: ++extraBestFitsCount;
430: }
431: }
432: }
433:
434: if (firstBestFit < 0) {
435: // Nothing was found
436: return -1;
437: } else if (extraBestFitsCount == 0) {
438: // single best fit
439: return firstBestFit;
440: }
441:
442: // report remaining ambiguity
443: StringBuffer buf = new StringBuffer();
444: for (int j = -1; j != extraBestFitsCount; ++j) {
445: int bestFitIndex;
446: if (j == -1) {
447: bestFitIndex = firstBestFit;
448: } else {
449: bestFitIndex = extraBestFits[j];
450: }
451: buf.append("\n ");
452: buf
453: .append(methodsOrCtors[bestFitIndex]
454: .toJavaDeclaration());
455: }
456:
457: MemberBox firstFitMember = methodsOrCtors[firstBestFit];
458: String memberName = firstFitMember.getName();
459: String memberClass = firstFitMember.getDeclaringClass()
460: .getName();
461:
462: if (methodsOrCtors[0].isMethod()) {
463: throw Context.reportRuntimeError3(
464: "msg.constructor.ambiguous", memberName,
465: scriptSignature(args), buf.toString());
466: } else {
467: throw Context.reportRuntimeError4("msg.method.ambiguous",
468: memberClass, memberName, scriptSignature(args), buf
469: .toString());
470: }
471: }
472:
473: /** Types are equal */
474: private static final int PREFERENCE_EQUAL = 0;
475: private static final int PREFERENCE_FIRST_ARG = 1;
476: private static final int PREFERENCE_SECOND_ARG = 2;
477: /** No clear "easy" conversion */
478: private static final int PREFERENCE_AMBIGUOUS = 3;
479:
480: /**
481: * Determine which of two signatures is the closer fit.
482: * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG,
483: * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS.
484: */
485: private static int preferSignature(Object[] args, Class[] sig1,
486: boolean vararg1, Class[] sig2, boolean vararg2) {
487: // TODO: This test is pretty primitive. It bascially prefers
488: // a matching no vararg method over a vararg method independent
489: // of the type conversion cost. This can lead to unexpected results.
490: int alength = args.length;
491: if (!vararg1 && vararg2) {
492: // prefer the no vararg signature
493: return PREFERENCE_FIRST_ARG;
494: } else if (vararg1 && !vararg2) {
495: // prefer the no vararg signature
496: return PREFERENCE_SECOND_ARG;
497: } else if (vararg1 && vararg2) {
498: if (sig1.length < sig2.length) {
499: // prefer the signature with more explicit types
500: return PREFERENCE_SECOND_ARG;
501: } else if (sig1.length > sig2.length) {
502: // prefer the signature with more explicit types
503: return PREFERENCE_FIRST_ARG;
504: } else {
505: // Both are varargs and have the same length, so make the
506: // decision with the explicit args.
507: alength = Math.min(args.length, sig1.length - 1);
508: }
509: }
510:
511: int totalPreference = 0;
512: for (int j = 0; j < alength; j++) {
513: Class type1 = sig1[j];
514: Class type2 = sig2[j];
515: if (type1 == type2) {
516: continue;
517: }
518: Object arg = args[j];
519:
520: // Determine which of type1, type2 is easier to convert from arg.
521:
522: int rank1 = NativeJavaObject
523: .getConversionWeight(arg, type1);
524: int rank2 = NativeJavaObject
525: .getConversionWeight(arg, type2);
526:
527: int preference;
528: if (rank1 < rank2) {
529: preference = PREFERENCE_FIRST_ARG;
530: } else if (rank1 > rank2) {
531: preference = PREFERENCE_SECOND_ARG;
532: } else {
533: // Equal ranks
534: if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL) {
535: if (type1.isAssignableFrom(type2)) {
536: preference = PREFERENCE_SECOND_ARG;
537: } else if (type2.isAssignableFrom(type1)) {
538: preference = PREFERENCE_FIRST_ARG;
539: } else {
540: preference = PREFERENCE_AMBIGUOUS;
541: }
542: } else {
543: preference = PREFERENCE_AMBIGUOUS;
544: }
545: }
546:
547: totalPreference |= preference;
548:
549: if (totalPreference == PREFERENCE_AMBIGUOUS) {
550: break;
551: }
552: }
553: return totalPreference;
554: }
555:
556: private static final boolean debug = false;
557:
558: private static void printDebug(String msg, MemberBox member,
559: Object[] args) {
560: if (debug) {
561: StringBuffer sb = new StringBuffer();
562: sb.append(" ----- ");
563: sb.append(msg);
564: sb.append(member.getDeclaringClass().getName());
565: sb.append('.');
566: if (member.isMethod()) {
567: sb.append(member.getName());
568: }
569: sb
570: .append(JavaMembers
571: .liveConnectSignature(member.argTypes));
572: sb.append(" for arguments (");
573: sb.append(scriptSignature(args));
574: sb.append(')');
575: System.out.println(sb);
576: }
577: }
578:
579: MemberBox[] methods;
580: private String functionName;
581: }
|