Mercurial > hg > openjdk > jigsaw > jdk
changeset 7020:7748ffdca16a
8004970: Implement serialization in the lambda metafactory
Reviewed-by: forax
author | rfield |
---|---|
date | Sat, 16 Feb 2013 12:36:54 -0800 |
parents | 048637b40787 |
children | 43726ed11fb3 |
files | src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java src/share/classes/java/lang/invoke/LambdaMetafactory.java src/share/classes/java/lang/invoke/MethodHandleInfo.java src/share/classes/java/lang/invoke/SerializedLambda.java src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java test/java/lang/invoke/lambda/LambdaSerialization.java |
diffstat | 7 files changed, 639 insertions(+), 159 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Fri Feb 15 11:06:52 2013 +0000 +++ b/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Sat Feb 16 12:36:54 2013 -0800 @@ -34,11 +34,11 @@ import static sun.invoke.util.Wrapper.*; /** - * Abstract implementation of a meta-factory which provides parameter unrolling and input validation. + * Abstract implementation of a lambda metafactory which provides parameter unrolling and input validation. * - * @author Robert Field + * @see LambdaMetafactory */ -/*non-public*/ abstract class AbstractValidatingLambdaMetafactory { +/* package */ abstract class AbstractValidatingLambdaMetafactory { /* * For context, the comments for the following fields are marked in quotes with their values, given this program: @@ -54,16 +54,19 @@ final Class<?> targetClass; // The class calling the meta-factory via invokedynamic "class X" final MethodType invokedType; // The type of the invoked method "(CC)II" final Class<?> samBase; // The type of the returned instance "interface JJ" - final boolean isSerializable; // Should the returned instance be serializable + final MethodHandle samMethod; // Raw method handle for the functional interface method final MethodHandleInfo samInfo; // Info about the SAM method handle "MethodHandleInfo[9 II.foo(Object)Object]" final Class<?> samClass; // Interface containing the SAM method "interface II" final MethodType samMethodType; // Type of the SAM method "(Object)Object" + final MethodHandle implMethod; // Raw method handle for the implementation method final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]" final int implKind; // Invocation kind for implementation "5"=invokevirtual final boolean implIsInstanceMethod; // Is the implementation an instance method "true" final Class<?> implDefiningClass; // Type defining the implementation "class CC" final MethodType implMethodType; // Type of the implementation method "(int)String" final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" + final boolean isSerializable; // Should the returned instance be serializable + final Class<?>[] markerInterfaces; // Additional marker interfaces to be implemented /** @@ -80,27 +83,35 @@ * @param implMethod The implementation method which should be called (with suitable adaptation of argument * types, return types, and adjustment for captured arguments) when methods of the resulting * functional interface instance are invoked. - * @param instantiatedMethodType The signature of the SAM method from the functional interface's perspective + * @param instantiatedMethodType The signature of the primary functional interface method after type variables + * are substituted with their instantiation from the capture site * @throws ReflectiveOperationException + * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated */ AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, MethodType invokedType, MethodHandle samMethod, MethodHandle implMethod, - MethodType instantiatedMethodType) - throws ReflectiveOperationException { + MethodType instantiatedMethodType, + int flags, + Class<?>[] markerInterfaces) + throws ReflectiveOperationException, LambdaConversionException { this.targetClass = caller.lookupClass(); this.invokedType = invokedType; this.samBase = invokedType.returnType(); - this.isSerializable = Serializable.class.isAssignableFrom(samBase); + this.samMethod = samMethod; this.samInfo = new MethodHandleInfo(samMethod); this.samClass = samInfo.getDeclaringClass(); this.samMethodType = samInfo.getMethodType(); + this.implMethod = implMethod; this.implInfo = new MethodHandleInfo(implMethod); - this.implKind = implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial? MethodHandleInfo.REF_invokeVirtual : implInfo.getReferenceKind(); // @@@ Temp work-around to hotspot incorrectly converting to invokespecial + // @@@ Temporary work-around pending resolution of 8005119 + this.implKind = (implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial) + ? MethodHandleInfo.REF_invokeVirtual + : implInfo.getReferenceKind(); this.implIsInstanceMethod = implKind == MethodHandleInfo.REF_invokeVirtual || implKind == MethodHandleInfo.REF_invokeSpecial || @@ -109,6 +120,30 @@ this.implMethodType = implInfo.getMethodType(); this.instantiatedMethodType = instantiatedMethodType; + + if (!samClass.isInterface()) { + throw new LambdaConversionException(String.format( + "Functional interface %s is not an interface", + samClass.getName())); + } + + boolean foundSerializableSupertype = Serializable.class.isAssignableFrom(samBase); + for (Class<?> c : markerInterfaces) { + if (!c.isInterface()) { + throw new LambdaConversionException(String.format( + "Marker interface %s is not an interface", + c.getName())); + } + foundSerializableSupertype |= Serializable.class.isAssignableFrom(c); + } + this.isSerializable = ((flags & LambdaMetafactory.FLAG_SERIALIZABLE) != 0) + || foundSerializableSupertype; + + if (isSerializable && !foundSerializableSupertype) { + markerInterfaces = Arrays.copyOf(markerInterfaces, markerInterfaces.length + 1); + markerInterfaces[markerInterfaces.length-1] = Serializable.class; + } + this.markerInterfaces = markerInterfaces; } /** @@ -127,8 +162,9 @@ void validateMetafactoryArgs() throws LambdaConversionException { // Check target type is a subtype of class where SAM method is defined if (!samClass.isAssignableFrom(samBase)) { - throw new LambdaConversionException(String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s", - samBase.getName(), samClass.getName())); + throw new LambdaConversionException( + String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s", + samBase.getName(), samClass.getName())); } switch (implKind) { @@ -149,14 +185,16 @@ final int samArity = samMethodType.parameterCount(); final int instantiatedArity = instantiatedMethodType.parameterCount(); if (implArity + receiverArity != capturedArity + samArity) { - throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface parameters, %d implementation parameters", - implIsInstanceMethod ? "instance" : "static", implInfo, - capturedArity, samArity, implArity)); + throw new LambdaConversionException( + String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + capturedArity, samArity, implArity)); } if (instantiatedArity != samArity) { - throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d functional interface parameters, %d SAM method parameters", - implIsInstanceMethod ? "instance" : "static", implInfo, - instantiatedArity, samArity)); + throw new LambdaConversionException( + String.format("Incorrect number of parameters for %s method %s; %d instantiated parameters, %d functional interface method parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + instantiatedArity, samArity)); } // If instance: first captured arg (receiver) must be subtype of class where impl method is defined @@ -180,8 +218,9 @@ // check receiver type if (!implDefiningClass.isAssignableFrom(receiverClass)) { - throw new LambdaConversionException(String.format("Invalid receiver type %s; not a subtype of implementation type %s", - receiverClass, implDefiningClass)); + throw new LambdaConversionException( + String.format("Invalid receiver type %s; not a subtype of implementation type %s", + receiverClass, implDefiningClass)); } } else { // no receiver @@ -196,7 +235,8 @@ Class<?> capturedParamType = invokedType.parameterType(i + capturedStart); if (!capturedParamType.equals(implParamType)) { throw new LambdaConversionException( - String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", i, capturedParamType, implParamType)); + String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", + i, capturedParamType, implParamType)); } } // Check for adaptation match on SAM arguments @@ -206,7 +246,8 @@ Class<?> instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset); if (!isAdaptableTo(instantiatedParamType, implParamType, true)) { throw new LambdaConversionException( - String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, instantiatedParamType, implParamType)); + String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", + i, instantiatedParamType, implParamType)); } } @@ -218,7 +259,8 @@ : implMethodType.returnType(); if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { throw new LambdaConversionException( - String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); + String.format("Type mismatch for lambda return: %s is not convertible to %s", + actualReturnType, expectedType)); } } @@ -274,8 +316,8 @@ } - /*********** Logging support -- for debugging only - static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only + /*********** Logging support -- for debugging only, uncomment as needed + static final Executor logPool = Executors.newSingleThreadExecutor(); protected static void log(final String s) { MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { @Override @@ -297,17 +339,21 @@ ***********************/ /** - * Find the SAM method and corresponding methods which should be bridged. SAM method and those to be bridged - * will have the same name and number of parameters. Check for matching default methods (non-abstract), they - * should not be bridged-over and indicate a complex bridging situation. + * Find the functional interface method and corresponding abstract methods + * which should be bridged. The functional interface method and those to be + * bridged will have the same name and number of parameters. Check for + * matching default methods (non-abstract), the VM will create bridges for + * default methods; We don't have enough readily available type information + * to distinguish between where the functional interface method should be + * bridged and where the default method should be bridged; This situation is + * flagged. */ class MethodAnalyzer { private final Method[] methods = samBase.getMethods(); - private final List<Method> methodsFound = new ArrayList<>(methods.length); private Method samMethod = null; private final List<Method> methodsToBridge = new ArrayList<>(methods.length); - private boolean defaultMethodFound = false; + private boolean conflictFoundBetweenDefaultAndBridge = false; MethodAnalyzer() { String samMethodName = samInfo.getName(); @@ -315,31 +361,36 @@ int samParamLength = samParamTypes.length; Class<?> samReturnType = samMethodType.returnType(); Class<?> objectClass = Object.class; + List<Method> defaultMethods = new ArrayList<>(methods.length); for (Method m : methods) { if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { Class<?>[] mParamTypes = m.getParameterTypes(); if (mParamTypes.length == samParamLength) { + // Method matches name and parameter length -- and is not Object if (Modifier.isAbstract(m.getModifiers())) { - // Exclude methods with duplicate signatures - if (methodUnique(m)) { - if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) { - // Exact match, this is the SAM method signature - samMethod = m; - } else { - methodsToBridge.add(m); - } + // Method is abstract + if (m.getReturnType().equals(samReturnType) + && Arrays.equals(mParamTypes, samParamTypes)) { + // Exact match, this is the SAM method signature + samMethod = m; + } else if (!hasMatchingBridgeSignature(m)) { + // Record bridges, exclude methods with duplicate signatures + methodsToBridge.add(m); } } else { - // This is a default method, flag for special processing - defaultMethodFound = true; - // Ignore future matching abstracts. - // Note, due to reabstraction, this is really a punt, hence pass-off to VM - methodUnique(m); + // Record default methods for conflict testing + defaultMethods.add(m); } } } } + for (Method dm : defaultMethods) { + if (hasMatchingBridgeSignature(dm)) { + conflictFoundBetweenDefaultAndBridge = true; + break; + } + } } Method getSamMethod() { @@ -350,27 +401,26 @@ return methodsToBridge; } - boolean wasDefaultMethodFound() { - return defaultMethodFound; + boolean conflictFoundBetweenDefaultAndBridge() { + return conflictFoundBetweenDefaultAndBridge; } /** - * Search the list of previously found methods to determine if there is a method with the same signature - * (return and parameter types) as the specified method. If it wasn't found before, add to the found list. + * Search the list of previously found bridge methods to determine if there is a method with the same signature + * (return and parameter types) as the specified method. * * @param m The method to match - * @return False if the method was found, True otherwise + * @return True if the method was found, False otherwise */ - private boolean methodUnique(Method m) { + private boolean hasMatchingBridgeSignature(Method m) { Class<?>[] ptypes = m.getParameterTypes(); Class<?> rtype = m.getReturnType(); - for (Method md : methodsFound) { + for (Method md : methodsToBridge) { if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { + return true; + } + } return false; } } - methodsFound.add(m); - return true; - } - } }
--- a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Fri Feb 15 11:06:52 2013 +0000 +++ b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Sat Feb 16 12:36:54 2013 -0800 @@ -36,21 +36,28 @@ import java.security.PrivilegedAction; /** - * InnerClassLambdaMetafactory + * Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite. + * + * @see LambdaMetafactory */ -/*non-public*/ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { +/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { private static final int CLASSFILE_VERSION = 51; - private static final Type TYPE_VOID = Type.getType(void.class); private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); private static final String NAME_MAGIC_ACCESSOR_IMPL = "java/lang/invoke/MagicLambdaImpl"; - private static final String NAME_SERIALIZABLE = "java/io/Serializable"; private static final String NAME_CTOR = "<init>"; //Serialization support - private static final String NAME_SERIALIZED_LAMBDA = "com/oracle/java/lang/invoke/SerializedLambdaImpl"; + private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; private static final String NAME_OBJECT = "java/lang/Object"; + private static final String DESCR_CTOR_SERIALIZED_LAMBDA + = MethodType.methodType(void.class, + String.class, + int.class, String.class, String.class, String.class, + int.class, String.class, String.class, String.class, + String.class, + Object[].class).toMethodDescriptorString(); // Used to ensure that each spun class name is unique private static final AtomicInteger counter = new AtomicInteger(0); @@ -70,7 +77,7 @@ private final Type[] instantiatedArgumentTypes; // ASM types for the functional interface arguments /** - * Meta-factory constructor. + * General meta-factory constructor, standard cases and allowing for uncommon options such as serialization. * * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges * of the caller. @@ -83,16 +90,23 @@ * @param implMethod The implementation method which should be called (with suitable adaptation of argument * types, return types, and adjustment for captured arguments) when methods of the resulting * functional interface instance are invoked. - * @param instantiatedMethodType The signature of the SAM method from the functional interface's perspective + * @param instantiatedMethodType The signature of the primary functional interface method after type variables + * are substituted with their instantiation from the capture site + * @param flags A bitmask containing flags that may influence the translation of this lambda expression. Defined + * fields include FLAG_SERIALIZABLE. + * @param markerInterfaces Additional interfaces which the lambda object should implement. * @throws ReflectiveOperationException + * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated */ public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, MethodType invokedType, MethodHandle samMethod, MethodHandle implMethod, - MethodType instantiatedMethodType) - throws ReflectiveOperationException { - super(caller, invokedType, samMethod, implMethod, instantiatedMethodType); + MethodType instantiatedMethodType, + int flags, + Class<?>[] markerInterfaces) + throws ReflectiveOperationException, LambdaConversionException { + super(caller, invokedType, samMethod, implMethod, instantiatedMethodType, flags, markerInterfaces); implMethodClassName = implDefiningClass.getName().replace('.', '/'); implMethodName = implInfo.getName(); implMethodDesc = implMethodType.toMethodDescriptorString(); @@ -109,7 +123,6 @@ argNames[i] = "arg$" + (i + 1); } instantiatedArgumentTypes = Type.getArgumentTypes(instantiatedMethodType.toMethodDescriptorString()); - } /** @@ -120,7 +133,8 @@ * * @return a CallSite, which, when invoked, will return an instance of the * functional interface - * @throws ReflectiveOperationException, LambdaConversionException + * @throws ReflectiveOperationException + * @throws LambdaConversionException If properly formed functional interface is not found */ @Override CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException { @@ -151,8 +165,8 @@ } else { return new ConstantCallSite( MethodHandles.Lookup.IMPL_LOOKUP - .findConstructor(innerClass, constructorType) - .asType(constructorType.changeReturnType(samBase))); + .findConstructor(innerClass, constructorType) + .asType(constructorType.changeReturnType(samBase))); } } @@ -161,16 +175,23 @@ * interface, define and return the class. * * @return a Class which implements the functional interface + * @throws LambdaConversionException If properly formed functional interface is not found */ - private <T> Class<? extends T> spinInnerClass() throws LambdaConversionException { + private Class<?> spinInnerClass() throws LambdaConversionException { String samName = samBase.getName().replace('.', '/'); - - cw.visit(CLASSFILE_VERSION, ACC_SUPER, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL, - isSerializable ? new String[]{samName, NAME_SERIALIZABLE} : new String[]{samName}); + String[] interfaces = new String[markerInterfaces.length + 1]; + interfaces[0] = samName; + for (int i=0; i<markerInterfaces.length; i++) { + interfaces[i+1] = markerInterfaces[i].getName().replace('.', '/'); + } + cw.visit(CLASSFILE_VERSION, ACC_SUPER, + lambdaClassName, null, + NAME_MAGIC_ACCESSOR_IMPL, interfaces); // Generate final fields to be filled in by constructor for (int i = 0; i < argTypes.length; i++) { - FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(), null, null); + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(), + null, null); fv.visitEnd(); } @@ -180,26 +201,24 @@ // Forward the SAM method if (ma.getSamMethod() == null) { - throw new LambdaConversionException(String.format("SAM method not found: %s", samMethodType)); + throw new LambdaConversionException(String.format("Functional interface method not found: %s", samMethodType)); } else { generateForwardingMethod(ma.getSamMethod(), false); } // Forward the bridges - // @@@ Once the VM can do fail-over, uncomment the default method test - if (!ma.getMethodsToBridge().isEmpty() /* && !ma.wasDefaultMethodFound() */) { + // @@@ The commented-out code is temporary, pending the VM's ability to bridge all methods on request + // @@@ Once the VM can do fail-over, uncomment the !ma.wasDefaultMethodFound() test, and emit the appropriate + // @@@ classfile attribute to request custom bridging. See 8002092. + if (!ma.getMethodsToBridge().isEmpty() /* && !ma.conflictFoundBetweenDefaultAndBridge() */ ) { for (Method m : ma.getMethodsToBridge()) { generateForwardingMethod(m, true); } } - /***** Serialization not yet supported if (isSerializable) { - String samMethodName = samInfo.getName(); - Type samType = Type.getType(samBase); - generateSerializationMethod(samType, samMethodName); + generateWriteReplace(); } - ******/ cw.visitEnd(); @@ -212,7 +231,7 @@ try (FileOutputStream fos = new FileOutputStream(lambdaClassName.replace('/', '.') + ".class")) { fos.write(classBytes); } catch (IOException ex) { - Logger.getLogger(InnerClassLambdaMetafactory.class.getName()).log(Level.SEVERE, null, ex); + PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName()).severe(ex.getMessage(), ex); } ***/ @@ -228,7 +247,8 @@ } ); - return (Class<? extends T>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, loader, pd); + return (Class<?>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, + loader, pd); } /** @@ -253,40 +273,44 @@ } /** - * Generate the serialization method (if needed) + * Generate the writeReplace method (if needed for serialization) */ - /****** This code is out of date -- known to be wrong -- and not currently used ****** - private void generateSerializationMethod(Type samType, String samMethodName) { - String samMethodDesc = samMethodType.toMethodDescriptorString(); - TypeConvertingMethodAdapter mv = new TypeConvertingMethodAdapter(cw.visitMethod(ACC_PRIVATE + ACC_FINAL, NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, null, null)); + private void generateWriteReplace() { + TypeConvertingMethodAdapter mv + = new TypeConvertingMethodAdapter(cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, + null, null)); mv.visitCode(); mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); - mv.dup(); - mv.visitLdcInsn(samType); - mv.visitLdcInsn(samMethodName); - mv.visitLdcInsn(samMethodDesc); - mv.visitLdcInsn(Type.getType(implDefiningClass)); - mv.visitLdcInsn(implMethodName); - mv.visitLdcInsn(implMethodDesc); + mv.visitInsn(DUP);; + mv.visitLdcInsn(targetClass.getName().replace('.', '/')); + mv.visitLdcInsn(samInfo.getReferenceKind()); + mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/')); + mv.visitLdcInsn(samInfo.getName()); + mv.visitLdcInsn(samInfo.getMethodType().toMethodDescriptorString()); + mv.visitLdcInsn(implInfo.getReferenceKind()); + mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); + mv.visitLdcInsn(implInfo.getName()); + mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); + mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString()); mv.iconst(argTypes.length); mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT); for (int i = 0; i < argTypes.length; i++) { - mv.dup(); + mv.visitInsn(DUP); mv.iconst(i); mv.visitVarInsn(ALOAD, 0); - mv.getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor()); - mv.boxIfPrimitive(argTypes[i]); + mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argTypes[i].getDescriptor()); + mv.boxIfTypePrimitive(argTypes[i]); mv.visitInsn(AASTORE); } - mv.invokespecial(NAME_SERIALIZED_LAMBDA, NAME_CTOR, - "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"); + mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, + DESCR_CTOR_SERIALIZED_LAMBDA); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored mv.visitEnd(); } - ********/ /** * Generate a method which calls the lambda implementation method, @@ -321,11 +345,11 @@ if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { visitTypeInsn(NEW, implMethodClassName); - dup(); + visitInsn(DUP);; } for (int i = 0; i < argTypes.length; i++) { visitVarInsn(ALOAD, 0); - getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor()); + visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argTypes[i].getDescriptor()); } convertArgumentTypes(Type.getArgumentTypes(m)); @@ -337,7 +361,7 @@ // Note: if adapting from non-void to void, the 'return' instruction will pop the unneeded result Type samReturnType = Type.getReturnType(m); convertType(implMethodReturnType, samReturnType, samReturnType); - areturn(samReturnType); + visitInsn(samReturnType.getOpcode(Opcodes.IRETURN)); visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored visitEnd(); @@ -352,7 +376,7 @@ Type rcvrType = samArgumentTypes[0]; Type instantiatedRcvrType = instantiatedArgumentTypes[0]; - load(lvIndex + 1, rcvrType); + visitVarInsn(rcvrType.getOpcode(ILOAD), lvIndex + 1); lvIndex += rcvrType.getSize(); convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType); } @@ -362,7 +386,7 @@ Type targetType = implMethodArgumentTypes[argOffset + i]; Type instantiatedArgType = instantiatedArgumentTypes[i]; - load(lvIndex + 1, argType); + visitVarInsn(argType.getOpcode(ILOAD), lvIndex + 1); lvIndex += argType.getSize(); convertType(argType, targetType, instantiatedArgType); } @@ -388,45 +412,5 @@ throw new InternalError("Unexpected invocation kind: " + implKind); } } - - /** - * The following methods are copied from - * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very - * small and fast Java bytecode manipulation framework. Copyright (c) - * 2000-2005 INRIA, France Telecom All rights reserved. - * - * Subclass with that (removing these methods) if that package/class is - * ever added to the JDK. - */ - private void iconst(final int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } - } - - private void load(final int var, final Type type) { - mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), var); - } - - private void dup() { - mv.visitInsn(Opcodes.DUP); - } - - private void areturn(final Type t) { - mv.visitInsn(t.getOpcode(Opcodes.IRETURN)); - } - - private void getfield( - final String owner, - final String name, - final String desc) { - mv.visitFieldInsn(Opcodes.GETFIELD, owner, name, desc); - } } }
--- a/src/share/classes/java/lang/invoke/LambdaMetafactory.java Fri Feb 15 11:06:52 2013 +0000 +++ b/src/share/classes/java/lang/invoke/LambdaMetafactory.java Sat Feb 16 12:36:54 2013 -0800 @@ -42,14 +42,13 @@ * method, and the static types of the captured lambda arguments, and link a call site which, when invoked, * produces the lambda object. * - * <p>Two pieces of information are needed about the functional interface: the SAM method and the type of the SAM - * method in the functional interface. The type can be different when parameterized types are used. For example, - * consider - * <code>interface I<T> { int m(T x); }</code> if this SAM type is used in a lambda - * <code>I<Byte> v = ...</code>, we need both the actual SAM method which has the signature - * <code>(Object)int</code> and the functional interface type of the method, which has signature - * <code>(Byte)int</code>. The latter is the instantiated erased functional interface method type, or - * simply <I>instantiated method type</I>. + * <p>When parameterized types are used, the instantiated type of the functional interface method may be different + * from that in the functional interface. For example, consider + * <code>interface I<T> { int m(T x); }</code> if this functional interface type is used in a lambda + * <code>I<Byte> v = ...</code>, we need both the actual functional interface method which has the signature + * <code>(Object)int</code> and the erased instantiated type of the functional interface method (or simply + * <I>instantiated method type</I>), which has signature + * <code>(Byte)int</code>. * * <p>While functional interfaces only have a single abstract method from the language perspective (concrete * methods in Object are and default methods may be present), at the bytecode level they may actually have multiple @@ -138,11 +137,25 @@ * </tr> * </table> * + * The default bootstrap ({@link #metaFactory}) represents the common cases and uses an optimized protocol. + * Alternate bootstraps (e.g., {@link #altMetaFactory}) exist to support uncommon cases such as serialization + * or additional marker superinterfaces. * */ public class LambdaMetafactory { + /** Flag for alternate metafactories indicating the lambda object is must to be serializable */ + public static final int FLAG_SERIALIZABLE = 1 << 0; + /** + * Flag for alternate metafactories indicating the lambda object implements other marker interfaces + * besides Serializable + */ + public static final int FLAG_MARKERS = 1 << 1; + + private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; + +/** * Standard meta-factory for conversion of lambda expressions or method references to functional interfaces. * * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges @@ -158,7 +171,8 @@ * @param implMethod The implementation method which should be called (with suitable adaptation of argument * types, return types, and adjustment for captured arguments) when methods of the resulting * functional interface instance are invoked. - * @param instantiatedMethodType The signature of the SAM method from the functional interface's perspective + * @param instantiatedMethodType The signature of the primary functional interface method after type variables + * are substituted with their instantiation from the capture site * @return a CallSite, which, when invoked, will return an instance of the functional interface * @throws ReflectiveOperationException * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated @@ -171,7 +185,85 @@ MethodType instantiatedMethodType) throws ReflectiveOperationException, LambdaConversionException { AbstractValidatingLambdaMetafactory mf; - mf = new InnerClassLambdaMetafactory(caller, invokedType, samMethod, implMethod, instantiatedMethodType); + mf = new InnerClassLambdaMetafactory(caller, invokedType, samMethod, implMethod, instantiatedMethodType, + 0, EMPTY_CLASS_ARRAY); + mf.validateMetafactoryArgs(); + return mf.buildCallSite(); + } + + /** + * Alternate meta-factory for conversion of lambda expressions or method references to functional interfaces, + * which supports serialization and other uncommon options. + * + * The declared argument list for this method is: + * + * CallSite altMetaFactory(MethodHandles.Lookup caller, + * String invokedName, + * MethodType invokedType, + * Object... args) + * + * but it behaves as if the argument list is: + * + * CallSite altMetaFactory(MethodHandles.Lookup caller, + * String invokedName, + * MethodType invokedType, + * MethodHandle samMethod + * MethodHandle implMethod, + * MethodType instantiatedMethodType, + * int flags, + * int markerInterfaceCount, // IF flags has MARKERS set + * Class... markerInterfaces // IF flags has MARKERS set + * ) + * + * + * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges + * of the caller. + * @param invokedName Stacked automatically by VM; the name of the invoked method as it appears at the call site. + * Currently unused. + * @param invokedType Stacked automatically by VM; the signature of the invoked method, which includes the + * expected static type of the returned lambda object, and the static types of the captured + * arguments for the lambda. In the event that the implementation method is an instance method, + * the first argument in the invocation signature will correspond to the receiver. + * @param samMethod The primary method in the functional interface to which the lambda or method reference is + * being converted, represented as a method handle. + * @param implMethod The implementation method which should be called (with suitable adaptation of argument + * types, return types, and adjustment for captured arguments) when methods of the resulting + * functional interface instance are invoked. + * @param instantiatedMethodType The signature of the primary functional interface method after type variables + * are substituted with their instantiation from the capture site + * @param flags A bitmask containing flags that may influence the translation of this lambda expression. Defined + * fields include FLAG_SERIALIZABLE and FLAG_MARKERS. + * @param markerInterfaceCount If the FLAG_MARKERS flag is set, this is a count of the number of additional + * marker interfaces + * @param markerInterfaces If the FLAG_MARKERS flag is set, this consists of Class objects identifying additional + * marker interfaces which the lambda object should implement, whose count equals + * markerInterfaceCount + * @return a CallSite, which, when invoked, will return an instance of the functional interface + * @throws ReflectiveOperationException + * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated + */ + public static CallSite altMetaFactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + Object... args) + throws ReflectiveOperationException, LambdaConversionException { + MethodHandle samMethod = (MethodHandle)args[0]; + MethodHandle implMethod = (MethodHandle)args[1]; + MethodType instantiatedMethodType = (MethodType)args[2]; + int flags = (Integer) args[3]; + Class<?>[] markerInterfaces; + int argIndex = 4; + if ((flags & FLAG_MARKERS) != 0) { + int markerCount = (Integer) args[argIndex++]; + markerInterfaces = new Class<?>[markerCount]; + System.arraycopy(args, argIndex, markerInterfaces, 0, markerCount); + argIndex += markerCount; + } + else + markerInterfaces = EMPTY_CLASS_ARRAY; + AbstractValidatingLambdaMetafactory mf; + mf = new InnerClassLambdaMetafactory(caller, invokedType, samMethod, implMethod, instantiatedMethodType, + flags, markerInterfaces); mf.validateMetafactoryArgs(); return mf.buildCallSite(); }
--- a/src/share/classes/java/lang/invoke/MethodHandleInfo.java Fri Feb 15 11:06:52 2013 +0000 +++ b/src/share/classes/java/lang/invoke/MethodHandleInfo.java Sat Feb 16 12:36:54 2013 -0800 @@ -26,8 +26,11 @@ package java.lang.invoke; import java.lang.invoke.MethodHandleNatives.Constants; -//Not yet public: public -class MethodHandleInfo { +/** + * Cracking (reflecting) method handles back into their constituent symbolic parts. + * + */ +final class MethodHandleInfo { public static final int REF_NONE = Constants.REF_NONE, REF_getField = Constants.REF_getField, @@ -65,7 +68,33 @@ return methodType; } + public int getModifiers() { + return -1; //TODO + } + public int getReferenceKind() { return referenceKind; } + + static String getReferenceKindString(int referenceKind) { + switch (referenceKind) { + case REF_NONE: return "REF_NONE"; + case REF_getField: return "getfield"; + case REF_getStatic: return "getstatic"; + case REF_putField: return "putfield"; + case REF_putStatic: return "putstatic"; + case REF_invokeVirtual: return "invokevirtual"; + case REF_invokeStatic: return "invokestatic"; + case REF_invokeSpecial: return "invokespecial"; + case REF_newInvokeSpecial: return "newinvokespecial"; + case REF_invokeInterface: return "invokeinterface"; + default: return "UNKNOWN_REFENCE_KIND" + "[" + referenceKind + "]"; + } + } + + @Override + public String toString() { + return String.format("%s %s.%s:%s", getReferenceKindString(referenceKind), + declaringClass.getName(), name, methodType); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/lang/invoke/SerializedLambda.java Sat Feb 16 12:36:54 2013 -0800 @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.lang.invoke; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; + +/** + * Serialized form of a lambda expression. The properties of this class represent the information that is present + * at the lambda factory site, including the identity of the primary functional interface method, the identity of the + * implementation method, and any variables captured from the local environment at the time of lambda capture. + * + * @see LambdaMetafactory + */ +public final class SerializedLambda implements Serializable { + private static final long serialVersionUID = 8025925345765570181L; + private final String capturingClass; + private final String functionalInterfaceClass; + private final String functionalInterfaceMethodName; + private final String functionalInterfaceMethodSignature; + private final int functionalInterfaceMethodKind; + private final String implClass; + private final String implMethodName; + private final String implMethodSignature; + private final int implMethodKind; + private final String instantiatedMethodType; + private final Object[] capturedArgs; + + /** + * Create a {@code SerializedLambda} from the low-level information present at the lambda factory site. + * + * @param capturingClass The class in which the lambda expression appears + * @param functionalInterfaceMethodKind Method handle kind (see {@link MethodHandleInfo}) for the + * functional interface method handle present at the lambda factory site + * @param functionalInterfaceClass Name, in slash-delimited form, for the functional interface class present at the + * lambda factory site + * @param functionalInterfaceMethodName Name of the primary method for the functional interface present at the + * lambda factory site + * @param functionalInterfaceMethodSignature Signature of the primary method for the functional interface present + * at the lambda factory site + * @param implMethodKind Method handle kind for the implementation method + * @param implClass Name, in slash-delimited form, for the class holding the implementation method + * @param implMethodName Name of the implementation method + * @param implMethodSignature Signature of the implementation method + * @param instantiatedMethodType The signature of the primary functional interface method after type variables + * are substituted with their instantiation from the capture site + * @param capturedArgs The dynamic arguments to the lambda factory site, which represent variables captured by + * the lambda + */ + public SerializedLambda(String capturingClass, + int functionalInterfaceMethodKind, + String functionalInterfaceClass, + String functionalInterfaceMethodName, + String functionalInterfaceMethodSignature, + int implMethodKind, + String implClass, + String implMethodName, + String implMethodSignature, + String instantiatedMethodType, + Object[] capturedArgs) { + this.capturingClass = capturingClass; + this.functionalInterfaceMethodKind = functionalInterfaceMethodKind; + this.functionalInterfaceClass = functionalInterfaceClass; + this.functionalInterfaceMethodName = functionalInterfaceMethodName; + this.functionalInterfaceMethodSignature = functionalInterfaceMethodSignature; + this.implMethodKind = implMethodKind; + this.implClass = implClass; + this.implMethodName = implMethodName; + this.implMethodSignature = implMethodSignature; + this.instantiatedMethodType = instantiatedMethodType; + this.capturedArgs = Objects.requireNonNull(capturedArgs).clone(); + } + + /** Get the name of the class that captured this lambda */ + public String getCapturingClass() { + return capturingClass; + } + + /** Get the name of the functional interface class to which this lambda has been converted */ + public String getFunctionalInterfaceClass() { + return functionalInterfaceClass; + } + + /** Get the name of the primary method for the functional interface to which this lambda has been converted */ + public String getFunctionalInterfaceMethodName() { + return functionalInterfaceMethodName; + } + + /** Get the signature of the primary method for the functional interface to which this lambda has been converted */ + public String getFunctionalInterfaceMethodSignature() { + return functionalInterfaceMethodSignature; + } + + /** Get the method handle kind (see {@link MethodHandleInfo}) of the primary method for the functional interface + * to which this lambda has been converted */ + public int getFunctionalInterfaceMethodKind() { + return functionalInterfaceMethodKind; + } + + /** Get the name of the class containing the implementation method */ + public String getImplClass() { + return implClass; + } + + /** Get the name of the implementation method */ + public String getImplMethodName() { + return implMethodName; + } + + /** Get the signature of the implementation method */ + public String getImplMethodSignature() { + return implMethodSignature; + } + + /** Get the method handle kind (see {@link MethodHandleInfo}) of the implementation method */ + public int getImplMethodKind() { + return implMethodKind; + } + + /** + * Get the signature of the primary functional interface method after type variables are substituted with + * their instantiation from the capture site + */ + public final String getInstantiatedMethodType() { + return instantiatedMethodType; + } + + /** Get the count of dynamic arguments to the lambda capture site */ + public int getCapturedArgCount() { + return capturedArgs.length; + } + + /** Get a dynamic argument to the lambda capture site */ + public Object getCapturedArg(int i) { + return capturedArgs[i]; + } + + private Object readResolve() throws ReflectiveOperationException { + try { + Method deserialize = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() { + @Override + public Method run() throws Exception { + Class<?> clazz = Class.forName(capturingClass.replace('/', '.'), true, + Thread.currentThread().getContextClassLoader()); + Method m = clazz.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class); + m.setAccessible(true); + return m; + } + }); + + return deserialize.invoke(null, this); + } + catch (PrivilegedActionException e) { + Exception cause = e.getException(); + if (cause instanceof ReflectiveOperationException) + throw (ReflectiveOperationException) cause; + else if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + else + throw new RuntimeException("Exception in SerializedLambda.readResolve", e); + } + } + + @Override + public String toString() { + return String.format("SerializedLambda[capturingClass=%s, functionalInterfaceMethod=%s %s.%s:%s, " + + "implementation=%s %s.%s:%s, instantiatedMethodType=%s, numCaptured=%d]", + capturingClass, MethodHandleInfo.getReferenceKindString(functionalInterfaceMethodKind), + functionalInterfaceClass, functionalInterfaceMethodName, functionalInterfaceMethodSignature, + MethodHandleInfo.getReferenceKindString(implMethodKind), implClass, implMethodName, + implMethodSignature, instantiatedMethodType, capturedArgs.length); + } + + /* + // @@@ Review question: is it worthwhile implementing a versioned serialization protocol? + + private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + } +*/ +}
--- a/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Fri Feb 15 11:06:52 2013 +0000 +++ b/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Sat Feb 16 12:36:54 2013 -0800 @@ -27,6 +27,7 @@ import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; import sun.invoke.util.Wrapper; import static sun.invoke.util.Wrapper.*; @@ -49,6 +50,9 @@ private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16]; + // Table of wrappers for primitives, indexed by ASM type sorts + private static final Wrapper[] FROM_TYPE_SORT = new Wrapper[16]; + static { for (Wrapper w : Wrapper.values()) { if (w.basicTypeChar() != 'L') { @@ -71,6 +75,15 @@ initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR); initWidening(DOUBLE, Opcodes.F2D, FLOAT); initWidening(DOUBLE, Opcodes.L2D, LONG); + + FROM_TYPE_SORT[Type.BYTE] = Wrapper.BYTE; + FROM_TYPE_SORT[Type.SHORT] = Wrapper.SHORT; + FROM_TYPE_SORT[Type.INT] = Wrapper.INT; + FROM_TYPE_SORT[Type.LONG] = Wrapper.LONG; + FROM_TYPE_SORT[Type.CHAR] = Wrapper.CHAR; + FROM_TYPE_SORT[Type.FLOAT] = Wrapper.FLOAT; + FROM_TYPE_SORT[Type.DOUBLE] = Wrapper.DOUBLE; + FROM_TYPE_SORT[Type.BOOLEAN] = Wrapper.BOOLEAN; } private static void initWidening(Wrapper to, int opcode, Wrapper... from) { @@ -124,8 +137,9 @@ return "()" + w.basicTypeChar(); } - void boxIfPrimitive(Wrapper w) { - if (w.zero() != null) { + void boxIfTypePrimitive(Type t) { + Wrapper w = FROM_TYPE_SORT[t.getSort()]; + if (w != null) { box(w); } } @@ -264,4 +278,22 @@ } } } + + /** + * The following method is copied from + * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small + * and fast Java bytecode manipulation framework. + * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. + */ + void iconst(final int cst) { + if (cst >= -1 && cst <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + cst); + } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { + mv.visitIntInsn(Opcodes.BIPUSH, cst); + } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, cst); + } else { + mv.visitLdcInsn(cst); + } + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/lang/invoke/lambda/LambdaSerialization.java Sat Feb 16 12:36:54 2013 -0800 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +@test +@bug 8004970 +@summary Lambda serialization + +*/ + +import java.io.*; + +public class LambdaSerialization { + + static int assertionCount = 0; + + static void assertTrue(boolean cond) { + assertionCount++; + if (!cond) + throw new AssertionError(); + } + + public static void main(String[] args) throws Exception { + try { + // Write lambdas out + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutput out = new ObjectOutputStream(baos); + + write(out, z -> "[" + z + "]" ); + write(out, z -> z + z ); + write(out, z -> "blah" ); + out.flush(); + out.close(); + + // Read them back + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + readAssert(in, "[X]"); + readAssert(in, "XX"); + readAssert(in, "blah"); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } + assertTrue(assertionCount == 3); + } + + static void write(ObjectOutput out, LSI lamb) throws IOException { + out.writeObject(lamb); + } + + static void readAssert(ObjectInputStream in, String expected) throws IOException, ClassNotFoundException { + LSI ls = (LSI) in.readObject(); + String result = ls.convert("X"); + System.out.printf("Result: %s\n", result); + assertTrue(result.equals(expected)); + } +} + +interface LSI extends Serializable { + String convert(String x); +}