/*
* Copyright 2016 ninjaframework.
*
* Licensed 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 ninja.utils;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Lambdas {
private static final Logger log = LoggerFactory.getLogger(Lambdas.class);
/**
* The kind of lambda. Lambdas in Java can either be references to methods
* or anonymously defined.
* https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
*/
public static enum Kind {
// Reference to a static method (e.g. ContainingClass::staticMethodName)
STATIC_METHOD_REFERENCE,
// Reference to an instance method of a specific object (e.g. specificObject::instanceMethodName)
SPECIFIC_INSTANCE_METHOD_REFERENCE,
// Reference to an instance method of an arbitrary object of a particular type (e.g. ContainingClass::instanceMethodName)
ANY_INSTANCE_METHOD_REFERENCE,
// Anonymously defined (e.g. () -> return "a"; )
ANONYMOUS_METHOD_REFERENCE
}
public static class LambdaInfo {
private final Object lambda;
private final Kind kind;
private final SerializedLambda serializedLambda;
private final Method functionalMethod;
private final Method implementationMethod;
public LambdaInfo(Object lambda, Kind kind, SerializedLambda serializedLambda, Method functionalMethod, Method implementationMethod) {
this.lambda = lambda;
this.kind = kind;
this.serializedLambda = serializedLambda;
this.functionalMethod = functionalMethod;
this.implementationMethod = implementationMethod;
}
public Object getLambda() {
return lambda;
}
public Kind getKind() {
return kind;
}
public SerializedLambda getSerializedLambda() {
return serializedLambda;
}
public Method getFunctionalMethod() {
return functionalMethod;
}
public Method getImplementationMethod() {
return implementationMethod;
}
public boolean areMethodParameterCountsEqual() {
// in case of captured arguments sometimes these do not match
return this.functionalMethod.getParameterCount() ==
this.implementationMethod.getParameterCount();
}
@Override
public String toString() {
return new StringBuilder()
.append("kind=").append(kind)
.append(" func=").append(functionalMethod)
.append(" impl=").append(implementationMethod)
.append(" serialized=").append(serializedLambda)
.toString();
}
}
public static LambdaInfo reflect(Object lambda) {
Objects.requireNonNull(lambda);
// note: this throws a runtime exception if the lambda isn't serializable
// or if the object isn't actually a lambda (e.g. an anon class)
SerializedLambda serializedLambda = getSerializedLambda(lambda);
Method functionalMethod;
try {
functionalMethod = getMethod(lambda.getClass(),
serializedLambda.getFunctionalInterfaceMethodName());
// important: only way classes other than the creator can invoke it
functionalMethod.setAccessible(true);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new RuntimeException("Unable to getFunctionalMethod", e);
}
Method implementationMethod;
try {
implementationMethod = getImplementationMethod(serializedLambda);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new RuntimeException("Unable to getImplementationMethod", e);
}
Kind kind;
// https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandleInfo.html
int implMethodKind = serializedLambda.getImplMethodKind();
if (implMethodKind == 6) { // REF_invokeStatic
if (serializedLambda.getImplMethodName().startsWith("lambda$")) {
kind = Kind.ANONYMOUS_METHOD_REFERENCE;
} else {
kind = Kind.STATIC_METHOD_REFERENCE;
}
} else {
if (serializedLambda.getCapturedArgCount() > 0) {
kind = Kind.SPECIFIC_INSTANCE_METHOD_REFERENCE;
} else {
kind = Kind.ANY_INSTANCE_METHOD_REFERENCE;
}
}
return new LambdaInfo(lambda, kind, serializedLambda, functionalMethod, implementationMethod);
}
/**
* Tries to get a SerializedLambda from an Object by searching the class
* hierarchy for a <code>writeReplace</code> method. The lambda must
* be serializable in order for this method to return a value.
* @param lambda An object that is an instance of a functional interface.
* @return The SerializedLambda
*/
public static SerializedLambda getSerializedLambda(Object lambda) {
Objects.requireNonNull(lambda);
if (!(lambda instanceof java.io.Serializable)) {
throw new IllegalArgumentException("Functional object does not implement java.io.Serializable");
}
for (Class<?> clazz = lambda.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method replaceMethod = clazz.getDeclaredMethod("writeReplace");
replaceMethod.setAccessible(true);
Object serializedForm = replaceMethod.invoke(lambda);
if (serializedForm instanceof SerializedLambda) {
return (SerializedLambda) serializedForm;
}
} catch (NoSuchMethodError e) {
// fall through the loop and try the next class
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Functional object is not a lambda");
} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException("Unable to cleanly serialize lambda", e);
}
}
throw new RuntimeException("writeReplace method not found");
}
public static Method getFunctionalMethod(SerializedLambda serializedLambda) throws NoSuchMethodException, ClassNotFoundException {
return getMethod(serializedLambda.getFunctionalInterfaceClass(),
serializedLambda.getFunctionalInterfaceMethodName());
}
public static Method getImplementationMethod(SerializedLambda serializedLambda) throws NoSuchMethodException, ClassNotFoundException {
return getMethod(serializedLambda.getImplClass(),
serializedLambda.getImplMethodName());
}
public static Method getMethod(String className, String methodName) throws NoSuchMethodException, ClassNotFoundException {
Class<?> clazz = Class.forName(className.replace('/', '.'));
return getMethod(clazz, methodName);
}
public static Method getMethod(Class<?> clazz, String methodName) throws NoSuchMethodException, ClassNotFoundException {
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method;
}
}
// no match then try interfaces in order
for (Class<?> interfaceClass : clazz.getInterfaces()) {
for (Method method : interfaceClass.getDeclaredMethods()) {
return method;
}
}
clazz = clazz.getSuperclass();
}
throw new NoSuchMethodException("Method " + methodName + " not found in " + clazz);
}
}