![JAR search and dependency download from the Maven repository](/logo.png)
xdean.jex.util.reflect.FunctionInterfaceUtil Maven / Gradle / Ivy
The newest version!
package xdean.jex.util.reflect;
import static xdean.jex.util.lang.PrimitiveTypeUtil.toWrapper;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import xdean.jex.log.Log;
import xdean.jex.log.LogFactory;
public class FunctionInterfaceUtil {
private static final Log LOG = LogFactory.from(FunctionInterfaceUtil.class);
/**
* Get the method from a function interface
*
* @param clz
* @return null if the given class is not a function interface
*/
public static Method getFunctionInterfaceMethod(Class> clz) {
if (!clz.isInterface()) {
return null;
}
Method[] ms = Stream.of(clz.getMethods())
.filter(m -> !(m.isDefault() || m.isBridge() || m.isSynthetic() || Modifier.isStatic(m.getModifiers())))
.toArray(Method[]::new);
if (ms.length != 1) {
return null;
}
return ms[0];
}
/**
* Adapt a method to a function interface.
* For example:
*
*
*
* static int increment(int i){
* return i+1;
* }
* Method m = ...
* UnaryOperator<Integer> uo = methodToFunctionInterface(m, null, UnaryOperator.class);//work
* UnaryOperator<Integer> uo = methodToFunctionInterface(m, null, UnaryOperator.class, Integer.class);//work and more safe
* UnaryOperator<Integer> uo = methodToFunctionInterface(m, null, UnaryOperator.class, String.class);//return null
*
*
*
* Note that only indeed adapted method can be converted.
* For example: {@code Integer function(Number)} can't adapt to {@code UnaryOperator}, though any call to a
* {@code UnaryOperator} can delegate by the function. Because you can't define like:
*
*
*
* interface Function extends UnaryOperator<Integer> {
* @Override
* public Integer apply(Number t);//error
* }
*
*
*
* Now only ignored things is ParameterizedType. That means this method consider return type and parameter types as
* raw type.
*
* @param method The method to adapt. Ensure the method can be access.
* @param target The method's target. If the method is static, target should be null.
* @param functionInterfaceClass The function interface to adapt to.
* @param explicitGenericTypes If the function interface has generic type, you can specify them in order. If a
* explicit type is null, it will be ignored.
* @return Instance of the function interface. Or null if can't adapt to. Note that returned object is raw type. If
* you don't specify explicit generic types, IllegalArgumentException(type mismatch) may happen when you call
* it.
*/
@SuppressWarnings("unchecked")
public static T methodToFunctionInterface(Method method, Object target, Class functionInterfaceClass,
Class>... explicitGenericTypes) {
Method functionMethod = getFunctionInterfaceMethod(functionInterfaceClass);
if (functionMethod == null) {
return null;
}
if (functionMethod.getParameterCount() != method.getParameterCount()) {
return null;
}
// Map the explicit type
Map, Class>> explicitTypeMap = new HashMap<>();
TypeVariable>[] typeParameters = functionInterfaceClass.getTypeParameters();
if (explicitGenericTypes.length > typeParameters.length) {
throw new IllegalArgumentException("The explicit generic types are too many. Expect " + typeParameters.length);
}
for (int i = 0; i < explicitGenericTypes.length; i++) {
explicitTypeMap.put(typeParameters[i], toWrapper(explicitGenericTypes[i]));
}
// Map the generic reference
Map, Type> typeVariableReference = GenericUtil.getGenericReferenceMap(functionInterfaceClass);
Function, Class>> getActualTypeVariable = tv -> {
Type next;
while (true) {
next = typeVariableReference.get(tv);
if (next == null) {
return explicitTypeMap.get(tv);
}
if (next instanceof Class>) {
return (Class>) next;
}
tv = (TypeVariable>) next;
}
};
// Resolve return type
Class> returnType = toWrapper(method.getReturnType());
Type functionGenericReturnType = functionMethod.getGenericReturnType();
if (functionGenericReturnType instanceof ParameterizedType) {
// TODO handle and match ParameterizedType
functionGenericReturnType = ((ParameterizedType) functionGenericReturnType).getRawType();
}
if (returnType == void.class && functionGenericReturnType == void.class) {
} else if (functionGenericReturnType instanceof Class) {
if (!toWrapper((Class>) functionGenericReturnType).isAssignableFrom(returnType)) {
return null;
}
} else if (functionGenericReturnType instanceof TypeVariable) {
TypeVariable> tv = (TypeVariable>) functionGenericReturnType;
Class> explicitType = getActualTypeVariable.apply(tv);
if (explicitType != null) {
if (!explicitType.equals(returnType)) {
return null;
}
} else if (FunctionInterfaceUtil.matchTypeBounds(returnType, tv)) {
explicitTypeMap.put(tv, returnType);
} else {
return null;
}
} else {
LOG.warn().log("Can't handle GenericReturnType: {} with type {}", functionGenericReturnType,
functionGenericReturnType.getClass());
return null;
}
// Resolve parameters
Type[] functionParams = functionMethod.getGenericParameterTypes();
Class>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
Type functionParamType = functionParams[i];
Class> paramType = toWrapper(params[i]);
if (functionParamType instanceof ParameterizedType) {
// TODO handle and match ParameterizedType
functionParamType = ((ParameterizedType) functionParamType).getRawType();
}
if (functionParamType instanceof Class) {
if (!paramType.isAssignableFrom(
toWrapper((Class>) functionParamType))) {
return null;
}
} else if (functionParamType instanceof TypeVariable) {
TypeVariable> tv = (TypeVariable>) functionParamType;
Class> explicitType = getActualTypeVariable.apply(tv);
if (explicitType != null) {
if (!explicitType.equals(paramType)) {
return null;
}
} else if (FunctionInterfaceUtil.matchTypeBounds(paramType, tv)) {
explicitTypeMap.put(tv, paramType);
} else {
return null;
}
} else {
LOG.warn().log("Can't handle GenericParameterType: {} with type {}", paramType, paramType.getClass());
return null;
}
}
// Resolve throws
List functionExceptionTypes = Arrays.asList(functionMethod.getGenericExceptionTypes());
for (Class> exceptionType : method.getExceptionTypes()) {
if (Exception.class.isAssignableFrom(exceptionType)
&& !RuntimeException.class.isAssignableFrom(exceptionType)
&& !functionExceptionTypes.stream().anyMatch(
functionThrowType -> {
Class> functionThrowClass = null;
if (functionThrowType instanceof Class) {
functionThrowClass = (Class>) functionThrowType;
} else if (functionThrowType instanceof TypeVariable) {
Class> explicitType = explicitTypeMap.get(functionThrowType);
if (explicitType == null) {
return FunctionInterfaceUtil.matchTypeBounds(exceptionType, (TypeVariable>) functionThrowType);
} else {
functionThrowClass = explicitType;
}
} else {
LOG.warn().log("Can't handle GenericException: {} with type {}", functionThrowType,
functionThrowType.getClass());
return false;
}
return functionThrowClass.isAssignableFrom(exceptionType);
})) {
return null;
}
}
return (T) Proxy.newProxyInstance(
functionInterfaceClass.getClassLoader(),
new Class[] { functionInterfaceClass },
(obj, m, args) -> {
if (m.equals(functionMethod)) {
return method.invoke(target, args);
}
Class> declaringClass = m.getDeclaringClass();
if (m.isDefault() || declaringClass.equals(Object.class)) {
Object result;
Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(
Class.class, int.class);
constructor.setAccessible(true);
result = constructor
.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(m, declaringClass)
.bindTo(obj)
.invokeWithArguments(args);
return (result);
}
return m.invoke(obj, args);
});
}
private static boolean matchTypeBounds(Class> clz, TypeVariable> tv) {
Class> wrapClz = toWrapper(clz);
return FunctionInterfaceUtil.getAllBounds(tv).allMatch(
c -> toWrapper(c).isAssignableFrom(wrapClz));
}
private static Stream> getAllBounds(TypeVariable> tv) {
return Stream.of(tv.getBounds())
.flatMap(t -> {
if (t instanceof Class) {
return Stream.of((Class>) t);
} else if (t instanceof TypeVariable) {
return getAllBounds(((TypeVariable>) t));
} else {
LOG.warn().log("Can't handle TypeVariable Bound: {} with type {}", t, t.getClass());
return Stream.empty();
}
});
}
// private static Map, Type> getTypeVariableReference(Class> clz) {
// HashMap, Type> map = new HashMap<>();
// if (clz.getSuperclass() != null) {
// map.putAll(getTypeVariableReference(clz.getSuperclass()));
// }
// Arrays.asList(clz.getInterfaces()).forEach(c -> map.putAll(getTypeVariableReference(c)));
// Stream.concat(Stream.of(clz.getGenericSuperclass()), Stream.of(clz.getGenericInterfaces()))
// .filter(not(null))
// .forEach(c -> {
// if (c instanceof Class) {
// } else if (c instanceof ParameterizedType) {
// Type[] actualTypeArguments = ((ParameterizedType) c).getActualTypeArguments();
// TypeVariable>[] implTypeParams = ((Class>) ((ParameterizedType) c).getRawType())
// .getTypeParameters();
// for (int i = 0; i < actualTypeArguments.length; i++) {
// map.put(implTypeParams[i], actualTypeArguments[i]);
// }
// } else {
// log.warn("Unknown Generic Type: {} with type {}", c, c.getClass());
// }
// });
// return map;
// }
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy