8000806: Implement runtime lambda metafactory
Implement lambda invokedynamic bootstrap by generating at runtime an inner class that implements the functional interface Reviewed-by: twisti
This commit is contained in:
parent
78aeafc1a2
commit
020472d122
@ -0,0 +1,376 @@
|
||||
/*
|
||||
* 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.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import sun.invoke.util.Wrapper;
|
||||
import static sun.invoke.util.Wrapper.*;
|
||||
|
||||
/**
|
||||
* Abstract implementation of a meta-factory which provides parameter unrolling and input validation.
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
/*non-public*/ abstract class AbstractValidatingLambdaMetafactory {
|
||||
|
||||
/*
|
||||
* For context, the comments for the following fields are marked in quotes with their values, given this program:
|
||||
* interface II<T> { Object foo(T x); }
|
||||
* interface JJ<R extends Number> extends II<R> { }
|
||||
* class CC { String impl(int i) { return "impl:"+i; }}
|
||||
* class X {
|
||||
* public static void main(String[] args) {
|
||||
* JJ<Integer> iii = (new CC())::impl;
|
||||
* System.out.printf(">>> %s\n", iii.foo(44));
|
||||
* }}
|
||||
*/
|
||||
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 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 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"
|
||||
|
||||
|
||||
/**
|
||||
* Meta-factory constructor.
|
||||
*
|
||||
* @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges
|
||||
* of the caller.
|
||||
* @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 SAM method from the functional interface's perspective
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller,
|
||||
MethodType invokedType,
|
||||
MethodHandle samMethod,
|
||||
MethodHandle implMethod,
|
||||
MethodType instantiatedMethodType)
|
||||
throws ReflectiveOperationException {
|
||||
this.targetClass = caller.lookupClass();
|
||||
this.invokedType = invokedType;
|
||||
|
||||
this.samBase = invokedType.returnType();
|
||||
this.isSerializable = Serializable.class.isAssignableFrom(samBase);
|
||||
|
||||
this.samInfo = new MethodHandleInfo(samMethod);
|
||||
this.samClass = samInfo.getDeclaringClass();
|
||||
this.samMethodType = samInfo.getMethodType();
|
||||
|
||||
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
|
||||
this.implIsInstanceMethod =
|
||||
implKind == MethodHandleInfo.REF_invokeVirtual ||
|
||||
implKind == MethodHandleInfo.REF_invokeSpecial ||
|
||||
implKind == MethodHandleInfo.REF_invokeInterface;
|
||||
this.implDefiningClass = implInfo.getDeclaringClass();
|
||||
this.implMethodType = implInfo.getMethodType();
|
||||
|
||||
this.instantiatedMethodType = instantiatedMethodType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CallSite.
|
||||
*
|
||||
* @return a CallSite, which, when invoked, will return an instance of the
|
||||
* functional interface
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
abstract CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException;
|
||||
|
||||
/**
|
||||
* Check the meta-factory arguments for errors
|
||||
* @throws LambdaConversionException if there are improper conversions
|
||||
*/
|
||||
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()));
|
||||
}
|
||||
|
||||
switch (implKind) {
|
||||
case MethodHandleInfo.REF_invokeInterface:
|
||||
case MethodHandleInfo.REF_invokeVirtual:
|
||||
case MethodHandleInfo.REF_invokeStatic:
|
||||
case MethodHandleInfo.REF_newInvokeSpecial:
|
||||
case MethodHandleInfo.REF_invokeSpecial:
|
||||
break;
|
||||
default:
|
||||
throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo));
|
||||
}
|
||||
|
||||
// Check arity: optional-receiver + captured + SAM == impl
|
||||
final int implArity = implMethodType.parameterCount();
|
||||
final int receiverArity = implIsInstanceMethod ? 1 : 0;
|
||||
final int capturedArity = invokedType.parameterCount();
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
// If instance: first captured arg (receiver) must be subtype of class where impl method is defined
|
||||
final int capturedStart;
|
||||
final int samStart;
|
||||
if (implIsInstanceMethod) {
|
||||
final Class<?> receiverClass;
|
||||
|
||||
// implementation is an instance method, adjust for receiver in captured variables / SAM arguments
|
||||
if (capturedArity == 0) {
|
||||
// receiver is function parameter
|
||||
capturedStart = 0;
|
||||
samStart = 1;
|
||||
receiverClass = instantiatedMethodType.parameterType(0);
|
||||
} else {
|
||||
// receiver is a captured variable
|
||||
capturedStart = 1;
|
||||
samStart = 0;
|
||||
receiverClass = invokedType.parameterType(0);
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
} else {
|
||||
// no receiver
|
||||
capturedStart = 0;
|
||||
samStart = 0;
|
||||
}
|
||||
|
||||
// Check for exact match on non-receiver captured arguments
|
||||
final int implFromCaptured = capturedArity - capturedStart;
|
||||
for (int i=0; i<implFromCaptured; i++) {
|
||||
Class<?> implParamType = implMethodType.parameterType(i);
|
||||
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));
|
||||
}
|
||||
}
|
||||
// Check for adaptation match on SAM arguments
|
||||
final int samOffset = samStart - implFromCaptured;
|
||||
for (int i=implFromCaptured; i<implArity; i++) {
|
||||
Class<?> implParamType = implMethodType.parameterType(i);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptation match: return type
|
||||
Class<?> expectedType = instantiatedMethodType.returnType();
|
||||
Class<?> actualReturnType =
|
||||
(implKind == MethodHandleInfo.REF_newInvokeSpecial)
|
||||
? implDefiningClass
|
||||
: implMethodType.returnType();
|
||||
if (!isAdaptableToAsReturn(actualReturnType, expectedType)) {
|
||||
throw new LambdaConversionException(
|
||||
String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check type adaptability
|
||||
* @param fromType
|
||||
* @param toType
|
||||
* @param strict If true, do strict checks, else allow that fromType may be parameterized
|
||||
* @return True if 'fromType' can be passed to an argument of 'toType'
|
||||
*/
|
||||
private boolean isAdaptableTo(Class<?> fromType, Class<?> toType, boolean strict) {
|
||||
if (fromType.equals(toType)) {
|
||||
return true;
|
||||
}
|
||||
if (fromType.isPrimitive()) {
|
||||
Wrapper wfrom = forPrimitiveType(fromType);
|
||||
if (toType.isPrimitive()) {
|
||||
// both are primitive: widening
|
||||
Wrapper wto = forPrimitiveType(toType);
|
||||
return wto.isConvertibleFrom(wfrom);
|
||||
} else {
|
||||
// from primitive to reference: boxing
|
||||
return toType.isAssignableFrom(wfrom.wrapperType());
|
||||
}
|
||||
} else {
|
||||
if (toType.isPrimitive()) {
|
||||
// from reference to primitive: unboxing
|
||||
Wrapper wfrom;
|
||||
if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) {
|
||||
// fromType is a primitive wrapper; unbox+widen
|
||||
Wrapper wto = forPrimitiveType(toType);
|
||||
return wto.isConvertibleFrom(wfrom);
|
||||
} else {
|
||||
// must be convertible to primitive
|
||||
return !strict;
|
||||
}
|
||||
} else {
|
||||
// both are reference types: fromType should be a superclass of toType.
|
||||
return strict? toType.isAssignableFrom(fromType) : true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check type adaptability for return types -- special handling of void type) and parameterized fromType
|
||||
* @param fromType
|
||||
* @param toType
|
||||
* @return True if 'fromType' can be converted to 'toType'
|
||||
*/
|
||||
private boolean isAdaptableToAsReturn(Class<?> fromType, Class<?> toType) {
|
||||
return toType.equals(void.class)
|
||||
|| !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false);
|
||||
}
|
||||
|
||||
|
||||
/*********** Logging support -- for debugging only
|
||||
static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only
|
||||
protected static void log(final String s) {
|
||||
MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static void log(final String s, final Throwable e) {
|
||||
MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println(s);
|
||||
e.printStackTrace(System.out);
|
||||
}
|
||||
});
|
||||
}
|
||||
***********************/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
|
||||
MethodAnalyzer() {
|
||||
String samMethodName = samInfo.getName();
|
||||
Class<?>[] samParamTypes = samMethodType.parameterArray();
|
||||
int samParamLength = samParamTypes.length;
|
||||
Class<?> samReturnType = samMethodType.returnType();
|
||||
Class<?> objectClass = Object.class;
|
||||
|
||||
for (Method m : methods) {
|
||||
if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) {
|
||||
Class<?>[] mParamTypes = m.getParameterTypes();
|
||||
if (mParamTypes.length == samParamLength) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Method getSamMethod() {
|
||||
return samMethod;
|
||||
}
|
||||
|
||||
List<Method> getMethodsToBridge() {
|
||||
return methodsToBridge;
|
||||
}
|
||||
|
||||
boolean wasDefaultMethodFound() {
|
||||
return defaultMethodFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param m The method to match
|
||||
* @return False if the method was found, True otherwise
|
||||
*/
|
||||
private boolean methodUnique(Method m) {
|
||||
Class<?>[] ptypes = m.getParameterTypes();
|
||||
Class<?> rtype = m.getReturnType();
|
||||
for (Method md : methodsFound) {
|
||||
if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
methodsFound.add(m);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/**
|
||||
* InnerClassLambdaMetafactory
|
||||
*/
|
||||
/*non-public*/ 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 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";
|
||||
|
||||
// Used to ensure that each spun class name is unique
|
||||
private static final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
// See context values in AbstractValidatingLambdaMetafactory
|
||||
private final String implMethodClassName; // Name of type containing implementation "CC"
|
||||
private final String implMethodName; // Name of implementation method "impl"
|
||||
private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;"
|
||||
private final Type[] implMethodArgumentTypes; // ASM types for implementaion method parameters
|
||||
private final Type implMethodReturnType; // ASM type for implementaion method return type "Ljava/lang/String;"
|
||||
private final MethodType constructorType; // Generated class constructor type "(CC)void"
|
||||
private final String constructorDesc; // Type descriptor for constructor "(LCC;)V"
|
||||
private final ClassWriter cw; // ASM class writer
|
||||
private final Type[] argTypes; // ASM types for the constructor arguments
|
||||
private final String[] argNames; // Generated names for the constructor arguments
|
||||
private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1"
|
||||
private final Type[] instantiatedArgumentTypes; // ASM types for the functional interface arguments
|
||||
|
||||
/**
|
||||
* Meta-factory constructor.
|
||||
*
|
||||
* @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges
|
||||
* of the caller.
|
||||
* @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 SAM method from the functional interface's perspective
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
|
||||
MethodType invokedType,
|
||||
MethodHandle samMethod,
|
||||
MethodHandle implMethod,
|
||||
MethodType instantiatedMethodType)
|
||||
throws ReflectiveOperationException {
|
||||
super(caller, invokedType, samMethod, implMethod, instantiatedMethodType);
|
||||
implMethodClassName = implDefiningClass.getName().replace('.', '/');
|
||||
implMethodName = implInfo.getName();
|
||||
implMethodDesc = implMethodType.toMethodDescriptorString();
|
||||
Type implMethodAsmType = Type.getMethodType(implMethodDesc);
|
||||
implMethodArgumentTypes = implMethodAsmType.getArgumentTypes();
|
||||
implMethodReturnType = implMethodAsmType.getReturnType();
|
||||
constructorType = invokedType.changeReturnType(Void.TYPE);
|
||||
constructorDesc = constructorType.toMethodDescriptorString();
|
||||
lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
|
||||
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
argTypes = Type.getArgumentTypes(constructorDesc);
|
||||
argNames = new String[argTypes.length];
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
argNames[i] = "arg$" + (i + 1);
|
||||
}
|
||||
instantiatedArgumentTypes = Type.getArgumentTypes(instantiatedMethodType.toMethodDescriptorString());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CallSite. Generate a class file which implements the functional
|
||||
* interface, define the class, if there are no parameters create an instance
|
||||
* of the class which the CallSite will return, otherwise, generate handles
|
||||
* which will call the class' constructor.
|
||||
*
|
||||
* @return a CallSite, which, when invoked, will return an instance of the
|
||||
* functional interface
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
@Override
|
||||
CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException {
|
||||
final Class<?> innerClass = spinInnerClass();
|
||||
if (invokedType.parameterCount() == 0) {
|
||||
return new ConstantCallSite(MethodHandles.constant(samBase, innerClass.newInstance()));
|
||||
} else {
|
||||
return new ConstantCallSite(
|
||||
MethodHandles.Lookup.IMPL_LOOKUP
|
||||
.findConstructor(innerClass, constructorType)
|
||||
.asType(constructorType.changeReturnType(samBase)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a class file which implements the functional
|
||||
* interface, define and return the class.
|
||||
*
|
||||
* @return a Class which implements the functional interface
|
||||
*/
|
||||
private <T> Class<? extends T> spinInnerClass() throws LambdaConversionException {
|
||||
String samName = samBase.getName().replace('.', '/');
|
||||
|
||||
cw.visit(CLASSFILE_VERSION, ACC_PUBLIC + ACC_SUPER, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL,
|
||||
isSerializable ? new String[]{samName, NAME_SERIALIZABLE} : new String[]{samName});
|
||||
|
||||
// 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);
|
||||
fv.visitEnd();
|
||||
}
|
||||
|
||||
generateConstructor();
|
||||
|
||||
MethodAnalyzer ma = new MethodAnalyzer();
|
||||
|
||||
// Forward the SAM method
|
||||
if (ma.getSamMethod() == null) {
|
||||
throw new LambdaConversionException(String.format("SAM 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() */) {
|
||||
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);
|
||||
}
|
||||
******/
|
||||
|
||||
cw.visitEnd();
|
||||
|
||||
// Define the generated class in this VM.
|
||||
|
||||
final byte[] classBytes = cw.toByteArray();
|
||||
|
||||
if (System.getProperty("debug.dump.generated") != null) {
|
||||
System.out.printf("Loaded: %s (%d bytes) %n", lambdaClassName, classBytes.length);
|
||||
try (FileOutputStream fos = new FileOutputStream(lambdaClassName.replace('/', '.') + ".class")) {
|
||||
fos.write(classBytes);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(InnerClassLambdaMetafactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
ClassLoader loader = targetClass.getClassLoader();
|
||||
ProtectionDomain pd = (loader == null) ? null : targetClass.getProtectionDomain();
|
||||
return (Class<? extends T>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, loader, pd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the constructor for the class
|
||||
*/
|
||||
private void generateConstructor() {
|
||||
// Generate constructor
|
||||
MethodVisitor ctor = cw.visitMethod(ACC_PUBLIC, NAME_CTOR, constructorDesc, null, null);
|
||||
ctor.visitCode();
|
||||
ctor.visitVarInsn(ALOAD, 0);
|
||||
ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR, METHOD_DESCRIPTOR_VOID);
|
||||
int lvIndex = 0;
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
ctor.visitVarInsn(ALOAD, 0);
|
||||
ctor.visitVarInsn(argTypes[i].getOpcode(ILOAD), lvIndex + 1);
|
||||
lvIndex += argTypes[i].getSize();
|
||||
ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argTypes[i].getDescriptor());
|
||||
}
|
||||
ctor.visitInsn(RETURN);
|
||||
ctor.visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
|
||||
ctor.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the serialization method (if needed)
|
||||
*/
|
||||
/****** 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));
|
||||
|
||||
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.iconst(argTypes.length);
|
||||
mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT);
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
mv.dup();
|
||||
mv.iconst(i);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor());
|
||||
mv.boxIfPrimitive(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.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,
|
||||
* converting arguments, as needed.
|
||||
* @param m The method whose signature should be generated
|
||||
* @param isBridge True if this methods should be flagged as a bridge
|
||||
*/
|
||||
private void generateForwardingMethod(Method m, boolean isBridge) {
|
||||
Class<?>[] exceptionTypes = m.getExceptionTypes();
|
||||
String[] exceptionNames = new String[exceptionTypes.length];
|
||||
for (int i = 0; i < exceptionTypes.length; i++) {
|
||||
exceptionNames[i] = exceptionTypes[i].getName().replace('.', '/');
|
||||
}
|
||||
String methodDescriptor = Type.getMethodDescriptor(m);
|
||||
int access = isBridge? ACC_PUBLIC | ACC_BRIDGE : ACC_PUBLIC;
|
||||
MethodVisitor mv = cw.visitMethod(access, m.getName(), methodDescriptor, null, exceptionNames);
|
||||
new ForwardingMethodGenerator(mv).generate(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class generates a method body which calls the lambda implementation
|
||||
* method, converting arguments, as needed.
|
||||
*/
|
||||
private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter {
|
||||
|
||||
ForwardingMethodGenerator(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
void generate(Method m) throws InternalError {
|
||||
visitCode();
|
||||
|
||||
if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
|
||||
visitTypeInsn(NEW, implMethodClassName);
|
||||
dup();
|
||||
}
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
visitVarInsn(ALOAD, 0);
|
||||
getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor());
|
||||
}
|
||||
|
||||
convertArgumentTypes(Type.getArgumentTypes(m));
|
||||
|
||||
// Invoke the method we want to forward to
|
||||
visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc);
|
||||
|
||||
// Convert the return value (if any) and return it
|
||||
// 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);
|
||||
|
||||
visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
|
||||
visitEnd();
|
||||
}
|
||||
|
||||
private void convertArgumentTypes(Type[] samArgumentTypes) {
|
||||
int lvIndex = 0;
|
||||
boolean samIncludesReceiver = implIsInstanceMethod && argTypes.length == 0;
|
||||
int samReceiverLength = samIncludesReceiver ? 1 : 0;
|
||||
if (samIncludesReceiver) {
|
||||
// push receiver
|
||||
Type rcvrType = samArgumentTypes[0];
|
||||
Type instantiatedRcvrType = instantiatedArgumentTypes[0];
|
||||
|
||||
load(lvIndex + 1, rcvrType);
|
||||
lvIndex += rcvrType.getSize();
|
||||
convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType);
|
||||
}
|
||||
int argOffset = implMethodArgumentTypes.length - samArgumentTypes.length;
|
||||
for (int i = samReceiverLength; i < samArgumentTypes.length; i++) {
|
||||
Type argType = samArgumentTypes[i];
|
||||
Type targetType = implMethodArgumentTypes[argOffset + i];
|
||||
Type instantiatedArgType = instantiatedArgumentTypes[i];
|
||||
|
||||
load(lvIndex + 1, argType);
|
||||
lvIndex += argType.getSize();
|
||||
convertType(argType, targetType, instantiatedArgType);
|
||||
}
|
||||
}
|
||||
|
||||
private void convertType(Type argType, Type targetType, Type functionalType) {
|
||||
convertType(argType.getDescriptor(), targetType.getDescriptor(), functionalType.getDescriptor());
|
||||
}
|
||||
|
||||
private int invocationOpcode() throws InternalError {
|
||||
switch (implKind) {
|
||||
case MethodHandleInfo.REF_invokeStatic:
|
||||
return INVOKESTATIC;
|
||||
case MethodHandleInfo.REF_newInvokeSpecial:
|
||||
return INVOKESPECIAL;
|
||||
case MethodHandleInfo.REF_invokeVirtual:
|
||||
return INVOKEVIRTUAL;
|
||||
case MethodHandleInfo.REF_invokeInterface:
|
||||
return INVOKEINTERFACE;
|
||||
case MethodHandleInfo.REF_invokeSpecial:
|
||||
return INVOKESPECIAL;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* LambdaConversionException
|
||||
*/
|
||||
public class LambdaConversionException extends Exception {
|
||||
public LambdaConversionException() {
|
||||
}
|
||||
|
||||
public LambdaConversionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public LambdaConversionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public LambdaConversionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public LambdaConversionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
178
jdk/src/share/classes/java/lang/invoke/LambdaMetafactory.java
Normal file
178
jdk/src/share/classes/java/lang/invoke/LambdaMetafactory.java
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Bootstrap methods for converting lambda expressions and method references to functional interface objects.</p>
|
||||
*
|
||||
* <p>For every lambda expressions or method reference in the source code, there is a target type which is a
|
||||
* functional interface. Evaluating a lambda expression produces an object of its target type. The mechanism for
|
||||
* evaluating lambda expressions is to invoke an invokedynamic call site, which takes arguments describing the sole
|
||||
* method of the functional interface and the implementation method, and returns an object (the lambda object) that
|
||||
* implements the target type. Methods of the lambda object invoke the implementation method. For method
|
||||
* references, the implementation method is simply the referenced method; for lambda expressions, the
|
||||
* implementation method is produced by the compiler based on the body of the lambda expression. The methods in
|
||||
* this file are the bootstrap methods for those invokedynamic call sites, called lambda factories, and the
|
||||
* bootstrap methods responsible for linking the lambda factories are called lambda meta-factories.
|
||||
*
|
||||
* <p>The bootstrap methods in this class take the information about the functional interface, the implementation
|
||||
* 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>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
|
||||
* methods because of the need for bridge methods. Invoking any of these methods on the lambda object will result
|
||||
* in invoking the implementation method.
|
||||
*
|
||||
* <p>The argument list of the implementation method and the argument list of the functional interface method(s)
|
||||
* may differ in several ways. The implementation methods may have additional arguments to accommodate arguments
|
||||
* captured by the lambda expression; there may also be differences resulting from permitted adaptations of
|
||||
* arguments, such as casting, boxing, unboxing, and primitive widening. They may also differ because of var-args,
|
||||
* but this is expected to be handled by the compiler.
|
||||
*
|
||||
* <p>Invokedynamic call sites have two argument lists: a static argument list and a dynamic argument list. The
|
||||
* static argument list lives in the constant pool; the dynamic argument list lives on the operand stack at
|
||||
* invocation time. The bootstrap method has access to the entire static argument list (which in this case,
|
||||
* contains method handles describing the implementation method and the canonical functional interface method),
|
||||
* as well as a method signature describing the number and static types (but not the values) of the dynamic
|
||||
* arguments, and the static return type of the invokedynamic site.
|
||||
*
|
||||
* <p>The implementation method is described with a method handle. In theory, any method handle could be used.
|
||||
* Currently supported are method handles representing invocation of virtual, interface, constructor and static
|
||||
* methods.
|
||||
*
|
||||
* <p>Assume:
|
||||
* <ul>
|
||||
* <li>the functional interface method has N arguments, of types (U1, U2, ... Un) and return type Ru</li>
|
||||
* <li>then the instantiated method type also has N arguments, of types (T1, T2, ... Tn) and return type Rt</li>
|
||||
* <li>the implementation method has M arguments, of types (A1..Am) and return type Ra,</li>
|
||||
* <li>the dynamic argument list has K arguments of types (D1..Dk), and the invokedynamic return site has
|
||||
* type Rd</li>
|
||||
* <li>the functional interface type is F</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The following signature invariants must hold:
|
||||
* <ul>
|
||||
* <li>Rd is a subtype of F</li>
|
||||
* <li>For i=1..N, Ti is a subtype of Ui</li>
|
||||
* <li>Either Rt and Ru are primitive and are the same type, or both are reference types and
|
||||
* Rt is a subtype of Ru</li>
|
||||
* <li>If the implementation method is a static method:
|
||||
* <ul>
|
||||
* <li>K + N = M</li>
|
||||
* <li>For i=1..K, Di = Ai</li>
|
||||
* <li>For i=1..N, Ti is adaptable to Aj, where j=i+k</li>
|
||||
* </ul></li>
|
||||
* <li>If the implementation method is an instance method:
|
||||
* <ul>
|
||||
* <li>K + N = M + 1</li>
|
||||
* <li>D1 must be a subtype of the enclosing class for the implementation method</li>
|
||||
* <li>For i=2..K, Di = Aj, where j=i-1</li>
|
||||
* <li>For i=1..N, Ti is adaptable to Aj, where j=i+k-1</li>
|
||||
* </ul></li>
|
||||
* <li>The return type Rt is void, or the return type Ra is not void and is adaptable to Rt</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that the potentially parameterized implementation return type provides the value for the SAM. Whereas
|
||||
* the completely known instantiated return type is adapted to the implementation arguments. Because the
|
||||
* instantiated type of the implementation method is not available, the adaptability of return types cannot be
|
||||
* checked as precisely at link-time as the arguments can be checked. Thus a loose version of link-time checking is
|
||||
* done on return type, while a strict version is applied to arguments.
|
||||
*
|
||||
* <p>A type Q is considered adaptable to S as follows:
|
||||
* <table>
|
||||
* <tr><th>Q</th><th>S</th><th>Link-time checks</th><th>Capture-time checks</th></tr>
|
||||
* <tr>
|
||||
* <td>Primitive</td><td>Primitive</td>
|
||||
* <td>Q can be converted to S via a primitive widening conversion</td>
|
||||
* <td>None</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Primitive</td><td>Reference</td>
|
||||
* <td>S is a supertype of the Wrapper(Q)</td>
|
||||
* <td>Cast from Wrapper(Q) to S</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Reference</td><td>Primitive</td>
|
||||
* <td>strict: Q is a primitive wrapper and Primitive(Q) can be widened to S
|
||||
* <br>loose: If Q is a primitive wrapper, check that Primitive(Q) can be widened to S</td>
|
||||
* <td>If Q is not a primitive wrapper, cast Q to the base Wrapper(S); for example Number for numeric types</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Reference</td><td>Reference</td>
|
||||
* <td>strict: S is a supertype of Q
|
||||
* <br>loose: none</td>
|
||||
* <td>Cast from Q to S</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class LambdaMetafactory {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 SAM method from the functional interface's perspective
|
||||
* @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 metaFactory(MethodHandles.Lookup caller,
|
||||
String invokedName,
|
||||
MethodType invokedType,
|
||||
MethodHandle samMethod,
|
||||
MethodHandle implMethod,
|
||||
MethodType instantiatedMethodType)
|
||||
throws ReflectiveOperationException, LambdaConversionException {
|
||||
AbstractValidatingLambdaMetafactory mf;
|
||||
mf = new InnerClassLambdaMetafactory(caller, invokedType, samMethod, implMethod, instantiatedMethodType);
|
||||
mf.validateMetafactoryArgs();
|
||||
return mf.buildCallSite();
|
||||
}
|
||||
}
|
39
jdk/src/share/classes/java/lang/invoke/MagicLambdaImpl.java
Normal file
39
jdk/src/share/classes/java/lang/invoke/MagicLambdaImpl.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/** <P> MagicLambdaImpl (named for similarity to MagicAccessorImpl and
|
||||
others, not because it actually implements an interface) is a
|
||||
marker class in the hierarchy. All subclasses of this class are
|
||||
"magically" granted access by the VM to otherwise inaccessible
|
||||
fields and methods of other classes. It is distinct from MagicAccessorImpl
|
||||
because, while we want to bypass accessibility checks, we do not want to
|
||||
bypass verification.</P>
|
||||
|
||||
<P> Do not change the name of this class without also changing the
|
||||
VM's code. </P> */
|
||||
|
||||
class MagicLambdaImpl {
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import sun.invoke.util.Wrapper;
|
||||
import static sun.invoke.util.Wrapper.*;
|
||||
|
||||
class TypeConvertingMethodAdapter extends MethodVisitor {
|
||||
|
||||
TypeConvertingMethodAdapter(MethodVisitor mv) {
|
||||
super(Opcodes.ASM4, mv);
|
||||
}
|
||||
|
||||
private static final int NUM_WRAPPERS = Wrapper.values().length;
|
||||
|
||||
private static final String NAME_OBJECT = "java/lang/Object";
|
||||
private static final String WRAPPER_PREFIX = "Ljava/lang/";
|
||||
|
||||
// Same for all primitives; name of the boxing method
|
||||
private static final String NAME_BOX_METHOD = "valueOf";
|
||||
|
||||
// Table of opcodes for widening primitive conversions; NOP = no conversion
|
||||
private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS];
|
||||
|
||||
private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16];
|
||||
|
||||
static {
|
||||
for (Wrapper w : Wrapper.values()) {
|
||||
if (w.basicTypeChar() != 'L') {
|
||||
int wi = hashWrapperName(w.wrapperSimpleName());
|
||||
assert (FROM_WRAPPER_NAME[wi] == null);
|
||||
FROM_WRAPPER_NAME[wi] = w;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_WRAPPERS; i++) {
|
||||
for (int j = 0; j < NUM_WRAPPERS; j++) {
|
||||
wideningOpcodes[i][j] = Opcodes.NOP;
|
||||
}
|
||||
}
|
||||
|
||||
initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(LONG, Opcodes.F2L, FLOAT);
|
||||
initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(FLOAT, Opcodes.L2F, LONG);
|
||||
initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(DOUBLE, Opcodes.F2D, FLOAT);
|
||||
initWidening(DOUBLE, Opcodes.L2D, LONG);
|
||||
}
|
||||
|
||||
private static void initWidening(Wrapper to, int opcode, Wrapper... from) {
|
||||
for (Wrapper f : from) {
|
||||
wideningOpcodes[f.ordinal()][to.ordinal()] = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class name to Wrapper hash, derived from Wrapper.hashWrap()
|
||||
* @param xn
|
||||
* @return The hash code 0-15
|
||||
*/
|
||||
private static int hashWrapperName(String xn) {
|
||||
if (xn.length() < 3) {
|
||||
return 0;
|
||||
}
|
||||
return (3 * xn.charAt(1) + xn.charAt(2)) % 16;
|
||||
}
|
||||
|
||||
private Wrapper wrapperOrNullFromDescriptor(String desc) {
|
||||
if (!desc.startsWith(WRAPPER_PREFIX)) {
|
||||
// Not a class type (array or method), so not a boxed type
|
||||
// or not in the right package
|
||||
return null;
|
||||
}
|
||||
// Pare it down to the simple class name
|
||||
String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1);
|
||||
// Hash to a Wrapper
|
||||
Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)];
|
||||
if (w == null || w.wrapperSimpleName().equals(cname)) {
|
||||
return w;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String wrapperName(Wrapper w) {
|
||||
return "java/lang/" + w.wrapperSimpleName();
|
||||
}
|
||||
|
||||
private static String unboxMethod(Wrapper w) {
|
||||
return w.primitiveSimpleName() + "Value";
|
||||
}
|
||||
|
||||
private static String boxingDescriptor(Wrapper w) {
|
||||
return String.format("(%s)L%s;", w.basicTypeChar(), wrapperName(w));
|
||||
}
|
||||
|
||||
private static String unboxingDescriptor(Wrapper w) {
|
||||
return "()" + w.basicTypeChar();
|
||||
}
|
||||
|
||||
void boxIfPrimitive(Wrapper w) {
|
||||
if (w.zero() != null) {
|
||||
box(w);
|
||||
}
|
||||
}
|
||||
|
||||
void widen(Wrapper ws, Wrapper wt) {
|
||||
if (ws != wt) {
|
||||
int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()];
|
||||
if (opcode != Opcodes.NOP) {
|
||||
visitInsn(opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void box(Wrapper w) {
|
||||
visitMethodInsn(Opcodes.INVOKESTATIC,
|
||||
wrapperName(w),
|
||||
NAME_BOX_METHOD,
|
||||
boxingDescriptor(w));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert types by unboxing. The source type is known to be a primitive wrapper.
|
||||
* @param ws A primitive wrapper corresponding to wrapped reference source type
|
||||
* @param wt A primitive wrapper being converted to
|
||||
*/
|
||||
void unbox(String sname, Wrapper wt) {
|
||||
visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||
sname,
|
||||
unboxMethod(wt),
|
||||
unboxingDescriptor(wt));
|
||||
}
|
||||
|
||||
private String descriptorToName(String desc) {
|
||||
int last = desc.length() - 1;
|
||||
if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') {
|
||||
// In descriptor form
|
||||
return desc.substring(1, last);
|
||||
} else {
|
||||
// Already in internal name form
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
void cast(String ds, String dt) {
|
||||
String ns = descriptorToName(ds);
|
||||
String nt = descriptorToName(dt);
|
||||
if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) {
|
||||
visitTypeInsn(Opcodes.CHECKCAST, nt);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPrimitive(Wrapper w) {
|
||||
return w != OBJECT;
|
||||
}
|
||||
|
||||
private Wrapper toWrapper(String desc) {
|
||||
char first = desc.charAt(0);
|
||||
if (first == '[' || first == '(') {
|
||||
first = 'L';
|
||||
}
|
||||
return Wrapper.forBasicType(first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an argument of type 'argType' to be passed to 'targetType' assuring that it is 'functionalType'.
|
||||
* Insert the needed conversion instructions in the method code.
|
||||
* @param argType
|
||||
* @param targetType
|
||||
* @param functionalType
|
||||
*/
|
||||
void convertType(String dArg, String dTarget, String dFunctional) {
|
||||
if (dArg.equals(dTarget)) {
|
||||
return;
|
||||
}
|
||||
Wrapper wArg = toWrapper(dArg);
|
||||
Wrapper wTarget = toWrapper(dTarget);
|
||||
if (wArg == VOID || wTarget == VOID) {
|
||||
return;
|
||||
}
|
||||
if (isPrimitive(wArg)) {
|
||||
if (isPrimitive(wTarget)) {
|
||||
// Both primitives: widening
|
||||
widen(wArg, wTarget);
|
||||
} else {
|
||||
// Primitive argument to reference target
|
||||
Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget);
|
||||
if (wPrimTarget != null) {
|
||||
// The target is a boxed primitive type, widen to get there before boxing
|
||||
widen(wArg, wPrimTarget);
|
||||
box(wPrimTarget);
|
||||
} else {
|
||||
// Otherwise, box and cast
|
||||
box(wArg);
|
||||
cast(wrapperName(wArg), dTarget);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String dSrc;
|
||||
Wrapper wFunctional = toWrapper(dFunctional);
|
||||
if (isPrimitive(wFunctional)) {
|
||||
dSrc = dArg;
|
||||
} else {
|
||||
// Cast to convert to possibly more specific type, and generate CCE for invalid arg
|
||||
dSrc = dFunctional;
|
||||
cast(dArg, dFunctional);
|
||||
}
|
||||
if (isPrimitive(wTarget)) {
|
||||
// Reference argument to primitive target
|
||||
Wrapper wps = wrapperOrNullFromDescriptor(dSrc);
|
||||
if (wps != null) {
|
||||
if (wps.isSigned() || wps.isFloating()) {
|
||||
// Boxed number to primitive
|
||||
unbox(wrapperName(wps), wTarget);
|
||||
} else {
|
||||
// Character or Boolean
|
||||
unbox(wrapperName(wps), wps);
|
||||
widen(wps, wTarget);
|
||||
}
|
||||
} else {
|
||||
// Source type is reference type, but not boxed type,
|
||||
// assume it is super type of target type
|
||||
String intermediate;
|
||||
if (wTarget.isSigned() || wTarget.isFloating()) {
|
||||
// Boxed number to primitive
|
||||
intermediate = "java/lang/Number";
|
||||
} else {
|
||||
// Character or Boolean
|
||||
intermediate = wrapperName(wTarget);
|
||||
}
|
||||
cast(dSrc, intermediate);
|
||||
unbox(intermediate, wTarget);
|
||||
}
|
||||
} else {
|
||||
// Both reference types: just case to target type
|
||||
cast(dSrc, dTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user