/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */package nova.core.util; import com.google.common.collect.ObjectArrays; import com.google.common.primitives.Primitives; import nova.core.util.exception.NovaException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; public class ReflectionUtil { /** * Array that contains the primitive wrappers in the order they can be * converted without a type-cast. */ private static final List<Class<?>> PRIMITIVE_WIDENING = Arrays.asList(Byte.class, Short.class, Character.class, Integer.class, Long.class, Float.class, Double.class); private ReflectionUtil() { } private static Class<?>[] types(Object... args) { return Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); } static float calculateDistancePrimitive(Class<?> a, Class<?> b) { // Nothing can be converted to a char or a boolean. if (b == Character.class || b == Boolean.class) return -1F; // char acts like a short. if (a == Character.class) a = Short.class; int indexA = PRIMITIVE_WIDENING.indexOf(a); int indexB = PRIMITIVE_WIDENING.indexOf(b); if (indexA < 0 || indexB < 0) return -1F; return (indexB - indexA) / 10F; } static float calculateDistance(Class<?> a, Class<?> b) { float cost = 0F; if (b.isPrimitive()) { b = Primitives.wrap(b); if (!a.isPrimitive()) { cost += 0.1F; // Penalty for unwrapping } a = Primitives.wrap(a); cost += calculateDistancePrimitive(a, b); return cost; } else { while (a != null && !a.equals(b)) { if (a.isInterface() && a.isAssignableFrom(b)) { // Interface match cost += 0.25F; break; } cost++; a = a.getSuperclass(); } if (a == null) { // Object match cost += 1.5F; } } return cost; } static float calculateDistance(Executable exec, Class<?>[] parameterTypes) { float cost = 0; Class<?>[] execTypes = exec.getParameterTypes(); for (int i = 0; i < exec.getParameterCount(); i++) { if (i >= parameterTypes.length && exec.isVarArgs()) break; Class<?> a = parameterTypes[i]; Class<?> b = execTypes[i]; if (i == exec.getParameterCount() - 1 && exec.isVarArgs()) { if (isAssignmentCompatible(a, b)) { // Passed array for var-args. cost += calculateDistance(a, b); } else { cost += calculateDistance(a, b.getComponentType()); // Penalty for every parameter that wasn't used. cost += (parameterTypes.length - exec.getParameterCount()) * 3F; // Death penalty for using var-args. cost += 10F; } } else { cost += calculateDistance(a, b); } } return cost; } static int compareDistance(Executable a, Executable b, Class<?>[] parameterTypes) { float distA = calculateDistance(a, parameterTypes); float distB = calculateDistance(b, parameterTypes); return Float.compare(distA, distB); } static boolean isAssignmentCompatible(Executable exec, Class<?>... parameterTypes) { // First checks to eliminate an obvious mismatch. if (exec.isVarArgs()) { if (exec.getParameterCount() == 1 && parameterTypes.length == 0) return true; if (parameterTypes.length < exec.getParameterCount() - 1) return false; } else if (parameterTypes.length != exec.getParameterCount()) { return false; } Class<?>[] execTypes = exec.getParameterTypes(); for (int i = 0; i < exec.getParameterCount(); i++) { Class<?> a = parameterTypes[i]; Class<?> b = execTypes[i]; if (i == exec.getParameterCount() - 1 && exec.isVarArgs()) { // Passed array type for var-args if (isAssignmentCompatible(a, b)) { return true; } // Var-args, have to check every element against the array type. b = b.getComponentType(); for (int j = i; j < parameterTypes.length; j++) { a = parameterTypes[j]; if (!isAssignmentCompatible(a, b)) return false; } return true; } else if (!isAssignmentCompatible(a, b)) { return false; } } return true; } static boolean isAssignmentCompatible(Class<?> a, Class<?> b) { if (a == b) return true; // Wrap primitive type a = Primitives.wrap(a); // Check primitive type assignment compatibility if (b.isPrimitive()) { b = Primitives.wrap(b); float distance = calculateDistancePrimitive(a, b); return distance >= 0; } else { return b.isInstance(b); } } @SuppressWarnings("unchecked") public static <T> Optional<Constructor<T>> findMatchingConstructor(Class<T> clazz, Class<?>... parameterTypes) { try { // Try the default method as it is much faster, works in // many of the cases. return Optional.of(clazz.getConstructor(parameterTypes)); } catch (Exception e) { //NOOP } // Aww, snap. Now we have to do it the hard way... try { return Arrays.stream(clazz.getConstructors()) .filter(cons -> isAssignmentCompatible(cons, parameterTypes)) .sorted((consA, consB) -> { int comp = compareDistance(consA, consB, parameterTypes); if (comp == 0) { // Found an ambiguous constructor throw new ReflectionException("Found an ambigious constructor."); } return comp; }) .findFirst() .map(constr2 -> (Constructor<T>) constr2); } catch (NovaException e) { throw e; } catch (Exception e) { return Optional.empty(); } } static <T> T newInstanceMatching(Constructor<T> constr, Object... args) { try { if (args == null || args.length == 0) { // 0 parameter case if (constr.isVarArgs()) { return constr.newInstance(new Object[] { args }); } else { return constr.newInstance(); } } else { if (constr.isVarArgs()) { int last = constr.getParameterCount() - 1; if (last < args.length) { if (args.length == constr.getParameterCount()) { // Check for array type Class<?> append = args[last].getClass(); if (append.isArray() && constr.getParameterTypes()[last].isAssignableFrom(append)) { // We have a matching array type as last // argument, so use that one return constr.newInstance(args); } } // Append arguments as new array. return constr.newInstance(ObjectArrays.concat( Arrays.copyOfRange(args, 0, last), (Object) Arrays.copyOfRange(args, last, args.length))); } else { // Empty parameter -> pass empty array for var-args return constr.newInstance(ObjectArrays.concat(args, (Object) new Object[0])); } } else { // No var-args return constr.newInstance(args); } } } catch (Exception e) { throw new ReflectionException(e); } } public static <T> T newInstanceMatching(Class<T> clazz, Object... args) { try { if (args != null && args.length > 0) { return newInstanceMatching(findMatchingConstructor(clazz, types(args)).get(), args); } else { return clazz.newInstance(); } } catch (Exception e) { throw new ReflectionException(e); } } public static <T> T newInstance(Class<T> clazz, Object... args) { try { if (args != null && args.length > 0) { return clazz.getConstructor(types(args)).newInstance(args); } return clazz.newInstance(); } catch (Exception e) { throw new ReflectionException(e); } } /** * Invokes an action on each field annotated with specified annotation of * given object * * @param <T> Annotation type * @param annotation Annotation type * @param clazz Class to scan * @param action Action to invoke */ public static <T extends Annotation> void forEachAnnotatedField(Class<? extends T> annotation, Class<?> clazz, BiConsumer<Field, T> action) { Arrays.stream(clazz.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(annotation) && !f.isSynthetic()) .forEachOrdered(f -> action.accept(f, f.getAnnotation(annotation))); } /** * Gets all the annotated fields of this class, including all the parents * classes in the order of hierarchy. * * @param annotation Your annotation class. * @param clazz Class to search through. * @return An ordered map of annotated fields and their annotations from the * order of the most sub class to the most super class. */ public static <T extends Annotation> Map<Field, T> getAnnotatedFields(Class<T> annotation, Class<?> clazz) { Map<Field, T> fields = new LinkedHashMap<>(); forEachRecursiveAnnotatedField(annotation, clazz, fields::put); return fields; } // TODO: Cache this? public static <T extends Annotation> void forEachRecursiveAnnotatedField(Class<T> annotation, Class<?> clazz, BiConsumer<Field, T> action) { Arrays.stream(clazz.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(annotation) && !f.isSynthetic()) .forEachOrdered(f -> action.accept(f, f.getAnnotation(annotation))); Class<?> superClass = clazz.getSuperclass(); if (superClass != null) { forEachRecursiveAnnotatedField(annotation, superClass, action); } } public static class ReflectionException extends NovaException { public ReflectionException() { super(); } public ReflectionException(String message, Object... parameters) { super(message, parameters); } public ReflectionException(String message) { super(message); } public ReflectionException(String message, Throwable cause) { super(message, cause); } public ReflectionException(Throwable cause) { super(cause); } } }