You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
17 KiB
376 lines
17 KiB
/*
|
|
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package java.lang.invoke;
|
|
|
|
import sun.invoke.util.Wrapper;
|
|
|
|
import static sun.invoke.util.Wrapper.forPrimitiveType;
|
|
import static sun.invoke.util.Wrapper.forWrapperType;
|
|
import static sun.invoke.util.Wrapper.isWrapperType;
|
|
|
|
/**
|
|
* Abstract implementation of a lambda metafactory which provides parameter
|
|
* unrolling and input validation.
|
|
*
|
|
* @see LambdaMetafactory
|
|
*/
|
|
/* package */ 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 String samMethodName; // Name of the SAM method "foo"
|
|
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
|
|
final MethodType[] additionalBridges; // Signatures of additional methods to bridge
|
|
|
|
|
|
/**
|
|
* 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 samMethodName Name of the method in the functional interface to
|
|
* which the lambda or method reference is being
|
|
* converted, represented as a String.
|
|
* @param samMethodType Type of the method in the functional interface to
|
|
* which the lambda or method reference is being
|
|
* converted, represented as a MethodType.
|
|
* @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 isSerializable Should the lambda be made serializable? If set,
|
|
* either the target type or one of the additional SAM
|
|
* types must extend {@code Serializable}.
|
|
* @param markerInterfaces Additional interfaces which the lambda object
|
|
* should implement.
|
|
* @param additionalBridges Method types for additional signatures to be
|
|
* bridged to the implementation method
|
|
* @throws LambdaConversionException If any of the meta-factory protocol
|
|
* invariants are violated
|
|
*/
|
|
AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller,
|
|
MethodType invokedType,
|
|
String samMethodName,
|
|
MethodType samMethodType,
|
|
MethodHandle implMethod,
|
|
MethodType instantiatedMethodType,
|
|
boolean isSerializable,
|
|
Class<?>[] markerInterfaces,
|
|
MethodType[] additionalBridges)
|
|
throws LambdaConversionException {
|
|
if ((caller.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
|
|
throw new LambdaConversionException(String.format(
|
|
"Invalid caller: %s",
|
|
caller.lookupClass().getName()));
|
|
}
|
|
this.targetClass = caller.lookupClass();
|
|
this.invokedType = invokedType;
|
|
|
|
this.samBase = invokedType.returnType();
|
|
|
|
this.samMethodName = samMethodName;
|
|
this.samMethodType = samMethodType;
|
|
|
|
this.implMethod = implMethod;
|
|
this.implInfo = caller.revealDirect(implMethod);
|
|
this.implKind = implInfo.getReferenceKind();
|
|
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;
|
|
this.isSerializable = isSerializable;
|
|
this.markerInterfaces = markerInterfaces;
|
|
this.additionalBridges = additionalBridges;
|
|
|
|
if (!samBase.isInterface()) {
|
|
throw new LambdaConversionException(String.format(
|
|
"Functional interface %s is not an interface",
|
|
samBase.getName()));
|
|
}
|
|
|
|
for (Class<?> c : markerInterfaces) {
|
|
if (!c.isInterface()) {
|
|
throw new LambdaConversionException(String.format(
|
|
"Marker interface %s is not an interface",
|
|
c.getName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the CallSite.
|
|
*
|
|
* @return a CallSite, which, when invoked, will return an instance of the
|
|
* functional interface
|
|
* @throws ReflectiveOperationException
|
|
*/
|
|
abstract CallSite buildCallSite()
|
|
throws LambdaConversionException;
|
|
|
|
/**
|
|
* Check the meta-factory arguments for errors
|
|
* @throws LambdaConversionException if there are improper conversions
|
|
*/
|
|
void validateMetafactoryArgs() throws LambdaConversionException {
|
|
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 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 instantiated parameters, %d functional interface method parameters",
|
|
implIsInstanceMethod ? "instance" : "static", implInfo,
|
|
instantiatedArity, samArity));
|
|
}
|
|
for (MethodType bridgeMT : additionalBridges) {
|
|
if (bridgeMT.parameterCount() != samArity) {
|
|
throw new LambdaConversionException(
|
|
String.format("Incorrect number of parameters for bridge signature %s; incompatible with %s",
|
|
bridgeMT, samMethodType));
|
|
}
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
|
|
Class<?> implReceiverClass = implMethod.type().parameterType(0);
|
|
if (implReceiverClass != implDefiningClass && !implReceiverClass.isAssignableFrom(receiverClass)) {
|
|
throw new LambdaConversionException(
|
|
String.format("Invalid receiver type %s; not a subtype of implementation receiver type %s",
|
|
receiverClass, implReceiverClass));
|
|
}
|
|
} 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();
|
|
Class<?> samReturnType = samMethodType.returnType();
|
|
if (!isAdaptableToAsReturn(actualReturnType, expectedType)) {
|
|
throw new LambdaConversionException(
|
|
String.format("Type mismatch for lambda return: %s is not convertible to %s",
|
|
actualReturnType, expectedType));
|
|
}
|
|
if (!isAdaptableToAsReturnStrict(expectedType, samReturnType)) {
|
|
throw new LambdaConversionException(
|
|
String.format("Type mismatch for lambda expected return: %s is not convertible to %s",
|
|
expectedType, samReturnType));
|
|
}
|
|
for (MethodType bridgeMT : additionalBridges) {
|
|
if (!isAdaptableToAsReturnStrict(expectedType, bridgeMT.returnType())) {
|
|
throw new LambdaConversionException(
|
|
String.format("Type mismatch for lambda expected return: %s is not convertible to %s",
|
|
expectedType, bridgeMT.returnType()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check type adaptability for parameter types.
|
|
* @param fromType Type to convert from
|
|
* @param toType Type to convert to
|
|
* @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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check type adaptability for return types --
|
|
* special handling of void type) and parameterized fromType
|
|
* @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);
|
|
}
|
|
private boolean isAdaptableToAsReturnStrict(Class<?> fromType, Class<?> toType) {
|
|
if (fromType.equals(void.class)) return toType.equals(void.class);
|
|
return isAdaptableTo(fromType, toType, true);
|
|
}
|
|
|
|
|
|
/*********** 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
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
***********************/
|
|
|
|
}
|