GROOVY-9320: Support serializable lambda expression
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
index b5f7fac..bbca52a 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
@@ -57,8 +57,10 @@
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import org.objectweb.asm.Opcodes;
+import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
+import java.lang.invoke.SerializedLambda;
import java.lang.ref.SoftReference;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
@@ -100,6 +102,7 @@
DYNAMIC_TYPE = makeCached(Object.class),
OBJECT_TYPE = DYNAMIC_TYPE,
CLOSURE_TYPE = makeCached(Closure.class),
+ SERIALIZEDLAMBDA_TYPE = makeCached(SerializedLambda .class),
GSTRING_TYPE = makeCached(GString.class),
RANGE_TYPE = makeCached(Range.class),
PATTERN_TYPE = makeCached(Pattern.class),
@@ -134,6 +137,7 @@
Annotation_TYPE = makeCached(Annotation.class),
ELEMENT_TYPE_TYPE = makeCached(ElementType.class),
AUTOCLOSEABLE_TYPE = makeCached(AutoCloseable.class),
+ SERIALIZABLE_TYPE = makeCached(Serializable.class),
// uncached constants
MAP_TYPE = makeWithoutCaching(Map.class),
diff --git a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
index 080e450..64cb989 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
@@ -36,6 +36,7 @@
* </pre>
*/
public class LambdaExpression extends ClosureExpression {
+ private boolean serializable;
public LambdaExpression(Parameter[] parameters, Statement code) {
super(parameters, code);
}
@@ -53,4 +54,12 @@
return "() -> { ... }";
}
}
+
+ public boolean isSerializable() {
+ return serializable;
+ }
+
+ public void setSerializable(boolean serializable) {
+ this.serializable = serializable;
+ }
}
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
index fe03ad7..e425cc0 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
@@ -29,6 +29,7 @@
import org.objectweb.asm.Type;
import java.util.Arrays;
+import java.util.LinkedList;
import java.util.List;
import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
@@ -60,7 +61,17 @@
);
}
- default Handle createBootstrapMethod(boolean isInterface) {
+ default Handle createBootstrapMethod(boolean isInterface, boolean serializable) {
+ if (serializable) {
+ return new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "altMetafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
+ isInterface
+ );
+ }
+
return new Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory",
@@ -70,20 +81,28 @@
);
}
- default Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode) {
+ default Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode, boolean serializable) {
Parameter[] parameters = methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE);
+ List<Object> argumentList = new LinkedList<>();
- return new Object[]{
- Type.getType(abstractMethodDesc),
+ argumentList.add(Type.getType(abstractMethodDesc));
+ argumentList.add(
new Handle(
insn,
BytecodeHelper.getClassInternalName(methodOwnerClassNode.getName()),
methodNode.getName(),
BytecodeHelper.getMethodDescriptor(methodNode),
methodOwnerClassNode.isInterface()
- ),
- Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), parameters))
- };
+ )
+ );
+ argumentList.add(Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), parameters)));
+
+ if (serializable) {
+ argumentList.add(5);
+ argumentList.add(0);
+ }
+
+ return argumentList.toArray();
}
default ClassNode convertParameterType(ClassNode parameterType, ClassNode inferredType) {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index 521bc2d..5005e33 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
@@ -27,12 +27,18 @@
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.builder.AstStringCompiler;
+import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.LambdaExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.classgen.BytecodeInstruction;
+import org.codehaus.groovy.classgen.BytecodeSequence;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.classgen.asm.CompileStack;
import org.codehaus.groovy.classgen.asm.LambdaWriter;
@@ -43,7 +49,6 @@
import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
import org.codehaus.groovy.transform.stc.StaticTypesMarker;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import java.util.Arrays;
import java.util.HashMap;
@@ -52,13 +57,24 @@
import java.util.Map;
import java.util.stream.Collectors;
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
/**
@@ -99,18 +115,29 @@
return;
}
+ boolean implementsSerializable = functionalInterfaceType.implementsInterface(SERIALIZABLE_TYPE);
+ expression.setSerializable(expression.isSerializable() || implementsSerializable);
+
MethodNode abstractMethodNode = ClassHelper.findSAM(redirect);
String abstractMethodDesc = createMethodDescriptor(abstractMethodNode);
ClassNode classNode = controller.getClassNode();
+
boolean isInterface = classNode.isInterface();
ClassNode lambdaWrapperClassNode = getOrAddLambdaClass(expression, ACC_PUBLIC | ACC_FINAL | (isInterface ? ACC_STATIC : 0) | ACC_SYNTHETIC, abstractMethodNode);
MethodNode syntheticLambdaMethodNode = lambdaWrapperClassNode.getMethods(DO_CALL).get(0);
- newGroovyLambdaWrapperAndLoad(lambdaWrapperClassNode, syntheticLambdaMethodNode);
+ boolean canDeserialize = classNode.hasMethod(createDeserializeLambdaMethodName(lambdaWrapperClassNode), createDeserializeLambdaMethodParams());
- loadEnclosingClassInstance();
+ if (!canDeserialize) {
+ if (expression.isSerializable()) {
+ addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaWrapperClassNode);
+ addDeserializeLambdaMethod();
+ }
+ newGroovyLambdaWrapperAndLoad(lambdaWrapperClassNode, expression);
+ loadEnclosingClassInstance();
+ }
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
@@ -118,12 +145,21 @@
mv.visitInvokeDynamicInsn(
abstractMethodNode.getName(),
createAbstractMethodDesc(functionalInterfaceType, lambdaWrapperClassNode),
- createBootstrapMethod(isInterface),
- createBootstrapMethodArguments(abstractMethodDesc, Opcodes.H_INVOKEVIRTUAL, lambdaWrapperClassNode, syntheticLambdaMethodNode)
+ createBootstrapMethod(isInterface, expression.isSerializable()),
+ createBootstrapMethodArguments(abstractMethodDesc, H_INVOKEVIRTUAL, lambdaWrapperClassNode, syntheticLambdaMethodNode, expression.isSerializable())
);
+
+ if (expression.isSerializable()) {
+ mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
+ }
+
operandStack.replace(redirect, 2);
}
+ private Parameter[] createDeserializeLambdaMethodParams() {
+ return new Parameter[]{new Parameter(ClassHelper.SERIALIZEDLAMBDA_TYPE, SERIALIZED_LAMBDA_PARAM_NAME)};
+ }
+
private void loadEnclosingClassInstance() {
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
@@ -137,7 +173,7 @@
}
}
- private void newGroovyLambdaWrapperAndLoad(ClassNode lambdaWrapperClassNode, MethodNode syntheticLambdaMethodNode) {
+ private void newGroovyLambdaWrapperAndLoad(ClassNode lambdaWrapperClassNode, LambdaExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
String lambdaWrapperClassInternalName = BytecodeHelper.getClassInternalName(lambdaWrapperClassNode);
mv.visitTypeInsn(NEW, lambdaWrapperClassInternalName);
@@ -146,7 +182,7 @@
loadEnclosingClassInstance();
controller.getOperandStack().dup();
- loadSharedVariables(syntheticLambdaMethodNode);
+ loadSharedVariables(expression);
List<ConstructorNode> constructorNodeList =
lambdaWrapperClassNode.getDeclaredConstructors().stream()
@@ -164,8 +200,8 @@
operandStack.replace(ClassHelper.CLOSURE_TYPE, lambdaWrapperClassConstructorParameters.length);
}
- private Parameter[] loadSharedVariables(MethodNode syntheticLambdaMethodNode) {
- Parameter[] lambdaSharedVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+ private Parameter[] loadSharedVariables(LambdaExpression expression) {
+ Parameter[] lambdaSharedVariableParameters = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
for (Parameter parameter : lambdaSharedVariableParameters) {
String parameterName = parameter.getName();
loadReference(parameterName, controller);
@@ -211,6 +247,8 @@
answer.setUsingGenerics(outerClass.isUsingGenerics());
answer.setSourcePosition(expression);
+ addSerialVersionUIDField(answer);
+
if (staticMethodOrInStaticClass) {
answer.setStaticClass(true);
}
@@ -219,7 +257,7 @@
}
MethodNode syntheticLambdaMethodNode = addSyntheticLambdaMethodNode(expression, answer, abstractMethodNode);
- Parameter[] localVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+ Parameter[] localVariableParameters = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
addFieldsAndGettersForLocalVariables(answer, localVariableParameters);
ConstructorNode constructorNode = addConstructor(expression, localVariableParameters, answer, createBlockStatementForConstructor(expression, outerClass, classNode));
@@ -231,6 +269,10 @@
return answer;
}
+ private void addSerialVersionUIDField(InnerClassNode answer) {
+ answer.addFieldFirst("serialVersionUID", ACC_PRIVATE | ACC_STATIC | ACC_FINAL, ClassHelper.long_TYPE, new ConstantExpression(-1L, true));
+ }
+
private String genLambdaClassName() {
ClassNode classNode = controller.getClassNode();
ClassNode outerClass = controller.getOutermostClass();
@@ -252,14 +294,14 @@
MethodNode methodNode =
answer.addMethod(
DO_CALL,
- Opcodes.ACC_PUBLIC,
+ ACC_PUBLIC,
abstractMethodNode.getReturnType() /*ClassHelper.OBJECT_TYPE*/ /*returnType*/,
methodParameterList.toArray(Parameter.EMPTY_ARRAY),
ClassNode.EMPTY_ARRAY,
expression.getCode()
);
methodNode.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType);
- methodNode.putNodeMetaData(LAMBDA_SHARED_VARIABLES, localVariableParameters);
+ expression.putNodeMetaData(LAMBDA_SHARED_VARIABLES, localVariableParameters);
methodNode.setSourcePosition(expression);
return methodNode;
@@ -292,6 +334,73 @@
return parameters;
}
+ private static final String SERIALIZED_LAMBDA_PARAM_NAME = "serializedLambda";
+ private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
+ private void addDeserializeLambdaMethod() {
+ ClassNode classNode = controller.getClassNode();
+ Parameter[] parameters = createDeserializeLambdaMethodParams();
+ if (classNode.hasMethod(DESERIALIZE_LAMBDA_METHOD_NAME, parameters)) {
+ return;
+ }
+ Statement code = block(
+ declS(localVarX("enclosingClass", ClassHelper.DYNAMIC_TYPE), new ClassExpression(classNode)),
+ ((BlockStatement) new AstStringCompiler().compile(
+ "return enclosingClass" +
+ ".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/', '$')}\\$\", serializedLambda.getClass())" +
+ ".invoke(null, serializedLambda)"
+ ).get(0)).getStatements().get(0)
+ );
+
+ classNode.addSyntheticMethod(
+ DESERIALIZE_LAMBDA_METHOD_NAME,
+ ACC_PRIVATE | ACC_STATIC,
+ ClassHelper.OBJECT_TYPE,
+ parameters,
+ ClassNode.EMPTY_ARRAY,
+ code);
+ }
+
+ private void addDeserializeLambdaMethodForEachLambdaExpression(LambdaExpression lambdaExpression, ClassNode lambdaWrapperClassNode) {
+ ClassNode classNode = controller.getClassNode();
+ Statement code = block(
+ new BytecodeSequence(new BytecodeInstruction() {
+ @Override
+ public void visit(MethodVisitor mv) {
+ callGetCapturedArg(mv, ICONST_0, lambdaWrapperClassNode);
+ callGetCapturedArg(mv, ICONST_1, classNode);
+ }
+
+ private void callGetCapturedArg(MethodVisitor mv, int capturedArgIndex, ClassNode resultType) {
+ OperandStack operandStack = controller.getOperandStack();
+
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(capturedArgIndex);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/invoke/SerializedLambda",
+ "getCapturedArg",
+ "(I)Ljava/lang/Object;",
+ false);
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(resultType));
+ operandStack.push(resultType);
+ }
+ }),
+ returnS(lambdaExpression)
+ );
+
+ classNode.addSyntheticMethod(
+ createDeserializeLambdaMethodName(lambdaWrapperClassNode),
+ ACC_PUBLIC | ACC_STATIC,
+ ClassHelper.OBJECT_TYPE,
+ createDeserializeLambdaMethodParams(),
+ ClassNode.EMPTY_ARRAY,
+ code);
+ }
+
+ private String createDeserializeLambdaMethodName(ClassNode lambdaWrapperClassNode) {
+ return "$deserializeLambda_" + lambdaWrapperClassNode.getName().replace('.', '$') + "$";
+ }
+
@Override
protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) {
return staticTypesClosureWriter.createClosureClass(expression, mods);
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index a8f8c29..c12a556 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
@@ -153,12 +153,12 @@
mv.visitInvokeDynamicInsn(
abstractMethodNode.getName(),
createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef),
- createBootstrapMethod(isInterface),
+ createBootstrapMethod(isInterface, false),
createBootstrapMethodArguments(
abstractMethodDesc,
methodRefMethod.isStatic() || isConstructorReference ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL,
isConstructorReference ? controller.getClassNode() : typeOrTargetRefType,
- methodRefMethod)
+ methodRefMethod, false)
);
if (isClassExpr) {
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index f762710..b01bb77 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -954,4 +954,338 @@
p()
'''
}
+
+ void testSerialize() {
+ assertScript '''
+ import java.util.function.Function
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ def p() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+
+ return out.toByteArray()
+ }
+ }
+
+ assert new Test1().p().length > 0
+ '''
+ }
+
+ void testSerializeFailed() {
+ shouldFail(NotSerializableException, '''
+ import java.util.function.Function
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ def p() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream {
+ Function<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+
+ return out.toByteArray()
+ }
+ }
+
+ new Test1().p()
+ ''')
+ }
+
+ void testDeserialize() {
+ assertScript '''
+ package tests.lambda
+ import java.util.function.Function
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ byte[] p() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+
+ return out.toByteArray()
+ }
+
+ static void main(String[] args) {
+ new ByteArrayInputStream(new Test1().p()).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+ }
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+ }
+ '''
+ }
+
+
+ void testDeserialize2() {
+ assertScript '''
+ import java.util.function.Function
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ static byte[] p() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+
+ return out.toByteArray()
+ }
+
+ static void main(String[] args) {
+ new ByteArrayInputStream(Test1.p()).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+ }
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+ }
+ '''
+ }
+
+ void testDeserializeNestedLambda() {
+ assertScript '''
+ import java.util.function.Function
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ def p() {
+ def out1 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f1 = (Integer e) -> 'a' + e
+ out1.withObjectOutputStream {
+ it.writeObject(f1)
+ }
+ def result1 = out1.toByteArray()
+
+ def out2 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f2 = (Integer e) -> 'b' + e
+ out2.withObjectOutputStream {
+ it.writeObject(f2)
+ }
+ def result2 = out2.toByteArray()
+
+ // nested lambda expression
+ def out3 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f3 = (Integer e) -> {
+ SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+ 'c' + nf(e)
+ }
+ out3.withObjectOutputStream {
+ it.writeObject(f3)
+ }
+ def result3 = out3.toByteArray()
+
+ return [result1, result2, result3]
+ }
+ }
+
+ def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = new Test1().p()
+ new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'b1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'cn1' == f.apply(1)
+ }
+ '''
+ }
+
+ void testDeserializeNestedLambda2() {
+ assertScript '''
+ import java.util.function.Function
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ def p() {
+ def out1 = new ByteArrayOutputStream()
+ out1.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+ def result1 = out1.toByteArray()
+
+ def out2 = new ByteArrayOutputStream()
+ out2.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'b' + e)
+ it.writeObject(f)
+ }
+ def result2 = out2.toByteArray()
+
+ // nested lambda expression
+ def out3 = new ByteArrayOutputStream()
+ out3.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> {
+ SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+ 'c' + nf(e)
+ })
+ it.writeObject(f)
+ }
+ def result3 = out3.toByteArray()
+
+ return [result1, result2, result3]
+ }
+ }
+
+ def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = new Test1().p()
+ new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'b1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'cn1' == f.apply(1)
+ }
+ '''
+ }
+
+ void testDeserializeNestedLambda3() {
+ assertScript '''
+ import java.util.function.Function
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ static p() {
+ def out1 = new ByteArrayOutputStream()
+ out1.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+ it.writeObject(f)
+ }
+ def result1 = out1.toByteArray()
+
+ def out2 = new ByteArrayOutputStream()
+ out2.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> 'b' + e)
+ it.writeObject(f)
+ }
+ def result2 = out2.toByteArray()
+
+ // nested lambda expression
+ def out3 = new ByteArrayOutputStream()
+ out3.withObjectOutputStream {
+ SerializableFunction<Integer, String> f = ((Integer e) -> {
+ SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+ 'c' + nf(e)
+ })
+ it.writeObject(f)
+ }
+ def result3 = out3.toByteArray()
+
+ return [result1, result2, result3]
+ }
+ }
+
+ def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = Test1.p()
+ new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'b1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'cn1' == f.apply(1)
+ }
+ '''
+ }
+
+ void testDeserializeNestedLambda4() {
+ assertScript '''
+ import java.util.function.Function
+
+ interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+
+ @groovy.transform.CompileStatic
+ class Test1 implements Serializable {
+ private static final long serialVersionUID = -1L;
+ static p() {
+ def out1 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f1 = (Integer e) -> 'a' + e
+ out1.withObjectOutputStream {
+ it.writeObject(f1)
+ }
+ def result1 = out1.toByteArray()
+
+ def out2 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f2 = (Integer e) -> 'b' + e
+ out2.withObjectOutputStream {
+ it.writeObject(f2)
+ }
+ def result2 = out2.toByteArray()
+
+ // nested lambda expression
+ def out3 = new ByteArrayOutputStream()
+ SerializableFunction<Integer, String> f3 = (Integer e) -> {
+ SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+ 'c' + nf(e)
+ }
+ out3.withObjectOutputStream {
+ it.writeObject(f3)
+ }
+ def result3 = out3.toByteArray()
+
+ return [result1, result2, result3]
+ }
+ }
+
+ def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = Test1.p()
+ new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'a1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'b1' == f.apply(1)
+ }
+
+ new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+ SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+ assert 'cn1' == f.apply(1)
+ }
+ '''
+ }
}