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-2000 Netscape Communications Corporation. All
\r
27 * Alternatively, the contents of this file may be used under the
\r
28 * terms of the GNU Public License (the "GPL"), in which case the
\r
29 * provisions of the GPL are applicable instead of those above.
\r
30 * If you wish to allow use of your version of this file only
\r
31 * under the terms of the GPL and not to allow others to use your
\r
32 * version of this file under the NPL, indicate your decision by
\r
33 * deleting the provisions above and replace them with the notice
\r
34 * and other provisions required by the GPL. If you do not delete
\r
35 * the provisions above, a recipient may use your version of this
\r
36 * file under either the NPL or the GPL.
\r
41 package org.mozilla.javascript;
\r
43 import java.util.Vector;
\r
44 import java.lang.reflect.Constructor;
\r
45 import java.lang.reflect.Member;
\r
46 import java.lang.reflect.Method;
\r
47 import java.lang.reflect.Modifier;
\r
48 import java.lang.reflect.InvocationTargetException;
\r
50 public class FunctionObject extends NativeFunction {
\r
53 * Create a JavaScript function object from a Java method.
\r
55 * <p>The <code>member</code> argument must be either a java.lang.reflect.Method
\r
56 * or a java.lang.reflect.Constructor and must match one of two forms.<p>
\r
58 * The first form is a member with zero or more parameters
\r
59 * of the following types: Object, String, boolean, Scriptable,
\r
60 * byte, short, int, float, or double. The Long type is not supported
\r
61 * because the double representation of a long (which is the
\r
62 * EMCA-mandated storage type for Numbers) may lose precision.
\r
63 * If the member is a Method, the return value must be void or one
\r
64 * of the types allowed for parameters.<p>
\r
66 * The runtime will perform appropriate conversions based
\r
67 * upon the type of the parameter. A parameter type of
\r
68 * Object specifies that no conversions are to be done. A parameter
\r
69 * of type String will use Context.toString to convert arguments.
\r
70 * Similarly, parameters of type double, boolean, and Scriptable
\r
71 * will cause Context.toNumber, Context.toBoolean, and
\r
72 * Context.toObject, respectively, to be called.<p>
\r
74 * If the method is not static, the Java 'this' value will
\r
75 * correspond to the JavaScript 'this' value. Any attempt
\r
76 * to call the function with a 'this' value that is not
\r
77 * of the right Java type will result in an error.<p>
\r
79 * The second form is the variable arguments (or "varargs")
\r
80 * form. If the FunctionObject will be used as a constructor,
\r
81 * the member must have the following parameters
\r
83 * (Context cx, Object[] args, Function ctorObj,
\r
84 * boolean inNewExpr)</pre>
\r
85 * and if it is a Method, be static and return an Object result.<p>
\r
87 * Otherwise, if the FunctionObject will <i>not</i> be used to define a
\r
88 * constructor, the member must be a static Method with parameters
\r
89 * (Context cx, Scriptable thisObj, Object[] args,
\r
90 * Function funObj) </pre>
\r
92 * and an Object result.<p>
\r
94 * When the function varargs form is called as part of a function call,
\r
95 * the <code>args</code> parameter contains the
\r
96 * arguments, with <code>thisObj</code>
\r
97 * set to the JavaScript 'this' value. <code>funObj</code>
\r
98 * is the function object for the invoked function.<p>
\r
100 * When the constructor varargs form is called or invoked while evaluating
\r
101 * a <code>new</code> expression, <code>args</code> contains the
\r
102 * arguments, <code>ctorObj</code> refers to this FunctionObject, and
\r
103 * <code>inNewExpr</code> is true if and only if a <code>new</code>
\r
104 * expression caused the call. This supports defining a function that
\r
105 * has different behavior when called as a constructor than when
\r
106 * invoked as a normal function call. (For example, the Boolean
\r
107 * constructor, when called as a function,
\r
108 * will convert to boolean rather than creating a new object.)<p>
\r
110 * @param name the name of the function
\r
111 * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor
\r
112 * that defines the object
\r
113 * @param scope enclosing scope of function
\r
114 * @see org.mozilla.javascript.Scriptable
\r
116 public FunctionObject(String name, Member methodOrConstructor,
\r
120 if (methodOrConstructor instanceof Constructor) {
\r
121 ctor = (Constructor) methodOrConstructor;
\r
122 isStatic = true; // well, doesn't take a 'this'
\r
123 types = ctor.getParameterTypes();
\r
124 methodName = ctor.getName();
\r
126 method = (Method) methodOrConstructor;
\r
127 isStatic = Modifier.isStatic(method.getModifiers());
\r
128 types = method.getParameterTypes();
\r
129 methodName = method.getName();
\r
131 this.functionName = name;
\r
133 if (types.length == 4 && (types[1].isArray() || types[2].isArray())) {
\r
134 // Either variable args or an error.
\r
135 if (types[1].isArray()) {
\r
137 types[0] != Context.class ||
\r
138 types[1].getComponentType() != ScriptRuntime.ObjectClass ||
\r
139 types[2] != ScriptRuntime.FunctionClass ||
\r
140 types[3] != Boolean.TYPE)
\r
142 throw Context.reportRuntimeError1(
\r
143 "msg.varargs.ctor", methodName);
\r
145 parmsLength = VARARGS_CTOR;
\r
148 types[0] != Context.class ||
\r
149 types[1] != ScriptRuntime.ScriptableClass ||
\r
150 types[2].getComponentType() != ScriptRuntime.ObjectClass ||
\r
151 types[3] != ScriptRuntime.FunctionClass)
\r
153 throw Context.reportRuntimeError1(
\r
154 "msg.varargs.fun", methodName);
\r
156 parmsLength = VARARGS_METHOD;
\r
158 // XXX check return type
\r
161 parmsLength = (short) types.length;
\r
162 for (int i=0; i < parmsLength; i++) {
\r
163 Class type = types[i];
\r
164 if (type != ScriptRuntime.ObjectClass &&
\r
165 type != ScriptRuntime.StringClass &&
\r
166 type != ScriptRuntime.BooleanClass &&
\r
167 !ScriptRuntime.NumberClass.isAssignableFrom(type) &&
\r
168 !Scriptable.class.isAssignableFrom(type) &&
\r
169 type != Boolean.TYPE &&
\r
170 type != Byte.TYPE &&
\r
171 type != Short.TYPE &&
\r
172 type != Integer.TYPE &&
\r
173 type != Float.TYPE &&
\r
174 type != Double.TYPE)
\r
176 // Note that long is not supported.
\r
177 throw Context.reportRuntimeError1("msg.bad.parms",
\r
181 length = parmsLength;
\r
184 // Initialize length property
\r
185 lengthPropertyValue = (short) length;
\r
187 hasVoidReturn = method != null && method.getReturnType() == Void.TYPE;
\r
188 this.argCount = (short) length;
\r
190 setParentScope(scope);
\r
191 setPrototype(getFunctionPrototype(scope));
\r
192 Context cx = Context.getCurrentContext();
\r
193 useDynamicScope = cx != null &&
\r
194 cx.hasCompileFunctionsWithDynamicScope();
\r
198 * Return the value defined by the method used to construct the object
\r
199 * (number of parameters of the method, or 1 if the method is a "varargs"
\r
200 * form), unless setLength has been called with a new value.
\r
201 * Overrides getLength in BaseFunction.
\r
203 * @see org.mozilla.javascript.FunctionObject#setLength
\r
204 * @see org.mozilla.javascript.BaseFunction#getLength
\r
206 public int getLength() {
\r
207 return lengthPropertyValue;
\r
211 * Set the value of the "length" property.
\r
213 * <p>Changing the value of the "length" property of a FunctionObject only
\r
214 * affects the value retrieved from get() and does not affect the way
\r
215 * the method itself is called. <p>
\r
217 * The "length" property will be defined by default as the number
\r
218 * of parameters of the method used to construct the FunctionObject,
\r
219 * unless the method is a "varargs" form, in which case the "length"
\r
220 * property will be defined to 1.
\r
222 * @param length the new length
\r
224 public void setLength(short length) {
\r
225 lengthPropertyValue = length;
\r
228 // TODO: Make not public
\r
230 * Finds methods of a given name in a given class.
\r
232 * <p>Searches <code>clazz</code> for methods with name
\r
233 * <code>name</code>. Maintains a cache so that multiple
\r
234 * lookups on the same class are cheap.
\r
236 * @param clazz the class to search
\r
237 * @param name the name of the methods to find
\r
238 * @return an array of the found methods, or null if no methods
\r
239 * by that name were found.
\r
240 * @see java.lang.Class#getMethods
\r
242 public static Method[] findMethods(Class clazz, String name) {
\r
243 return findMethods(getMethodList(clazz), name);
\r
246 static Method[] findMethods(Method[] methods, String name) {
\r
247 // Usually we're just looking for a single method, so optimize
\r
250 Method first = null;
\r
251 for (int i=0; i < methods.length; i++) {
\r
252 if (methods[i] == null)
\r
254 if (methods[i].getName().equals(name)) {
\r
255 if (first == null) {
\r
256 first = methods[i];
\r
260 v.addElement(first);
\r
262 v.addElement(methods[i]);
\r
269 Method[] single = { first };
\r
272 Method[] result = new Method[v.size()];
\r
273 v.copyInto(result);
\r
277 static Method[] getMethodList(Class clazz) {
\r
278 Method[] cached = methodsCache; // get once to avoid synchronization
\r
279 if (cached != null && cached[0].getDeclaringClass() == clazz)
\r
281 Method[] methods = null;
\r
283 // getDeclaredMethods may be rejected by the security manager
\r
284 // but getMethods is more expensive
\r
285 if (!sawSecurityException)
\r
286 methods = clazz.getDeclaredMethods();
\r
287 } catch (SecurityException e) {
\r
288 // If we get an exception once, give up on getDeclaredMethods
\r
289 sawSecurityException = true;
\r
291 if (methods == null) {
\r
292 methods = clazz.getMethods();
\r
295 for (int i=0; i < methods.length; i++) {
\r
296 if (sawSecurityException
\r
297 ? methods[i].getDeclaringClass() != clazz
\r
298 : !Modifier.isPublic(methods[i].getModifiers()))
\r
305 Method[] result = new Method[count];
\r
307 for (int i=0; i < methods.length; i++) {
\r
308 if (methods[i] != null)
\r
309 result[j++] = methods[i];
\r
311 if (result.length > 0 && Context.isCachingEnabled)
\r
312 methodsCache = result;
\r
317 * Define this function as a JavaScript constructor.
\r
319 * Sets up the "prototype" and "constructor" properties. Also
\r
320 * calls setParent and setPrototype with appropriate values.
\r
321 * Then adds the function object as a property of the given scope, using
\r
322 * <code>prototype.getClassName()</code>
\r
323 * as the name of the property.
\r
325 * @param scope the scope in which to define the constructor (typically
\r
326 * the global object)
\r
327 * @param prototype the prototype object
\r
328 * @see org.mozilla.javascript.Scriptable#setParentScope
\r
329 * @see org.mozilla.javascript.Scriptable#setPrototype
\r
330 * @see org.mozilla.javascript.Scriptable#getClassName
\r
332 public void addAsConstructor(Scriptable scope, Scriptable prototype) {
\r
333 setParentScope(scope);
\r
334 setPrototype(getFunctionPrototype(scope));
\r
335 setImmunePrototypeProperty(prototype);
\r
337 prototype.setParentScope(this);
\r
339 final int attr = ScriptableObject.DONTENUM |
\r
340 ScriptableObject.PERMANENT |
\r
341 ScriptableObject.READONLY;
\r
342 defineProperty(prototype, "constructor", this, attr);
\r
344 String name = prototype.getClassName();
\r
345 defineProperty(scope, name, this, ScriptableObject.DONTENUM);
\r
347 setParentScope(scope);
\r
350 static public Object convertArg(Scriptable scope,
\r
351 Object arg, Class desired)
\r
353 if (desired == ScriptRuntime.StringClass)
\r
354 return ScriptRuntime.toString(arg);
\r
355 if (desired == ScriptRuntime.IntegerClass ||
\r
356 desired == Integer.TYPE)
\r
358 return new Integer(ScriptRuntime.toInt32(arg));
\r
360 if (desired == ScriptRuntime.BooleanClass ||
\r
361 desired == Boolean.TYPE)
\r
363 return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE
\r
366 if (desired == ScriptRuntime.DoubleClass ||
\r
367 desired == Double.TYPE)
\r
369 return new Double(ScriptRuntime.toNumber(arg));
\r
371 if (desired == ScriptRuntime.ScriptableClass)
\r
372 return ScriptRuntime.toObject(scope, arg);
\r
373 if (desired == ScriptRuntime.ObjectClass)
\r
376 // Note that the long type is not supported; see the javadoc for
\r
377 // the constructor for this class
\r
378 throw Context.reportRuntimeError1
\r
379 ("msg.cant.convert", desired.getName());
\r
383 * Performs conversions on argument types if needed and
\r
384 * invokes the underlying Java method or constructor.
\r
386 * Implements Function.call.
\r
388 * @see org.mozilla.javascript.Function#call
\r
389 * @exception JavaScriptException if the underlying Java method or
\r
390 * constructor threw an exception
\r
392 public Object call(Context cx, Scriptable scope, Scriptable thisObj,
\r
394 throws JavaScriptException
\r
396 if (parmsLength < 0) {
\r
397 return callVarargs(cx, thisObj, args, false);
\r
400 // OPT: cache "clazz"?
\r
401 Class clazz = method != null ? method.getDeclaringClass()
\r
402 : ctor.getDeclaringClass();
\r
403 while (!clazz.isInstance(thisObj)) {
\r
404 thisObj = thisObj.getPrototype();
\r
405 if (thisObj == null || !useDynamicScope) {
\r
406 // Couldn't find an object to call this on.
\r
407 throw NativeGlobal.typeError1
\r
408 ("msg.incompat.call", functionName, scope);
\r
412 Object[] invokeArgs;
\r
414 if (parmsLength == args.length) {
\r
416 // avoid copy loop if no conversions needed
\r
417 i = (types == null) ? parmsLength : 0;
\r
419 invokeArgs = new Object[parmsLength];
\r
422 for (; i < parmsLength; i++) {
\r
423 Object arg = (i < args.length)
\r
425 : Undefined.instance;
\r
426 if (types != null) {
\r
427 arg = convertArg(this, arg, types[i]);
\r
429 invokeArgs[i] = arg;
\r
432 Object result = method == null ? ctor.newInstance(invokeArgs)
\r
433 : doInvoke(thisObj, invokeArgs);
\r
434 return hasVoidReturn ? Undefined.instance : result;
\r
436 catch (InvocationTargetException e) {
\r
437 throw JavaScriptException.wrapException(scope, e);
\r
439 catch (IllegalAccessException e) {
\r
440 throw WrappedException.wrapException(e);
\r
442 catch (InstantiationException e) {
\r
443 throw WrappedException.wrapException(e);
\r
448 * Performs conversions on argument types if needed and
\r
449 * invokes the underlying Java method or constructor
\r
450 * to create a new Scriptable object.
\r
452 * Implements Function.construct.
\r
454 * @param cx the current Context for this thread
\r
455 * @param scope the scope to execute the function relative to. This
\r
456 * set to the value returned by getParentScope() except
\r
457 * when the function is called from a closure.
\r
458 * @param args arguments to the constructor
\r
459 * @see org.mozilla.javascript.Function#construct
\r
460 * @exception JavaScriptException if the underlying Java method or constructor
\r
461 * threw an exception
\r
463 public Scriptable construct(Context cx, Scriptable scope, Object[] args)
\r
464 throws JavaScriptException
\r
466 if (method == null || parmsLength == VARARGS_CTOR) {
\r
468 if (method != null) {
\r
469 result = (Scriptable) callVarargs(cx, null, args, true);
\r
471 result = (Scriptable) call(cx, scope, null, args);
\r
474 if (result.getPrototype() == null)
\r
475 result.setPrototype(getClassPrototype());
\r
476 if (result.getParentScope() == null) {
\r
477 Scriptable parent = getParentScope();
\r
478 if (result != parent)
\r
479 result.setParentScope(parent);
\r
483 } else if (method != null && !isStatic) {
\r
486 result = (Scriptable) method.getDeclaringClass().newInstance();
\r
487 } catch (IllegalAccessException e) {
\r
488 throw WrappedException.wrapException(e);
\r
489 } catch (InstantiationException e) {
\r
490 throw WrappedException.wrapException(e);
\r
493 result.setPrototype(getClassPrototype());
\r
494 result.setParentScope(getParentScope());
\r
496 Object val = call(cx, scope, result, args);
\r
497 if (val != null && val != Undefined.instance &&
\r
498 val instanceof Scriptable)
\r
500 return (Scriptable) val;
\r
505 return super.construct(cx, scope, args);
\r
508 private final Object doInvoke(Object thisObj, Object[] args)
\r
509 throws IllegalAccessException, InvocationTargetException
\r
511 Invoker master = invokerMaster;
\r
512 if (master != null) {
\r
513 if (invoker == null) {
\r
514 invoker = master.createInvoker(method, types);
\r
517 return invoker.invoke(thisObj, args);
\r
518 } catch (RuntimeException e) {
\r
519 throw new InvocationTargetException(e);
\r
522 return method.invoke(thisObj, args);
\r
525 private Object callVarargs(Context cx, Scriptable thisObj, Object[] args,
\r
527 throws JavaScriptException
\r
530 Object[] invokeArgs;
\r
532 if (cx.arrayCache.size() > 0) invokeArgs = (Object[])cx.arrayCache.lastElement();
\r
533 else invokeArgs = new Object[4];
\r
535 if (parmsLength == VARARGS_METHOD) {
\r
536 invokeArgs[0] = cx;
\r
537 invokeArgs[1] = thisObj;
\r
538 invokeArgs[2] = args;
\r
539 invokeArgs[3] = this;
\r
540 Object result = doInvoke(null, invokeArgs);
\r
541 ret = hasVoidReturn ? Undefined.instance : result;
\r
543 Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE;
\r
544 invokeArgs[0] = cx;
\r
545 invokeArgs[1] = args;
\r
546 invokeArgs[2] = this;
\r
548 ret = (method == null)
\r
549 ? ctor.newInstance(invokeArgs)
\r
550 : doInvoke(null, invokeArgs);
\r
553 cx.arrayCache.addElement(invokeArgs);
\r
556 catch (InvocationTargetException e) {
\r
557 Throwable target = e.getTargetException();
\r
558 if (target instanceof EvaluatorException)
\r
559 throw (EvaluatorException) target;
\r
560 if (target instanceof EcmaError)
\r
561 throw (EcmaError) target;
\r
562 Scriptable scope = thisObj == null ? this : thisObj;
\r
563 throw JavaScriptException.wrapException(scope, target);
\r
565 catch (IllegalAccessException e) {
\r
566 throw WrappedException.wrapException(e);
\r
568 catch (InstantiationException e) {
\r
569 throw WrappedException.wrapException(e);
\r
573 boolean isVarArgsMethod() {
\r
574 return parmsLength == VARARGS_METHOD;
\r
577 boolean isVarArgsConstructor() {
\r
578 return parmsLength == VARARGS_CTOR;
\r
581 static void setCachingEnabled(boolean enabled) {
\r
583 methodsCache = null;
\r
584 invokerMaster = null;
\r
585 } else if (invokerMaster == null) {
\r
586 invokerMaster = newInvokerMaster();
\r
590 /** Get default master implementation or null if not available */
\r
591 private static Invoker newInvokerMaster() {
\r
594 Class cl = ScriptRuntime.loadClassName(INVOKER_MASTER_CLASS);
\r
595 return (Invoker)cl.newInstance();
\r
597 catch (ClassNotFoundException ex) {}
\r
598 catch (IllegalAccessException ex) {}
\r
599 catch (InstantiationException ex) {}
\r
600 catch (SecurityException ex) {}
\r
605 private static final String
\r
606 INVOKER_MASTER_CLASS = "org.mozilla.javascript.optimizer.InvokerImpl";
\r
608 static Invoker invokerMaster = newInvokerMaster();
\r
610 private static final short VARARGS_METHOD = -1;
\r
611 private static final short VARARGS_CTOR = -2;
\r
613 private static boolean sawSecurityException;
\r
615 static Method[] methodsCache;
\r
619 private Class[] types;
\r
621 private short parmsLength;
\r
622 private short lengthPropertyValue;
\r
623 private boolean hasVoidReturn;
\r
624 private boolean isStatic;
\r
625 private boolean useDynamicScope;
\r