/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.flink.api.java.typeutils; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.flink.annotation.Internal; import org.apache.flink.api.common.functions.Function; import static org.objectweb.asm.Type.getConstructorDescriptor; import static org.objectweb.asm.Type.getMethodDescriptor; @Internal public class TypeExtractionUtils { private TypeExtractionUtils() { // do not allow instantiation } /** * Similar to a Java 8 Executable but with a return type. */ public static class LambdaExecutable { private Type[] parameterTypes; private Type returnType; private String name; private Object executable; public LambdaExecutable(Constructor<?> constructor) { this.parameterTypes = constructor.getGenericParameterTypes(); this.returnType = constructor.getDeclaringClass(); this.name = constructor.getName(); this.executable = constructor; } public LambdaExecutable(Method method) { this.parameterTypes = method.getGenericParameterTypes(); this.returnType = method.getGenericReturnType(); this.name = method.getName(); this.executable = method; } public Type[] getParameterTypes() { return parameterTypes; } public Type getReturnType() { return returnType; } public String getName() { return name; } public boolean executablesEquals(Method m) { return executable.equals(m); } public boolean executablesEquals(Constructor<?> c) { return executable.equals(c); } } /** * Checks if the given function has been implemented using a Java 8 lambda. If yes, a LambdaExecutable * is returned describing the method/constructor. Otherwise null. * * @throws TypeExtractionException lambda extraction is pretty hacky, it might fail for unknown JVM issues. */ public static LambdaExecutable checkAndExtractLambda(Function function) throws TypeExtractionException { try { // get serialized lambda Object serializedLambda = null; for (Class<?> clazz = function.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method replaceMethod = clazz.getDeclaredMethod("writeReplace"); replaceMethod.setAccessible(true); Object serialVersion = replaceMethod.invoke(function); // check if class is a lambda function if (serialVersion.getClass().getName().equals("java.lang.invoke.SerializedLambda")) { // check if SerializedLambda class is present try { Class.forName("java.lang.invoke.SerializedLambda"); } catch (Exception e) { throw new TypeExtractionException("User code tries to use lambdas, but framework is running with a Java version < 8"); } serializedLambda = serialVersion; break; } } catch (NoSuchMethodException e) { // thrown if the method is not there. fall through the loop } } // not a lambda method -> return null if (serializedLambda == null) { return null; } // find lambda method Method implClassMethod = serializedLambda.getClass().getDeclaredMethod("getImplClass"); Method implMethodNameMethod = serializedLambda.getClass().getDeclaredMethod("getImplMethodName"); Method implMethodSig = serializedLambda.getClass().getDeclaredMethod("getImplMethodSignature"); String className = (String) implClassMethod.invoke(serializedLambda); String methodName = (String) implMethodNameMethod.invoke(serializedLambda); String methodSig = (String) implMethodSig.invoke(serializedLambda); Class<?> implClass = Class.forName(className.replace('/', '.'), true, Thread.currentThread().getContextClassLoader()); // find constructor if (methodName.equals("<init>")) { Constructor<?>[] constructors = implClass.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { if(getConstructorDescriptor(constructor).equals(methodSig)) { return new LambdaExecutable(constructor); } } } // find method else { List<Method> methods = getAllDeclaredMethods(implClass); for (Method method : methods) { if(method.getName().equals(methodName) && getMethodDescriptor(method).equals(methodSig)) { return new LambdaExecutable(method); } } } throw new TypeExtractionException("No lambda method found."); } catch (Exception e) { throw new TypeExtractionException("Could not extract lambda method out of function: " + e.getClass().getSimpleName() + " - " + e.getMessage(), e); } } /** * Returns all declared methods of a class including methods of superclasses. */ public static List<Method> getAllDeclaredMethods(Class<?> clazz) { List<Method> result = new ArrayList<>(); while (clazz != null) { Method[] methods = clazz.getDeclaredMethods(); Collections.addAll(result, methods); clazz = clazz.getSuperclass(); } return result; } /** * Convert ParameterizedType or Class to a Class. */ public static Class<?> typeToClass(Type t) { if (t instanceof Class) { return (Class<?>)t; } else if (t instanceof ParameterizedType) { return ((Class<?>)((ParameterizedType) t).getRawType()); } throw new IllegalArgumentException("Cannot convert type to class"); } /** * Checks if a type can be converted to a Class. This is true for ParameterizedType and Class. */ public static boolean isClassType(Type t) { return t instanceof Class<?> || t instanceof ParameterizedType; } /** * Checks whether two types are type variables describing the same. */ public static boolean sameTypeVars(Type t1, Type t2) { return t1 instanceof TypeVariable && t2 instanceof TypeVariable && ((TypeVariable<?>) t1).getName().equals(((TypeVariable<?>) t2).getName()) && ((TypeVariable<?>) t1).getGenericDeclaration().equals(((TypeVariable<?>) t2).getGenericDeclaration()); } }