org.mozilla.javascript.LambdaConstructor Maven / Gradle / Ivy
Show all versions of rhino-runtime Show documentation
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
/**
* This class implements a JavaScript function that may be used as a constructor by delegating to an
* interface that can be easily implemented as a lambda. The LambdaFunction class may be used to add
* functions to the prototype that are also implemented as lambdas.
*
* In micro benchmarks (as of 2021) using this class to implement a built-in class is about 15%
* more efficient than using IdScriptableObject, and about 25% faster than using reflection via the
* ScriptableObject.defineClass() family of methods. Furthermore, it results in code that more
* directly maps to JavaScript idioms than either methods, it is much easier to implement than
* IdScriptableObject, and the lambda pattern makes it easier to maintain state in various ways that
* don't always map directly to the existing concepts.
*/
public class LambdaConstructor extends LambdaFunction {
private static final long serialVersionUID = 2691205302914111400L;
/** If this flag is set, the constructor may be invoked as an ordinary function */
public static final int CONSTRUCTOR_FUNCTION = 1;
/** If this flag is set, the constructor may be invoked using "new" */
public static final int CONSTRUCTOR_NEW = 1 << 1;
/** By default, the constructor may be invoked either way */
public static final int CONSTRUCTOR_DEFAULT = CONSTRUCTOR_FUNCTION | CONSTRUCTOR_NEW;
// Lambdas should not be serialized.
private final transient Constructable targetConstructor;
private final int flags;
/**
* Create a new function that may be used as a constructor. The new object will have the
* Function prototype and no parent. The caller is responsible for binding this object to the
* appropriate scope.
*
* @param scope scope of the calling context
* @param name name of the function
* @param length the arity of the function
* @param target an object that implements the function in Java. Since Constructable is a
* single-function interface this will typically be implemented as a lambda.
*/
public LambdaConstructor(Scriptable scope, String name, int length, Constructable target) {
super(scope, name, length, null);
this.targetConstructor = target;
this.flags = CONSTRUCTOR_DEFAULT;
}
/**
* Create a new function and control whether it may be invoked using new, as a function, or
* both.
*/
public LambdaConstructor(
Scriptable scope, String name, int length, int flags, Constructable target) {
super(scope, name, length, null);
this.targetConstructor = target;
this.flags = flags;
}
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if ((flags & CONSTRUCTOR_FUNCTION) == 0) {
throw ScriptRuntime.typeErrorById("msg.constructor.no.function", getFunctionName());
}
return targetConstructor.construct(cx, scope, args);
}
@Override
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
if ((flags & CONSTRUCTOR_NEW) == 0) {
throw ScriptRuntime.typeErrorById("msg.no.new", getFunctionName());
}
Scriptable obj = targetConstructor.construct(cx, scope, args);
obj.setPrototype(getClassPrototype());
obj.setParentScope(scope);
return obj;
}
/**
* Define a function property on the prototype of the constructor using a LambdaFunction under
* the covers.
*/
public void definePrototypeMethod(Scriptable scope, String name, int length, Callable target) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(name, f, 0);
}
/**
* Define a function property on the prototype of the constructor using a LambdaFunction under
* the covers.
*/
public void definePrototypeMethod(
Scriptable scope,
String name,
int length,
Callable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
f.setStandardPropertyAttributes(propertyAttributes);
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(name, f, attributes);
}
/** Define a property that may be of any type on the prototype of this constructor. */
public void definePrototypeProperty(String name, Object value, int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(name, value, attributes);
}
public void definePrototypeProperty(Symbol key, Object value, int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(key, value, attributes);
}
/**
* Define a function property directly on the constructor that is implemented under the covers
* by a LambdaFunction.
*
* @param name the key to use to look up the new function property, and also the value to return
* for the "name" property of the Function object
* @param length the value to return for the "length" property of the Function object
* @param target the target to call when the method is invoked
* @param attributes the attributes to set on the new property
*/
public void defineConstructorMethod(
Scriptable scope, String name, int length, Callable target, int attributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
defineProperty(name, f, attributes);
}
/**
* Define a function property directly on the constructor that is implemented under the covers
* by a LambdaFunction.
*
* @param key the Symbol to use to look up the property
* @param name the value to return for the "name" property of the Function object
* @param length the value to return for the "length" property of the Function object
* @param target the target to call when the method is invoked
* @param attributes the attributes to set on the new property
*/
public void defineConstructorMethod(
Scriptable scope,
Symbol key,
String name,
int length,
Callable target,
int attributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
defineProperty(key, f, attributes);
}
/**
* Define a function property directly on the constructor that is implemented under the covers
* by a LambdaFunction, and override the properties of its "name", "length", and "arity"
* properties.
*/
public void defineConstructorMethod(
Scriptable scope,
String name,
int length,
Callable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
f.setStandardPropertyAttributes(propertyAttributes);
defineProperty(name, f, attributes);
}
/**
* A convenience method to convert JavaScript's "this" object into a target class and throw a
* TypeError if it does not match. This is useful for implementing lambda functions, as "this"
* in JavaScript doesn't necessarily map to an instance of the class.
*/
@SuppressWarnings("unchecked")
public static T convertThisObject(Scriptable thisObj, Class targetClass) {
if (!targetClass.isInstance(thisObj)) {
throw ScriptRuntime.typeErrorById("msg.this.not.instance");
}
return (T) thisObj;
}
private ScriptableObject getPrototypeScriptable() {
Object prop = getPrototypeProperty();
if (!(prop instanceof ScriptableObject)) {
throw ScriptRuntime.typeError("Not properly a lambda constructor");
}
return (ScriptableObject) prop;
}
}