/* * 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; } }