All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.corejs.javascript.LambdaConstructor Maven / Gradle / Ivy

/* -*- 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.htmlunit.corejs.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; } protected Constructable getTargetConstructor() { return targetConstructor; } @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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy