All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.vpro.util.ReflectionUtils Maven / Gradle / Ivy

There is a newer version: 5.3.1
Show newest version
package nl.vpro.util;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.*;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.LocaleUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import static nl.vpro.util.ReflectionUtils.ResultAction.*;

/**
 *
 * This provides some basic reflection code to call setters and getters on java beans. There are of course other libraries available
 * for these kind of things, but it is not hard,  and this allows for customizing details of it.
 * 

* Most importantly we add a number of {@link #configured(Object, Map)} methods so this class together with {@link ConfigUtils} provides a complete configuration mechanism. *

* * @author Michiel Meeuwissen * @since 0.40 */ @Slf4j public class ReflectionUtils { /** * A normal 'bean' like setter of a property. The property 'title' is set by {@code setTitle(String}} */ public static final Function SETTER = ReflectionUtils::defaultSetter; /** * A normal 'bean' like getter of a property. The property 'title' is gotten by {@code getTitle()} */ public static final Function GETTER = ReflectionUtils::defaultGetter; /** * A builder like setter of a property. The property 'title' is set by {@code title(String)} */ public static final Function IDENTITY = k -> k; public static String defaultSetter(String k) { return "set" + Character.toUpperCase(k.charAt(0)) + k.substring(1); } public static String defaultGetter(String k) { return "get" + Character.toUpperCase(k.charAt(0)) + k.substring(1); } /** * Sets a certain property value in an object using reflection * @return A {@link Result} object describing what happened */ public static Result setProperty(Object instance, String key, Object value) { return setProperty( instance, key, Arrays.asList(SETTER.apply(key), IDENTITY.apply(key)), value); } /** * Configure an instance using a map of properties. * @param setterName How, given a property name, the setter methods must be calculated. * This is list of functions to convert the name of a property to a setter-method. * */ public static T configured( T instance, Map properties, Collection> setterName) { log.debug("Configuring with {}", properties); final Set found = new HashSet<>(); final Set notfound = new HashSet<>(); properties.forEach( (k, v) -> { List setterNames = setterName.stream() .map(f -> f.apply(String.valueOf(k))) .collect(Collectors.toList()); if (setProperty(instance, k, setterNames, v).getAction() == SET) { if (! found.add(k)) { log.warn("{} Set twice!", k); } } else { notfound.add(k); } } ); log.debug("Set {}/{}. Not found {}", found.size(), properties.size(), notfound); return instance; } /** * Configure an instance using a map of properties. Properties are only set if they are in the given instance still {@code null}. * * @param setterName How, given a property name, the setter methods must be calculated. * @param getterName How, given a property name, the getter methods must be calculated. Needed to check if the current value indeed is still {@code null} * */ public static T configureIfNull(@NonNull T instance, @NonNull Map properties, @NonNull Collection> setterName, @NonNull Collection> getterName) { log.debug("Configuring with {}", properties); final Set found = new HashSet<>(); final Set notfound = new HashSet<>(); properties.forEach( (k, v) -> { List setterNames = setterName.stream() .map(f -> f.apply(String.valueOf(k))) .collect(Collectors.toList()); List getterNames = getterName.stream() .map(f -> f.apply(String.valueOf(k))) .collect(Collectors.toList()); ResultAction result = setProperty(instance, k, setterNames, getterNames, v, true).getAction(); if (result == SET) { if (!found.add(k)) { log.warn("{} Set twice!", k); } } else { if (result.isErroneous()) { notfound.add(k); } } } ); log.debug("Set {}/{}. Not found {}", found.size(), properties.size(), notfound); return instance; } /** * Defaulting version of {@link #configured(Object, Map, Collection)}. * Using {@link #SETTER}, {@link #IDENTITY} for the setter discovery, which means that normal bean like setters and builder pattern setters are supported. *

* E.g. if the given properties contain 'title=foo bar' then the code will try {@code setTitle("foo bar")}, or if this method does not exist {@code title("foo bar"}} */ public static T configured(T instance, Map properties) { return configured(instance, properties, Arrays.asList(SETTER, IDENTITY)); } /** * Defaulting version of {@link #configureIfNull(Object, Map, Collection, Collection)} Using {@link #SETTER}, {@link #IDENTITY} for the setter discovery, and {@link #GETTER}, {@link #IDENTITY} for getter. */ @SuppressWarnings("UnusedReturnValue") public static T configureIfNull(T instance, Map properties) { return configureIfNull(instance, properties, Arrays.asList(SETTER, IDENTITY), Arrays.asList(GETTER, IDENTITY) ); } /** * We can also create the instance itself (supporting the Builder pattern of lombok) * @param clazz Class which can have a static 'builder' method, or a no args constructor * @param properties Properties which are used to configure the created instant * @param type of object to create * @return a new, configured instance of T */ @SuppressWarnings("unchecked") public static T configured(Class clazz, Map properties) { try { Method builderMethod = clazz.getDeclaredMethod("builder"); if (Modifier.isStatic(builderMethod.getModifiers())) { Object builder = builderMethod.invoke(null); configured(builder, properties, Collections.singletonList(IDENTITY)); Method objectMethod = builder.getClass().getMethod("build"); return (T) objectMethod.invoke(builder); } log.debug("No static builder method found"); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { log.debug("Cannot build with builder because ", e); } try { Constructor constructor = clazz.getConstructor(); constructor.setAccessible(true); T object = constructor.newInstance(); configured(object, properties); return object; } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { log.error(e.getMessage(), e); throw new RuntimeException(e); } } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configured(Env env, Class clazz, String... configFiles) { return ConfigUtils.configured(env, clazz, configFiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static void configured(Env env, Object instance, String... configFiles) { ConfigUtils.configured(env, instance, configFiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configured(Class clazz, String... configfiles) { return ConfigUtils.configured(clazz, configfiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static Map getProperties(String... configFiles) { return ConfigUtils.getProperties(configFiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static Map getProperties(Map initial, String... configFiles) { return ConfigUtils.getProperties(initial, configFiles); } @Deprecated public static void substitute(Map map) { ConfigUtils.substitute(map, map); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static Map filtered(Env e, Map properties) { return ConfigUtils.filtered(e, properties); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static Map filtered(Env e, String prefix, Map properties) { return ConfigUtils.filtered(e, prefix, properties); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configured(T instance, String... configFiles) { return ConfigUtils.configured(instance, configFiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configuredInHome(T instance, String... configFiles) { return ConfigUtils.configured(instance, ConfigUtils.getConfigFilesInHome(configFiles)); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configuredInHome(Env env, T instance, String... configFiles) { return ConfigUtils.configuredInHome(env, instance, configFiles); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configured(Env env, Class clazz, Map config) { return ConfigUtils.configured(env, clazz, config); } /** * @deprecated Use {@link ConfigUtils} */ @Deprecated public static T configured(Env env, T instance, Map config) { return ConfigUtils.configured(env, instance, config); } private static Result setProperty( @NonNull Object instance, @Nullable String fieldName, @NonNull Collection setterNames, @NonNull Collection getterNames, @Nullable Object value, boolean onlyIfNull) { String v = value == null ? null : String.valueOf(value); Method[] methods = instance.getClass().getMethods(); Type parameterClass = null; if (onlyIfNull) { for (Method m : methods) { if (getterNames.contains(m.getName()) && m.getParameterCount() == 0) { try { Object existingValue = m.invoke(instance); if (existingValue != null) { return new Result(fieldName, IGNORED); } } catch (IllegalAccessException | InvocationTargetException e) { log.error(e.getMessage(), e); } } } if (fieldName != null) { try { Field f = getField(instance.getClass(), fieldName); f.setAccessible(true); Object existingValue = f.get(instance); if (existingValue != null) { return new Result(fieldName, IGNORED); } } catch (NoSuchFieldException e) { log.debug(e.getMessage()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); } } } for (Method m : methods) { if (setterNames.contains(m.getName()) && m.getParameterCount() == 1) { try { parameterClass = m.getParameters()[0].getParameterizedType(); Object convertedValue = convert(v, parameterClass); m.invoke(instance, convertedValue); log.debug("Set {} to {}", m.getName(), v); return new Result(fieldName, SET); } catch (IllegalAccessException | InvocationTargetException e) { log.error(e.getMessage(), e); } } } if (fieldName != null) { try { Field f = getField(instance.getClass(), fieldName); parameterClass = f.getType(); f.setAccessible(true); f.set(instance, convert(v, f.getGenericType())); log.debug("Set field {} to {}", f.getName(), v); return new Result(fieldName, SET); } catch (NoSuchFieldException e) { log.debug(e.getMessage()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); } } if (parameterClass != null) { log.warn("Unrecognized parameter type " + parameterClass); } log.debug("Unrecognized property {} on {}", setterNames, instance.getClass()); return new Result(fieldName, NOTFOUND); } private static Result setProperty(Object instance, String fieldName, Collection setterNames, Object value) { return setProperty(instance, fieldName, setterNames, Collections.emptyList(), value, false); } static Object convert(String o, Parameter parameter) { return convert(o, parameter.getParameterizedType()); } @SuppressWarnings({"unchecked", "ConstantConditions"}) private static Object convert(String o, Type parameterType) { Class parameterClass; if (parameterType instanceof Class) { parameterClass = (Class) parameterType; } else if (parameterType instanceof ParameterizedType parameterizedType) { parameterClass = (Class) parameterizedType.getRawType(); } else if (parameterType instanceof WildcardType wildcardType) { parameterClass = (Class) wildcardType.getUpperBounds()[0]; } else { throw new UnsupportedOperationException("Cannot convert " + o + " to " + parameterType); } if (String.class.isAssignableFrom(parameterClass)) { return o; } else if (boolean.class.equals(parameterClass) || parameterClass.isAssignableFrom(Boolean.class)) { return Boolean.valueOf(o); } else if (int.class.equals(parameterClass) || parameterClass.isAssignableFrom(Integer.class)) { return Integer.valueOf(o); } else if (long.class.equals(parameterClass) || parameterClass.isAssignableFrom(Long.class)) { return Long.valueOf(o); } else if (float.class.equals(parameterClass) || parameterClass.isAssignableFrom(Float.class)) { return Float.valueOf(o); } else if (double.class.equals(parameterClass) || parameterClass.isAssignableFrom(Double.class)) { return Double.valueOf(o); } else if (Enum.class.isAssignableFrom(parameterClass)) { try { return Enum.valueOf((Class) parameterClass, o); } catch (IllegalArgumentException iae) { try { return Enum.valueOf((Class) parameterClass, o.toUpperCase()); } catch (IllegalArgumentException iae2) { try { Method valueOfXml = parameterClass.getDeclaredMethod("valueOfXml", String.class); valueOfXml.setAccessible(true); return valueOfXml.invoke(null, o); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException nsme) { throw iae2; } } } } else if (parameterClass.isAssignableFrom(Locale.class)) { return LocaleUtils.toLocale(o); } else if (parameterClass.isAssignableFrom(Duration.class)) { return TimeUtils.parseDuration(o).orElse(null); } else if (parameterClass.isAssignableFrom(List.class)) { ParameterizedType parameterizedType = (ParameterizedType) parameterType; return Arrays.stream(o.split("\\s*,\\s*")) .map(s -> convert(s, parameterizedType.getActualTypeArguments()[0])) .collect(Collectors.toList()); } else { throw new UnsupportedOperationException(); } } /** * Returns the first {@link Field} in the hierarchy for the specified name */ private static Field getField(@NonNull Class clazz, final @NonNull String name) throws NoSuchFieldException { NoSuchFieldException noSuchFieldException = null; while (clazz != null) { try { return clazz.getDeclaredField(name); } catch (NoSuchFieldException e) { noSuchFieldException = e; } clazz = clazz.getSuperclass(); } throw noSuchFieldException; } public static boolean hasClass(String clazz) { try { classForName(clazz, ReflectionUtils.class.getClassLoader()); return true; } catch (ClassNotFoundException e) { log.debug(e.getMessage(), e); return false; } } /** * Finds the class given its name. *
* This method also retrieves primitive types (unlike {@code Class#forName(String)}). */ public static Class classForName(String name, ClassLoader loader) throws ClassNotFoundException { Class c = primitiveClasses.get(name); if (c == null) { c = Class.forName(name, false, loader); } return c; } public static Class classForName(String name) throws ClassNotFoundException { return classForName(name, ReflectionUtils.class.getClassLoader()); } @SuppressWarnings("unchecked") public static T callProtected(Object o, Class clazz, String method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method m = clazz.getDeclaredMethod("getPredictionsForXml"); m.setAccessible(true); T t = (T) m.invoke(o); return t; } public static T callProtected(Object o, String method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class clazz = o.getClass(); while(! clazz.equals(Object.class)) { try { return callProtected(o, clazz, method); } catch (NoSuchMethodException nsm) { clazz = clazz.getSuperclass(); } } throw new NoSuchMethodException(); } private static final Map> primitiveClasses = new HashMap<>(); static { Class[] primitives = {byte.class, short.class, int.class, long.class, float.class, double.class, char.class, boolean.class}; for (Class clazz : primitives) primitiveClasses.put(clazz.getName(), clazz); } @Getter public static class Result { final String property; final ResultAction action; public Result(String property, ResultAction action) { this.property = property; this.action = action; } } @Getter public enum ResultAction { SET(false), NOTFOUND(true), ERROR(true), IGNORED(false); final boolean erroneous; ResultAction(boolean erroneous) { this.erroneous = erroneous; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy