提交 90188c0d 编写于 作者: R rfield

8004970: Implement serialization in the lambda metafactory

Reviewed-by: forax
上级 fc05a616
......@@ -34,11 +34,11 @@ import sun.invoke.util.Wrapper;
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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
* @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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
// 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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
: 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 @@ import static sun.invoke.util.Wrapper.*;
}
/*********** 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 @@ import static sun.invoke.util.Wrapper.*;
***********************/
/**
* 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 @@ import static sun.invoke.util.Wrapper.*;
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 @@ import static sun.invoke.util.Wrapper.*;
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;
}
}
}
......@@ -36,21 +36,28 @@ import java.security.AccessController;
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 @@ import java.security.PrivilegedAction;
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 @@ import java.security.PrivilegedAction;
* @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 @@ import java.security.PrivilegedAction;
argNames[i] = "arg$" + (i + 1);
}
instantiatedArgumentTypes = Type.getArgumentTypes(instantiatedMethodType.toMethodDescriptorString());
}
/**
......@@ -120,7 +133,8 @@ import java.security.PrivilegedAction;
*
* @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 @@ import java.security.PrivilegedAction;
} 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 @@ import java.security.PrivilegedAction;
* 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 @@ import java.security.PrivilegedAction;
// 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 @@ import java.security.PrivilegedAction;
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 @@ import java.security.PrivilegedAction;
}
);
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 @@ import java.security.PrivilegedAction;
}
/**
* 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 @@ import java.security.PrivilegedAction;
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 @@ import java.security.PrivilegedAction;
// 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 @@ import java.security.PrivilegedAction;
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 @@ import java.security.PrivilegedAction;
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 @@ import java.security.PrivilegedAction;
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);
}
}
}
......@@ -42,14 +42,13 @@ package java.lang.invoke;
* 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&lt;T&gt; { int m(T x); }</code> if this SAM type is used in a lambda
* <code>I&lt;Byte&gt; 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&lt;T&gt; { int m(T x); }</code> if this functional interface type is used in a lambda
* <code>I&lt;Byte&gt; 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 @@ package java.lang.invoke;
* </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 @@ public class LambdaMetafactory {
* @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 @@ public class LambdaMetafactory {
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();
}
......
......@@ -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 @@ class MethodHandleInfo {
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);
}
}
/*
* 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 {
}
*/
}
......@@ -27,6 +27,7 @@ package java.lang.invoke;
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 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
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 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
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 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
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 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
}
}
}
/**
* 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);
}
}
}
/*
* 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);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册