/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.api.Augmentation;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
/**
* Reference to a function or lambda.
* <p>
* Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap}
* either statically from bytecode with invokedynamic, or at runtime from Java.
*/
public class FunctionRef {
/** functional interface method name */
public final String interfaceMethodName;
/** factory (CallSite) method signature */
public final MethodType factoryMethodType;
/** functional interface method signature */
public final MethodType interfaceMethodType;
/** class of the delegate method to be called */
public final String delegateClassName;
/** the invocation type of the delegate method */
public final int delegateInvokeType;
/** the name of the delegate method */
public final String delegateMethodName;
/** delegate method signature */
public final MethodType delegateMethodType;
/** interface method */
public final Method interfaceMethod;
/** delegate method */
public final Method delegateMethod;
/** factory method type descriptor */
public final String factoryDescriptor;
/** functional interface method as type */
public final org.objectweb.asm.Type interfaceType;
/** delegate method type method as type */
public final org.objectweb.asm.Type delegateType;
/**
* Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist.
* @param definition the whitelist against which this script is being compiled
* @param expected functional interface type to implement.
* @param type the left hand side of a method reference expression
* @param call the right hand side of a method reference expression
* @param numCaptures number of captured arguments
*/
public FunctionRef(Definition definition, Type expected, String type, String call, int numCaptures) {
this(expected, expected.struct.getFunctionalMethod(), lookup(definition, expected, type, call, numCaptures > 0), numCaptures);
}
/**
* Creates a new FunctionRef (already resolved)
* @param expected functional interface type to implement
* @param interfaceMethod functional interface method
* @param delegateMethod implementation method
* @param numCaptures number of captured arguments
*/
public FunctionRef(Type expected, Method interfaceMethod, Method delegateMethod, int numCaptures) {
MethodType delegateMethodType = delegateMethod.getMethodType();
interfaceMethodName = interfaceMethod.name;
factoryMethodType = MethodType.methodType(expected.clazz,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
interfaceMethodType = interfaceMethod.getMethodType().dropParameterTypes(0, 1);
// the Painless$Script class can be inferred if owner is null
if (delegateMethod.owner == null) {
delegateClassName = CLASS_NAME;
} else if (delegateMethod.augmentation) {
delegateClassName = Augmentation.class.getName();
} else {
delegateClassName = delegateMethod.owner.clazz.getName();
}
if ("<init>".equals(delegateMethod.name)) {
delegateInvokeType = H_NEWINVOKESPECIAL;
} else if (Modifier.isStatic(delegateMethod.modifiers)) {
delegateInvokeType = H_INVOKESTATIC;
} else if (delegateMethod.owner.clazz.isInterface()) {
delegateInvokeType = H_INVOKEINTERFACE;
} else {
delegateInvokeType = H_INVOKEVIRTUAL;
}
delegateMethodName = delegateMethod.name;
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.interfaceMethod = interfaceMethod;
this.delegateMethod = delegateMethod;
factoryDescriptor = factoryMethodType.toMethodDescriptorString();
interfaceType = org.objectweb.asm.Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
delegateType = org.objectweb.asm.Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
}
/**
* Creates a new FunctionRef (low level).
* It is for runtime use only.
*/
public FunctionRef(Type expected, Method interfaceMethod, String delegateMethodName, MethodType delegateMethodType, int numCaptures) {
interfaceMethodName = interfaceMethod.name;
factoryMethodType = MethodType.methodType(expected.clazz,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
interfaceMethodType = interfaceMethod.getMethodType().dropParameterTypes(0, 1);
delegateClassName = CLASS_NAME;
delegateInvokeType = H_INVOKESTATIC;
this.delegateMethodName = delegateMethodName;
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.interfaceMethod = null;
delegateMethod = null;
factoryDescriptor = null;
interfaceType = null;
delegateType = null;
}
/**
* Looks up {@code type::call} from the whitelist, and returns a matching method.
*/
private static Definition.Method lookup(Definition definition, Definition.Type expected,
String type, String call, boolean receiverCaptured) {
// check its really a functional interface
// for e.g. Comparable
Method method = expected.struct.getFunctionalMethod();
if (method == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + expected.name + "], not a functional interface");
}
// lookup requested method
Definition.Struct struct = definition.getType(type).struct;
final Definition.Method impl;
// ctor ref
if ("new".equals(call)) {
impl = struct.constructors.get(new Definition.MethodKey("<init>", method.arguments.size()));
} else {
// look for a static impl first
Definition.Method staticImpl = struct.staticMethods.get(new Definition.MethodKey(call, method.arguments.size()));
if (staticImpl == null) {
// otherwise a virtual impl
final int arity;
if (receiverCaptured) {
// receiver captured
arity = method.arguments.size();
} else {
// receiver passed
arity = method.arguments.size() - 1;
}
impl = struct.methods.get(new Definition.MethodKey(call, arity));
} else {
impl = staticImpl;
}
}
if (impl == null) {
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " +
"[" + expected + "]");
}
return impl;
}
}