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-2000
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Norris Boyd
026: * Igor Bukanov
027: * David C. Navas
028: * Ted Neward
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: // API class
043: package org.mozilla.javascript;
044:
045: import java.lang.reflect.*;
046: import java.io.*;
047:
048: public class FunctionObject extends BaseFunction {
049: static final long serialVersionUID = -5332312783643935019L;
050:
051: /**
052: * Create a JavaScript function object from a Java method.
053: *
054: * <p>The <code>member</code> argument must be either a java.lang.reflect.Method
055: * or a java.lang.reflect.Constructor and must match one of two forms.<p>
056: *
057: * The first form is a member with zero or more parameters
058: * of the following types: Object, String, boolean, Scriptable,
059: * int, or double. The Long type is not supported
060: * because the double representation of a long (which is the
061: * EMCA-mandated storage type for Numbers) may lose precision.
062: * If the member is a Method, the return value must be void or one
063: * of the types allowed for parameters.<p>
064: *
065: * The runtime will perform appropriate conversions based
066: * upon the type of the parameter. A parameter type of
067: * Object specifies that no conversions are to be done. A parameter
068: * of type String will use Context.toString to convert arguments.
069: * Similarly, parameters of type double, boolean, and Scriptable
070: * will cause Context.toNumber, Context.toBoolean, and
071: * Context.toObject, respectively, to be called.<p>
072: *
073: * If the method is not static, the Java 'this' value will
074: * correspond to the JavaScript 'this' value. Any attempt
075: * to call the function with a 'this' value that is not
076: * of the right Java type will result in an error.<p>
077: *
078: * The second form is the variable arguments (or "varargs")
079: * form. If the FunctionObject will be used as a constructor,
080: * the member must have the following parameters
081: * <pre>
082: * (Context cx, Object[] args, Function ctorObj,
083: * boolean inNewExpr)</pre>
084: * and if it is a Method, be static and return an Object result.<p>
085: *
086: * Otherwise, if the FunctionObject will <i>not</i> be used to define a
087: * constructor, the member must be a static Method with parameters
088: * (Context cx, Scriptable thisObj, Object[] args,
089: * Function funObj) </pre>
090: * <pre>
091: * and an Object result.<p>
092: *
093: * When the function varargs form is called as part of a function call,
094: * the <code>args</code> parameter contains the
095: * arguments, with <code>thisObj</code>
096: * set to the JavaScript 'this' value. <code>funObj</code>
097: * is the function object for the invoked function.<p>
098: *
099: * When the constructor varargs form is called or invoked while evaluating
100: * a <code>new</code> expression, <code>args</code> contains the
101: * arguments, <code>ctorObj</code> refers to this FunctionObject, and
102: * <code>inNewExpr</code> is true if and only if a <code>new</code>
103: * expression caused the call. This supports defining a function that
104: * has different behavior when called as a constructor than when
105: * invoked as a normal function call. (For example, the Boolean
106: * constructor, when called as a function,
107: * will convert to boolean rather than creating a new object.)<p>
108: *
109: * @param name the name of the function
110: * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor
111: * that defines the object
112: * @param scope enclosing scope of function
113: * @see org.mozilla.javascript.Scriptable
114: */
115: public FunctionObject(String name, Member methodOrConstructor,
116: Scriptable scope) {
117: if (methodOrConstructor instanceof Constructor) {
118: member = new MemberBox((Constructor) methodOrConstructor);
119: isStatic = true; // well, doesn't take a 'this'
120: } else {
121: member = new MemberBox((Method) methodOrConstructor);
122: isStatic = member.isStatic();
123: }
124: String methodName = member.getName();
125: this .functionName = name;
126: Class[] types = member.argTypes;
127: int arity = types.length;
128: if (arity == 4 && (types[1].isArray() || types[2].isArray())) {
129: // Either variable args or an error.
130: if (types[1].isArray()) {
131: if (!isStatic
132: || types[0] != ScriptRuntime.ContextClass
133: || types[1].getComponentType() != ScriptRuntime.ObjectClass
134: || types[2] != ScriptRuntime.FunctionClass
135: || types[3] != Boolean.TYPE) {
136: throw Context.reportRuntimeError1(
137: "msg.varargs.ctor", methodName);
138: }
139: parmsLength = VARARGS_CTOR;
140: } else {
141: if (!isStatic
142: || types[0] != ScriptRuntime.ContextClass
143: || types[1] != ScriptRuntime.ScriptableClass
144: || types[2].getComponentType() != ScriptRuntime.ObjectClass
145: || types[3] != ScriptRuntime.FunctionClass) {
146: throw Context.reportRuntimeError1(
147: "msg.varargs.fun", methodName);
148: }
149: parmsLength = VARARGS_METHOD;
150: }
151: } else {
152: parmsLength = arity;
153: if (arity > 0) {
154: typeTags = new byte[arity];
155: for (int i = 0; i != arity; ++i) {
156: int tag = getTypeTag(types[i]);
157: if (tag == JAVA_UNSUPPORTED_TYPE) {
158: throw Context.reportRuntimeError2(
159: "msg.bad.parms", types[i].getName(),
160: methodName);
161: }
162: typeTags[i] = (byte) tag;
163: }
164: }
165: }
166:
167: if (member.isMethod()) {
168: Method method = member.method();
169: Class returnType = method.getReturnType();
170: if (returnType == Void.TYPE) {
171: hasVoidReturn = true;
172: } else {
173: returnTypeTag = getTypeTag(returnType);
174: }
175: } else {
176: Class ctorType = member.getDeclaringClass();
177: if (!ScriptRuntime.ScriptableClass
178: .isAssignableFrom(ctorType)) {
179: throw Context.reportRuntimeError1(
180: "msg.bad.ctor.return", ctorType.getName());
181: }
182: }
183:
184: ScriptRuntime.setFunctionProtoAndParent(this , scope);
185: }
186:
187: /**
188: * @return One of <tt>JAVA_*_TYPE</tt> constants to indicate desired type
189: * or {@link #JAVA_UNSUPPORTED_TYPE} if the convertion is not
190: * possible
191: */
192: public static int getTypeTag(Class type) {
193: if (type == ScriptRuntime.StringClass)
194: return JAVA_STRING_TYPE;
195: if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE)
196: return JAVA_INT_TYPE;
197: if (type == ScriptRuntime.BooleanClass || type == Boolean.TYPE)
198: return JAVA_BOOLEAN_TYPE;
199: if (type == ScriptRuntime.DoubleClass || type == Double.TYPE)
200: return JAVA_DOUBLE_TYPE;
201: if (ScriptRuntime.ScriptableClass.isAssignableFrom(type))
202: return JAVA_SCRIPTABLE_TYPE;
203: if (type == ScriptRuntime.ObjectClass)
204: return JAVA_OBJECT_TYPE;
205:
206: // Note that the long type is not supported; see the javadoc for
207: // the constructor for this class
208:
209: return JAVA_UNSUPPORTED_TYPE;
210: }
211:
212: public static Object convertArg(Context cx, Scriptable scope,
213: Object arg, int typeTag) {
214: switch (typeTag) {
215: case JAVA_STRING_TYPE:
216: if (arg instanceof String)
217: return arg;
218: return ScriptRuntime.toString(arg);
219: case JAVA_INT_TYPE:
220: if (arg instanceof Integer)
221: return arg;
222: return new Integer(ScriptRuntime.toInt32(arg));
223: case JAVA_BOOLEAN_TYPE:
224: if (arg instanceof Boolean)
225: return arg;
226: return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE
227: : Boolean.FALSE;
228: case JAVA_DOUBLE_TYPE:
229: if (arg instanceof Double)
230: return arg;
231: return new Double(ScriptRuntime.toNumber(arg));
232: case JAVA_SCRIPTABLE_TYPE:
233: if (arg instanceof Scriptable)
234: return arg;
235: return ScriptRuntime.toObject(cx, scope, arg);
236: case JAVA_OBJECT_TYPE:
237: return arg;
238: default:
239: throw new IllegalArgumentException();
240: }
241: }
242:
243: /**
244: * Return the value defined by the method used to construct the object
245: * (number of parameters of the method, or 1 if the method is a "varargs"
246: * form).
247: */
248: public int getArity() {
249: return parmsLength < 0 ? 1 : parmsLength;
250: }
251:
252: /**
253: * Return the same value as {@link #getArity()}.
254: */
255: public int getLength() {
256: return getArity();
257: }
258:
259: public String getFunctionName() {
260: return (functionName == null) ? "" : functionName;
261: }
262:
263: /**
264: * Get Java method or constructor this function represent.
265: */
266: public Member getMethodOrConstructor() {
267: if (member.isMethod()) {
268: return member.method();
269: } else {
270: return member.ctor();
271: }
272: }
273:
274: static Method findSingleMethod(Method[] methods, String name) {
275: Method found = null;
276: for (int i = 0, N = methods.length; i != N; ++i) {
277: Method method = methods[i];
278: if (method != null && name.equals(method.getName())) {
279: if (found != null) {
280: throw Context.reportRuntimeError2(
281: "msg.no.overload", name, method
282: .getDeclaringClass().getName());
283: }
284: found = method;
285: }
286: }
287: return found;
288: }
289:
290: /**
291: * Returns all public methods declared by the specified class. This excludes
292: * inherited methods.
293: *
294: * @param clazz the class from which to pull public declared methods
295: * @return the public methods declared in the specified class
296: * @see Class#getDeclaredMethods()
297: */
298: static Method[] getMethodList(Class clazz) {
299: Method[] methods = null;
300: try {
301: // getDeclaredMethods may be rejected by the security manager
302: // but getMethods is more expensive
303: if (!sawSecurityException)
304: methods = clazz.getDeclaredMethods();
305: } catch (SecurityException e) {
306: // If we get an exception once, give up on getDeclaredMethods
307: sawSecurityException = true;
308: }
309: if (methods == null) {
310: methods = clazz.getMethods();
311: }
312: int count = 0;
313: for (int i = 0; i < methods.length; i++) {
314: if (sawSecurityException ? methods[i].getDeclaringClass() != clazz
315: : !Modifier.isPublic(methods[i].getModifiers())) {
316: methods[i] = null;
317: } else {
318: count++;
319: }
320: }
321: Method[] result = new Method[count];
322: int j = 0;
323: for (int i = 0; i < methods.length; i++) {
324: if (methods[i] != null)
325: result[j++] = methods[i];
326: }
327: return result;
328: }
329:
330: /**
331: * Define this function as a JavaScript constructor.
332: * <p>
333: * Sets up the "prototype" and "constructor" properties. Also
334: * calls setParent and setPrototype with appropriate values.
335: * Then adds the function object as a property of the given scope, using
336: * <code>prototype.getClassName()</code>
337: * as the name of the property.
338: *
339: * @param scope the scope in which to define the constructor (typically
340: * the global object)
341: * @param prototype the prototype object
342: * @see org.mozilla.javascript.Scriptable#setParentScope
343: * @see org.mozilla.javascript.Scriptable#setPrototype
344: * @see org.mozilla.javascript.Scriptable#getClassName
345: */
346: public void addAsConstructor(Scriptable scope, Scriptable prototype) {
347: initAsConstructor(scope, prototype);
348: defineProperty(scope, prototype.getClassName(), this ,
349: ScriptableObject.DONTENUM);
350: }
351:
352: void initAsConstructor(Scriptable scope, Scriptable prototype) {
353: ScriptRuntime.setFunctionProtoAndParent(this , scope);
354: setImmunePrototypeProperty(prototype);
355:
356: prototype.setParentScope(this );
357:
358: defineProperty(prototype, "constructor", this ,
359: ScriptableObject.DONTENUM | ScriptableObject.PERMANENT
360: | ScriptableObject.READONLY);
361: setParentScope(scope);
362: }
363:
364: /**
365: * @deprecated Use {@link #getTypeTag(Class)}
366: * and {@link #convertArg(Context, Scriptable, Object, int)}
367: * for type convertion.
368: */
369: public static Object convertArg(Context cx, Scriptable scope,
370: Object arg, Class desired) {
371: int tag = getTypeTag(desired);
372: if (tag == JAVA_UNSUPPORTED_TYPE) {
373: throw Context.reportRuntimeError1("msg.cant.convert",
374: desired.getName());
375: }
376: return convertArg(cx, scope, arg, tag);
377: }
378:
379: /**
380: * Performs conversions on argument types if needed and
381: * invokes the underlying Java method or constructor.
382: * <p>
383: * Implements Function.call.
384: *
385: * @see org.mozilla.javascript.Function#call(
386: * Context, Scriptable, Scriptable, Object[])
387: */
388: public Object call(Context cx, Scriptable scope,
389: Scriptable this Obj, Object[] args) {
390: Object result;
391: boolean checkMethodResult = false;
392:
393: if (parmsLength < 0) {
394: if (parmsLength == VARARGS_METHOD) {
395: Object[] invokeArgs = { cx, this Obj, args, this };
396: result = member.invoke(null, invokeArgs);
397: checkMethodResult = true;
398: } else {
399: boolean inNewExpr = (this Obj == null);
400: Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE;
401: Object[] invokeArgs = { cx, args, this , b };
402: result = (member.isCtor()) ? member
403: .newInstance(invokeArgs) : member.invoke(null,
404: invokeArgs);
405: }
406:
407: } else {
408: if (!isStatic) {
409: Class clazz = member.getDeclaringClass();
410: if (!clazz.isInstance(this Obj)) {
411: boolean compatible = false;
412: if (this Obj == scope) {
413: Scriptable parentScope = getParentScope();
414: if (scope != parentScope) {
415: // Call with dynamic scope for standalone function,
416: // use parentScope as thisObj
417: compatible = clazz.isInstance(parentScope);
418: if (compatible) {
419: this Obj = parentScope;
420: }
421: }
422: }
423: if (!compatible) {
424: // Couldn't find an object to call this on.
425: throw ScriptRuntime.typeError1(
426: "msg.incompat.call", functionName);
427: }
428: }
429: }
430:
431: Object[] invokeArgs;
432: if (parmsLength == args.length) {
433: // Do not allocate new argument array if java arguments are
434: // the same as the original js ones.
435: invokeArgs = args;
436: for (int i = 0; i != parmsLength; ++i) {
437: Object arg = args[i];
438: Object converted = convertArg(cx, scope, arg,
439: typeTags[i]);
440: if (arg != converted) {
441: if (invokeArgs == args) {
442: invokeArgs = args.clone();
443: }
444: invokeArgs[i] = converted;
445: }
446: }
447: } else if (parmsLength == 0) {
448: invokeArgs = ScriptRuntime.emptyArgs;
449: } else {
450: invokeArgs = new Object[parmsLength];
451: for (int i = 0; i != parmsLength; ++i) {
452: Object arg = (i < args.length) ? args[i]
453: : Undefined.instance;
454: invokeArgs[i] = convertArg(cx, scope, arg,
455: typeTags[i]);
456: }
457: }
458:
459: if (member.isMethod()) {
460: result = member.invoke(this Obj, invokeArgs);
461: checkMethodResult = true;
462: } else {
463: result = member.newInstance(invokeArgs);
464: }
465:
466: }
467:
468: if (checkMethodResult) {
469: if (hasVoidReturn) {
470: result = Undefined.instance;
471: } else if (returnTypeTag == JAVA_UNSUPPORTED_TYPE) {
472: result = cx.getWrapFactory().wrap(cx, scope, result,
473: null);
474: }
475: // XXX: the code assumes that if returnTypeTag == JAVA_OBJECT_TYPE
476: // then the Java method did a proper job of converting the
477: // result to JS primitive or Scriptable to avoid
478: // potentially costly Context.javaToJS call.
479: }
480:
481: return result;
482: }
483:
484: /**
485: * Return new {@link Scriptable} instance using the default
486: * constructor for the class of the underlying Java method.
487: * Return null to indicate that the call method should be used to create
488: * new objects.
489: */
490: public Scriptable createObject(Context cx, Scriptable scope) {
491: if (member.isCtor() || parmsLength == VARARGS_CTOR) {
492: return null;
493: }
494: Scriptable result;
495: try {
496: result = (Scriptable) member.getDeclaringClass()
497: .newInstance();
498: } catch (Exception ex) {
499: throw Context.throwAsScriptRuntimeEx(ex);
500: }
501:
502: result.setPrototype(getClassPrototype());
503: result.setParentScope(getParentScope());
504: return result;
505: }
506:
507: boolean isVarArgsMethod() {
508: return parmsLength == VARARGS_METHOD;
509: }
510:
511: boolean isVarArgsConstructor() {
512: return parmsLength == VARARGS_CTOR;
513: }
514:
515: private void readObject(ObjectInputStream in) throws IOException,
516: ClassNotFoundException {
517: in.defaultReadObject();
518: if (parmsLength > 0) {
519: Class[] types = member.argTypes;
520: typeTags = new byte[parmsLength];
521: for (int i = 0; i != parmsLength; ++i) {
522: typeTags[i] = (byte) getTypeTag(types[i]);
523: }
524: }
525: if (member.isMethod()) {
526: Method method = member.method();
527: Class returnType = method.getReturnType();
528: if (returnType == Void.TYPE) {
529: hasVoidReturn = true;
530: } else {
531: returnTypeTag = getTypeTag(returnType);
532: }
533: }
534: }
535:
536: private static final short VARARGS_METHOD = -1;
537: private static final short VARARGS_CTOR = -2;
538:
539: private static boolean sawSecurityException;
540:
541: public static final int JAVA_UNSUPPORTED_TYPE = 0;
542: public static final int JAVA_STRING_TYPE = 1;
543: public static final int JAVA_INT_TYPE = 2;
544: public static final int JAVA_BOOLEAN_TYPE = 3;
545: public static final int JAVA_DOUBLE_TYPE = 4;
546: public static final int JAVA_SCRIPTABLE_TYPE = 5;
547: public static final int JAVA_OBJECT_TYPE = 6;
548:
549: MemberBox member;
550: private String functionName;
551: private transient byte[] typeTags;
552: private int parmsLength;
553: private transient boolean hasVoidReturn;
554: private transient int returnTypeTag;
555: private boolean isStatic;
556: }
|