Skip to content

Commit

Permalink
Make lambda-based classes serializable
Browse files Browse the repository at this point in the history
Add new interfaces so that native classes based on LambdaConstructor and LambdaFunction can be serialized without error.
  • Loading branch information
gbrail authored Jan 29, 2025
1 parent ab43fc9 commit 631ef54
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.mozilla.javascript;

import java.util.function.BiConsumer;
import java.util.function.Function;

/**
* A specialized property accessor using lambda functions, similar to {@link LambdaSlot}, but allows
* defining properties with getter and setter lambdas that require access to the owner object
Expand All @@ -15,8 +12,8 @@
* native functionality without the need for reflection.
*/
public class LambdaAccessorSlot extends Slot {
private transient Function<Scriptable, Object> getter;
private transient BiConsumer<Scriptable, Object> setter;
private ScriptableObject.LambdaGetterFunction getter;
private ScriptableObject.LambdaSetterFunction setter;
private LambdaFunction getterFunction;
private LambdaFunction setterFunction;

Expand Down Expand Up @@ -123,7 +120,7 @@ public Object getValue(Scriptable owner) {
return super.getValue(owner);
}

public void setGetter(Scriptable scope, Function<Scriptable, Object> getter) {
public void setGetter(Scriptable scope, ScriptableObject.LambdaGetterFunction getter) {
this.getter = getter;
if (getter != null) {
this.getterFunction =
Expand All @@ -135,7 +132,7 @@ public void setGetter(Scriptable scope, Function<Scriptable, Object> getter) {
}
}

public void setSetter(Scriptable scope, BiConsumer<Scriptable, Object> setter) {
public void setSetter(Scriptable scope, ScriptableObject.LambdaSetterFunction setter) {
this.setter = setter;
if (setter != null) {
this.setterFunction =
Expand Down
41 changes: 24 additions & 17 deletions rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

package org.mozilla.javascript;

import java.util.function.BiConsumer;
import java.util.function.Function;

/**
* 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
Expand All @@ -35,7 +32,7 @@ public class LambdaConstructor extends LambdaFunction {
public static final int CONSTRUCTOR_DEFAULT = CONSTRUCTOR_FUNCTION | CONSTRUCTOR_NEW;

// Lambdas should not be serialized.
protected final transient Constructable targetConstructor;
protected final SerializableConstructable targetConstructor;
private final int flags;

/**
Expand All @@ -51,7 +48,8 @@ public class LambdaConstructor extends LambdaFunction {
* @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) {
public LambdaConstructor(
Scriptable scope, String name, int length, SerializableConstructable target) {
super(scope, name, length, null);
this.targetConstructor = target;
this.flags = CONSTRUCTOR_DEFAULT;
Expand All @@ -72,7 +70,11 @@ public LambdaConstructor(Scriptable scope, String name, int length, Constructabl
* single-function interface this will typically be implemented as a lambda.
*/
public LambdaConstructor(
Scriptable scope, String name, int length, int flags, Constructable target) {
Scriptable scope,
String name,
int length,
int flags,
SerializableConstructable target) {
super(scope, name, length, null);
this.targetConstructor = target;
this.flags = flags;
Expand All @@ -97,8 +99,8 @@ public LambdaConstructor(
Scriptable scope,
String name,
int length,
Callable target,
Constructable targetConstructor) {
SerializableCallable target,
SerializableConstructable targetConstructor) {
super(scope, name, length, target);
this.targetConstructor = targetConstructor;
this.flags = CONSTRUCTOR_DEFAULT;
Expand Down Expand Up @@ -138,7 +140,8 @@ private Scriptable fireConstructor(Context cx, Scriptable scope, Object[] args)
* 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) {
public void definePrototypeMethod(
Scriptable scope, String name, int length, SerializableCallable target) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(name, f, 0);
Expand All @@ -152,7 +155,7 @@ public void definePrototypeMethod(
Scriptable scope,
String name,
int length,
Callable target,
SerializableCallable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
Expand All @@ -169,7 +172,7 @@ public void definePrototypeMethod(
Scriptable scope,
SymbolKey name,
int length,
Callable target,
SerializableCallable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, "[" + name.getName() + "]", length, target);
Expand All @@ -195,7 +198,7 @@ public void definePrototypeProperty(Symbol key, Object value, int attributes) {
* "Object.defineOwnProperty" with a property descriptor.
*/
public void definePrototypeProperty(
Context cx, String name, Function<Scriptable, Object> getter, int attributes) {
Context cx, String name, ScriptableObject.LambdaGetterFunction getter, int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(cx, name, getter, null, attributes);
}
Expand All @@ -208,8 +211,8 @@ public void definePrototypeProperty(
public void definePrototypeProperty(
Context cx,
String name,
Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
ScriptableObject.LambdaGetterFunction getter,
ScriptableObject.LambdaSetterFunction setter,
int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(cx, name, getter, setter, attributes);
Expand All @@ -226,7 +229,11 @@ public void definePrototypeProperty(
* @param attributes the attributes to set on the new property
*/
public void defineConstructorMethod(
Scriptable scope, String name, int length, Callable target, int attributes) {
Scriptable scope,
String name,
int length,
SerializableCallable target,
int attributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
defineProperty(name, f, attributes);
}
Expand All @@ -246,7 +253,7 @@ public void defineConstructorMethod(
Symbol key,
String name,
int length,
Callable target,
SerializableCallable target,
int attributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
defineProperty(key, f, attributes);
Expand All @@ -261,7 +268,7 @@ public void defineConstructorMethod(
Scriptable scope,
String name,
int length,
Callable target,
SerializableCallable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class LambdaFunction extends BaseFunction {
private static final long serialVersionUID = -8388132362854748293L;

// The target is expected to be a lambda -- lambdas should not be serialized.
protected final transient Callable target;
protected final SerializableCallable target;
private final String name;
private final int length;

Expand All @@ -30,7 +30,7 @@ public class LambdaFunction extends BaseFunction {
* @param target an object that implements the function in Java. Since Callable is a
* single-function interface this will typically be implemented as a lambda.
*/
public LambdaFunction(Scriptable scope, String name, int length, Callable target) {
public LambdaFunction(Scriptable scope, String name, int length, SerializableCallable target) {
this.target = target;
this.name = name;
this.length = length;
Expand All @@ -39,7 +39,7 @@ public LambdaFunction(Scriptable scope, String name, int length, Callable target
}

/** Create a new built-in function, with no name, and no default prototype. */
public LambdaFunction(Scriptable scope, int length, Callable target) {
public LambdaFunction(Scriptable scope, int length, SerializableCallable target) {
this.target = target;
this.length = length;
this.name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class NativeProxy extends ScriptableObject implements Callable, Constructa
private Scriptable handlerObj;
private final String typeOf;

private static final class Revoker implements Callable {
private static final class Revoker implements SerializableCallable {
private NativeProxy revocableProxy = null;

public Revoker(NativeProxy proxy) {
Expand Down
27 changes: 21 additions & 6 deletions rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex;
Expand Down Expand Up @@ -1386,7 +1385,7 @@ public void defineProperty(
Scriptable scope,
String name,
int length,
Callable target,
SerializableCallable target,
int attributes,
int propertyAttributes) {
LambdaFunction f = new LambdaFunction(scope, name, length, target);
Expand Down Expand Up @@ -1747,6 +1746,22 @@ public void defineProperty(
slot.setter = setter;
}

/**
* This is a single method interface suitable to be implemented as a lambda. It's used in the
* "defineProperty" method.
*/
public interface LambdaGetterFunction extends Serializable {
Object apply(Scriptable scope);
}

/**
* This is a single method interface suitable to be implemented as a lambda. It's used in the
* "defineProperty" method.
*/
public interface LambdaSetterFunction extends Serializable {
void accept(Scriptable scope, Object value);
}

/**
* Define a property on this object that is implemented using lambda functions accepting
* Scriptable `this` object as first parameter. Unlike with `defineProperty(String name,
Expand All @@ -1768,8 +1783,8 @@ public void defineProperty(
public void defineProperty(
Context cx,
String name,
java.util.function.Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
LambdaGetterFunction getter,
LambdaSetterFunction setter,
int attributes) {
if (getter == null && setter == null)
throw ScriptRuntime.typeError("at least one of {getter, setter} is required");
Expand Down Expand Up @@ -1812,8 +1827,8 @@ private LambdaAccessorSlot replaceExistingLambdaSlot(
private LambdaAccessorSlot createLambdaAccessorSlot(
Object name,
int index,
java.util.function.Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
LambdaGetterFunction getter,
LambdaSetterFunction setter,
int attributes) {
LambdaAccessorSlot slot = new LambdaAccessorSlot(name, index);
slot.setGetter(this, getter);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.mozilla.javascript;

import java.io.Serializable;

/**
* This interface makes it possible to pass a lambda function to the various methods in
* LambdaConstructor and LambdaFunction that require a Callable that is also Serializable. Code that
* works with lambdas will largely "not notice" this interface, but it will make it possible for
* lambda-based classes to work with serialization like older Rhino native classes.
*/
public interface SerializableCallable extends Callable, Serializable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.mozilla.javascript;

import java.io.Serializable;

/**
* This interface makes it possible to pass a lambda function to the various methods in
* LambdaConstructor and LambdaFunction that require a Constructable that is also Serializable.
*/
public interface SerializableConstructable extends Constructable, Serializable {}
Loading

0 comments on commit 631ef54

Please sign in to comment.