diff options
Diffstat (limited to 'infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java')
-rw-r--r-- | infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java new file mode 100644 index 0000000..8fa4e68 --- /dev/null +++ b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java @@ -0,0 +1,569 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * David C. Navas + * Ted Neward + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +// API class + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.io.*; + +public class FunctionObject extends BaseFunction +{ + static final long serialVersionUID = -5332312783643935019L; + + /** + * Create a JavaScript function object from a Java method. + * + * <p>The <code>member</code> argument must be either a java.lang.reflect.Method + * or a java.lang.reflect.Constructor and must match one of two forms.<p> + * + * The first form is a member with zero or more parameters + * of the following types: Object, String, boolean, Scriptable, + * int, or double. The Long type is not supported + * because the double representation of a long (which is the + * EMCA-mandated storage type for Numbers) may lose precision. + * If the member is a Method, the return value must be void or one + * of the types allowed for parameters.<p> + * + * The runtime will perform appropriate conversions based + * upon the type of the parameter. A parameter type of + * Object specifies that no conversions are to be done. A parameter + * of type String will use Context.toString to convert arguments. + * Similarly, parameters of type double, boolean, and Scriptable + * will cause Context.toNumber, Context.toBoolean, and + * Context.toObject, respectively, to be called.<p> + * + * If the method is not static, the Java 'this' value will + * correspond to the JavaScript 'this' value. Any attempt + * to call the function with a 'this' value that is not + * of the right Java type will result in an error.<p> + * + * The second form is the variable arguments (or "varargs") + * form. If the FunctionObject will be used as a constructor, + * the member must have the following parameters + * <pre> + * (Context cx, Object[] args, Function ctorObj, + * boolean inNewExpr)</pre> + * and if it is a Method, be static and return an Object result.<p> + * + * Otherwise, if the FunctionObject will <i>not</i> be used to define a + * constructor, the member must be a static Method with parameters + * (Context cx, Scriptable thisObj, Object[] args, + * Function funObj) </pre> + * <pre> + * and an Object result.<p> + * + * When the function varargs form is called as part of a function call, + * the <code>args</code> parameter contains the + * arguments, with <code>thisObj</code> + * set to the JavaScript 'this' value. <code>funObj</code> + * is the function object for the invoked function.<p> + * + * When the constructor varargs form is called or invoked while evaluating + * a <code>new</code> expression, <code>args</code> contains the + * arguments, <code>ctorObj</code> refers to this FunctionObject, and + * <code>inNewExpr</code> is true if and only if a <code>new</code> + * expression caused the call. This supports defining a function that + * has different behavior when called as a constructor than when + * invoked as a normal function call. (For example, the Boolean + * constructor, when called as a function, + * will convert to boolean rather than creating a new object.)<p> + * + * @param name the name of the function + * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor + * that defines the object + * @param scope enclosing scope of function + * @see org.mozilla.javascript.Scriptable + */ + public FunctionObject(String name, Member methodOrConstructor, + Scriptable scope) + { + if (methodOrConstructor instanceof Constructor) { + member = new MemberBox((Constructor) methodOrConstructor); + isStatic = true; // well, doesn't take a 'this' + } else { + member = new MemberBox((Method) methodOrConstructor); + isStatic = member.isStatic(); + } + String methodName = member.getName(); + this.functionName = name; + Class[] types = member.argTypes; + int arity = types.length; + if (arity == 4 && (types[1].isArray() || types[2].isArray())) { + // Either variable args or an error. + if (types[1].isArray()) { + if (!isStatic || + types[0] != ScriptRuntime.ContextClass || + types[1].getComponentType() != ScriptRuntime.ObjectClass || + types[2] != ScriptRuntime.FunctionClass || + types[3] != Boolean.TYPE) + { + throw Context.reportRuntimeError1( + "msg.varargs.ctor", methodName); + } + parmsLength = VARARGS_CTOR; + } else { + if (!isStatic || + types[0] != ScriptRuntime.ContextClass || + types[1] != ScriptRuntime.ScriptableClass || + types[2].getComponentType() != ScriptRuntime.ObjectClass || + types[3] != ScriptRuntime.FunctionClass) + { + throw Context.reportRuntimeError1( + "msg.varargs.fun", methodName); + } + parmsLength = VARARGS_METHOD; + } + } else { + parmsLength = arity; + if (arity > 0) { + typeTags = new byte[arity]; + for (int i = 0; i != arity; ++i) { + int tag = getTypeTag(types[i]); + if (tag == JAVA_UNSUPPORTED_TYPE) { + throw Context.reportRuntimeError2( + "msg.bad.parms", types[i].getName(), methodName); + } + typeTags[i] = (byte)tag; + } + } + } + + if (member.isMethod()) { + Method method = member.method(); + Class returnType = method.getReturnType(); + if (returnType == Void.TYPE) { + hasVoidReturn = true; + } else { + returnTypeTag = getTypeTag(returnType); + } + } else { + Class ctorType = member.getDeclaringClass(); + if (!ScriptRuntime.ScriptableClass.isAssignableFrom(ctorType)) { + throw Context.reportRuntimeError1( + "msg.bad.ctor.return", ctorType.getName()); + } + } + + ScriptRuntime.setFunctionProtoAndParent(this, scope); + } + + /** + * @return One of <tt>JAVA_*_TYPE</tt> constants to indicate desired type + * or {@link #JAVA_UNSUPPORTED_TYPE} if the convertion is not + * possible + */ + public static int getTypeTag(Class type) + { + if (type == ScriptRuntime.StringClass) + return JAVA_STRING_TYPE; + if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) + return JAVA_INT_TYPE; + if (type == ScriptRuntime.BooleanClass || type == Boolean.TYPE) + return JAVA_BOOLEAN_TYPE; + if (type == ScriptRuntime.DoubleClass || type == Double.TYPE) + return JAVA_DOUBLE_TYPE; + if (ScriptRuntime.ScriptableClass.isAssignableFrom(type)) + return JAVA_SCRIPTABLE_TYPE; + if (type == ScriptRuntime.ObjectClass) + return JAVA_OBJECT_TYPE; + + // Note that the long type is not supported; see the javadoc for + // the constructor for this class + + return JAVA_UNSUPPORTED_TYPE; + } + + public static Object convertArg(Context cx, Scriptable scope, + Object arg, int typeTag) + { + switch (typeTag) { + case JAVA_STRING_TYPE: + if (arg instanceof String) + return arg; + return ScriptRuntime.toString(arg); + case JAVA_INT_TYPE: + if (arg instanceof Integer) + return arg; + return new Integer(ScriptRuntime.toInt32(arg)); + case JAVA_BOOLEAN_TYPE: + if (arg instanceof Boolean) + return arg; + return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE + : Boolean.FALSE; + case JAVA_DOUBLE_TYPE: + if (arg instanceof Double) + return arg; + return new Double(ScriptRuntime.toNumber(arg)); + case JAVA_SCRIPTABLE_TYPE: + if (arg instanceof Scriptable) + return arg; + return ScriptRuntime.toObject(cx, scope, arg); + case JAVA_OBJECT_TYPE: + return arg; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Return the value defined by the method used to construct the object + * (number of parameters of the method, or 1 if the method is a "varargs" + * form). + */ + public int getArity() { + return parmsLength < 0 ? 1 : parmsLength; + } + + /** + * Return the same value as {@link #getArity()}. + */ + public int getLength() { + return getArity(); + } + + public String getFunctionName() + { + return (functionName == null) ? "" : functionName; + } + + /** + * Get Java method or constructor this function represent. + */ + public Member getMethodOrConstructor() + { + if (member.isMethod()) { + return member.method(); + } else { + return member.ctor(); + } + } + + static Method findSingleMethod(Method[] methods, String name) + { + Method found = null; + for (int i = 0, N = methods.length; i != N; ++i) { + Method method = methods[i]; + if (method != null && name.equals(method.getName())) { + if (found != null) { + throw Context.reportRuntimeError2( + "msg.no.overload", name, + method.getDeclaringClass().getName()); + } + found = method; + } + } + return found; + } + + /** + * Returns all public methods declared by the specified class. This excludes + * inherited methods. + * + * @param clazz the class from which to pull public declared methods + * @return the public methods declared in the specified class + * @see Class#getDeclaredMethods() + */ + static Method[] getMethodList(Class clazz) { + Method[] methods = null; + try { + // getDeclaredMethods may be rejected by the security manager + // but getMethods is more expensive + if (!sawSecurityException) + methods = clazz.getDeclaredMethods(); + } catch (SecurityException e) { + // If we get an exception once, give up on getDeclaredMethods + sawSecurityException = true; + } + if (methods == null) { + methods = clazz.getMethods(); + } + int count = 0; + for (int i=0; i < methods.length; i++) { + if (sawSecurityException + ? methods[i].getDeclaringClass() != clazz + : !Modifier.isPublic(methods[i].getModifiers())) + { + methods[i] = null; + } else { + count++; + } + } + Method[] result = new Method[count]; + int j=0; + for (int i=0; i < methods.length; i++) { + if (methods[i] != null) + result[j++] = methods[i]; + } + return result; + } + + /** + * Define this function as a JavaScript constructor. + * <p> + * Sets up the "prototype" and "constructor" properties. Also + * calls setParent and setPrototype with appropriate values. + * Then adds the function object as a property of the given scope, using + * <code>prototype.getClassName()</code> + * as the name of the property. + * + * @param scope the scope in which to define the constructor (typically + * the global object) + * @param prototype the prototype object + * @see org.mozilla.javascript.Scriptable#setParentScope + * @see org.mozilla.javascript.Scriptable#setPrototype + * @see org.mozilla.javascript.Scriptable#getClassName + */ + public void addAsConstructor(Scriptable scope, Scriptable prototype) + { + initAsConstructor(scope, prototype); + defineProperty(scope, prototype.getClassName(), + this, ScriptableObject.DONTENUM); + } + + void initAsConstructor(Scriptable scope, Scriptable prototype) + { + ScriptRuntime.setFunctionProtoAndParent(this, scope); + setImmunePrototypeProperty(prototype); + + prototype.setParentScope(this); + + defineProperty(prototype, "constructor", this, + ScriptableObject.DONTENUM | + ScriptableObject.PERMANENT | + ScriptableObject.READONLY); + setParentScope(scope); + } + + /** + * @deprecated Use {@link #getTypeTag(Class)} + * and {@link #convertArg(Context, Scriptable, Object, int)} + * for type convertion. + */ + public static Object convertArg(Context cx, Scriptable scope, + Object arg, Class desired) + { + int tag = getTypeTag(desired); + if (tag == JAVA_UNSUPPORTED_TYPE) { + throw Context.reportRuntimeError1 + ("msg.cant.convert", desired.getName()); + } + return convertArg(cx, scope, arg, tag); + } + + /** + * Performs conversions on argument types if needed and + * invokes the underlying Java method or constructor. + * <p> + * Implements Function.call. + * + * @see org.mozilla.javascript.Function#call( + * Context, Scriptable, Scriptable, Object[]) + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + Object result; + boolean checkMethodResult = false; + + if (parmsLength < 0) { + if (parmsLength == VARARGS_METHOD) { + Object[] invokeArgs = { cx, thisObj, args, this }; + result = member.invoke(null, invokeArgs); + checkMethodResult = true; + } else { + boolean inNewExpr = (thisObj == null); + Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE; + Object[] invokeArgs = { cx, args, this, b }; + result = (member.isCtor()) + ? member.newInstance(invokeArgs) + : member.invoke(null, invokeArgs); + } + + } else { + if (!isStatic) { + Class clazz = member.getDeclaringClass(); + if (!clazz.isInstance(thisObj)) { + boolean compatible = false; + if (thisObj == scope) { + Scriptable parentScope = getParentScope(); + if (scope != parentScope) { + // Call with dynamic scope for standalone function, + // use parentScope as thisObj + compatible = clazz.isInstance(parentScope); + if (compatible) { + thisObj = parentScope; + } + } + } + if (!compatible) { + // Couldn't find an object to call this on. + throw ScriptRuntime.typeError1("msg.incompat.call", + functionName); + } + } + } + + Object[] invokeArgs; + if (parmsLength == args.length) { + // Do not allocate new argument array if java arguments are + // the same as the original js ones. + invokeArgs = args; + for (int i = 0; i != parmsLength; ++i) { + Object arg = args[i]; + Object converted = convertArg(cx, scope, arg, typeTags[i]); + if (arg != converted) { + if (invokeArgs == args) { + invokeArgs = args.clone(); + } + invokeArgs[i] = converted; + } + } + } else if (parmsLength == 0) { + invokeArgs = ScriptRuntime.emptyArgs; + } else { + invokeArgs = new Object[parmsLength]; + for (int i = 0; i != parmsLength; ++i) { + Object arg = (i < args.length) + ? args[i] + : Undefined.instance; + invokeArgs[i] = convertArg(cx, scope, arg, typeTags[i]); + } + } + + if (member.isMethod()) { + result = member.invoke(thisObj, invokeArgs); + checkMethodResult = true; + } else { + result = member.newInstance(invokeArgs); + } + + } + + if (checkMethodResult) { + if (hasVoidReturn) { + result = Undefined.instance; + } else if (returnTypeTag == JAVA_UNSUPPORTED_TYPE) { + result = cx.getWrapFactory().wrap(cx, scope, result, null); + } + // XXX: the code assumes that if returnTypeTag == JAVA_OBJECT_TYPE + // then the Java method did a proper job of converting the + // result to JS primitive or Scriptable to avoid + // potentially costly Context.javaToJS call. + } + + return result; + } + + /** + * Return new {@link Scriptable} instance using the default + * constructor for the class of the underlying Java method. + * Return null to indicate that the call method should be used to create + * new objects. + */ + public Scriptable createObject(Context cx, Scriptable scope) { + if (member.isCtor() || parmsLength == VARARGS_CTOR) { + return null; + } + Scriptable result; + try { + result = (Scriptable) member.getDeclaringClass().newInstance(); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + + result.setPrototype(getClassPrototype()); + result.setParentScope(getParentScope()); + return result; + } + + boolean isVarArgsMethod() { + return parmsLength == VARARGS_METHOD; + } + + boolean isVarArgsConstructor() { + return parmsLength == VARARGS_CTOR; + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + if (parmsLength > 0) { + Class[] types = member.argTypes; + typeTags = new byte[parmsLength]; + for (int i = 0; i != parmsLength; ++i) { + typeTags[i] = (byte)getTypeTag(types[i]); + } + } + if (member.isMethod()) { + Method method = member.method(); + Class returnType = method.getReturnType(); + if (returnType == Void.TYPE) { + hasVoidReturn = true; + } else { + returnTypeTag = getTypeTag(returnType); + } + } + } + + private static final short VARARGS_METHOD = -1; + private static final short VARARGS_CTOR = -2; + + private static boolean sawSecurityException; + + public static final int JAVA_UNSUPPORTED_TYPE = 0; + public static final int JAVA_STRING_TYPE = 1; + public static final int JAVA_INT_TYPE = 2; + public static final int JAVA_BOOLEAN_TYPE = 3; + public static final int JAVA_DOUBLE_TYPE = 4; + public static final int JAVA_SCRIPTABLE_TYPE = 5; + public static final int JAVA_OBJECT_TYPE = 6; + + MemberBox member; + private String functionName; + private transient byte[] typeTags; + private int parmsLength; + private transient boolean hasVoidReturn; + private transient int returnTypeTag; + private boolean isStatic; +} |