aboutsummaryrefslogtreecommitdiffstats
path: root/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java
diff options
context:
space:
mode:
Diffstat (limited to 'infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java')
-rw-r--r--infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java569
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;
+}