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

cl.core.util.Reflections Maven / Gradle / Ivy

The newest version!
package cl.core.util;

import static cl.core.decorator.exception.ExceptionDecorators.safely;
import static cl.core.decorator.exception.ExceptionDecorators.uncheck;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import cl.core.ds.Pair;
import cl.core.function.FunctionWithException;
import cl.core.function.SupplierWithException;
import cl.core.function.stringparser.StringParsers;
import cl.core.function.stringparser.StringParsers.StringParser;
import cl.core.types.Transient;

/**
 * Reflection utilities.
 *  
 * 

Any checked exceptions thrown by reflection API will be * wrapped in run-time exceptions in all methods of this class. */ @SuppressWarnings("unchecked") public final class Reflections { private Reflections(){} /** * Create a new instance of the given class without throwing a compile-time * exception (but it will throw run rime exception in case of instantiation problem). */ public static T newInstance(Class klass) { return uncheck(() -> { Constructor c = klass.getConstructor(); c.setAccessible(true); return c.newInstance(); }); } /** * Execute a setter. * * @param propertyName A property name to set. This is not a method name, but a lower-cased property name. * (For example, it's not "setFirstName" but "firstName") * @param propertyValue A property value * @param object An object, whose property we're setting. */ public static void set(String propertyName, Object propertyValue, Object object) { call(getSetterName(propertyName), object, propertyValue); } /** * Execute a getter. * * @param propertyName A property name. This is not a method name, but a lower-cased property name. * @param object An object on which the method is being called. * @return A property value */ public static T get(String propertyName, Object object) { return call(getGetterName(propertyName), object); } /** * Call a method which takes no arguments on a given object. * * @param method A method name * @param object An object on which the method is being called * @return The result of the method call */ public static T call(String method, Object object) { return (T)call( () -> object.getClass().getMethod(method), m -> m.invoke(object)); } /** * Call a method which takes one argument on a given object. * * @param method A method name * @param object An object on which the method is being called * @param arg A method argument * @return The result of the method call */ public static T call(String method, Object object, Object arg) { return (T)call( () -> object.getClass().getMethod(method, arg.getClass()), m -> m.invoke(object, arg)); } /** * Call a method which takes two arguments on a given object. * * @param method A method name * @param object An object on which the method is being called * @param arg1 The first method argument * @param arg2 The second method argument * @return The result of the method call */ public static T call(String method, Object object, Object arg1, Object arg2) { return (T)call( () -> object.getClass().getMethod(method, arg1.getClass(), arg2.getClass()), m -> m.invoke(object, arg1, arg2)); } /** * Call a method which takes three arguments on a given object. * * @param method A method name * @param object An object on which the method is being called * @param arg1 The first method argument * @param arg2 The second method argument * @param arg3 The third method argument * @return The result of the method call */ public static T call(String method, Object object, Object arg1, Object arg2, Object arg3) { return (T)call( () -> object.getClass().getMethod(method, arg1.getClass(), arg2.getClass(), arg3.getClass()), m -> m.invoke(object, arg1, arg2, arg3)); } /** * Call a method which take variable number of arguments on a given object. * * @param method A method name * @param object An object on which the method is being called * @param args Method arguments. Note that, the arguments are of the same type * @return The result of the method call */ @SafeVarargs public static T call(String method, Object object, P ... args) { return (T)call(() -> object.getClass().getMethod(method, args.getClass()), m -> m.invoke(object, new Object[] {args})); } /** * Get a value directly from an object field. * * @param fieldName Field name * @param object An object whose value we're getting * @return The field value */ public static T getField(String fieldName, Object object) { return (T)uncheck(() -> getFld(fieldName, object.getClass()).get(object)); } /** * Check if a field with the given name exists in the object. */ public static boolean fieldExists(String fieldName, Object object) { return fieldExists(fieldName, object.getClass()); } /** * Check if a field with the given name exists in the class. */ public static boolean fieldExists(String fieldName, Class klass) { return null != safely(() -> getFld(fieldName, klass), NoSuchFieldException.class); } /** * Set a field value directly. * * @param fieldName Field name * @param fieldValue Filed value * @param object An object whose value we're setting */ public static void setField(String fieldName, Object fieldValue, Object object) { uncheck(() -> getFld(fieldName, object.getClass()).set(object, fieldValue)); } /** * Set a field directly without knowledge of the field type. The method will * try to parse the given string value according to the field type discovered * via reflection. * *

* This method will use {@link cl.core.function.stringparser.StringParsers} * class to parse strings into objects. * * @param fieldName Field name * @param fieldValue Field value string representation * @param object An object whose value we're setting * * @throws UnsupportedOperationException if the type of the field is not supported * by this method * * @see cl.core.function.stringparser.StringParsers */ public static void trySetField(String fieldName, String fieldValue, Object object) { if (fieldValue == null || fieldValue.isEmpty()) return; Object o = object; String v = fieldValue; uncheck(() -> { Field field = getFld(fieldName, o.getClass()); Class klass = field.getType(); if (klass.isPrimitive()) { if (klass == int.class) field.setInt(o, Integer.parseInt(v)); else if (klass == double.class) field.setDouble(o, Double.parseDouble(v)); else if (klass == long.class) field.setLong(o, Long.parseLong(v)); else if (klass == float.class) field.setFloat(o, Float.parseFloat(v)); else if (klass == boolean.class) field.setBoolean(o, Boolean.parseBoolean(v)); else if (klass == char.class) field.setChar(o, v.charAt(0)); else if (klass == byte.class) field.setByte(o, Byte.parseByte(v)); else if (klass == short.class) field.setShort(o, Short.parseShort(v)); } else if (klass.isEnum()) { @SuppressWarnings("rawtypes") Class enumClass = klass; field.set(o, Enum.valueOf(enumClass, v)); } else { StringParser p = StringParsers.get(klass); if (p != null) { field.set(o, p.parse(v)); } else { throw new UnsupportedOperationException( "Cannot parse string value '" + v + "' to instance of class " + klass); } } }); } /** * Find a setter method (with one parameter only) by property name amongst the object's methods, * figure out the method parameter type, parse a given String input to object of that type, * and, finally, call the setter. * * @param property property name in Camel case * @param value a string value which will be parsed into an object which matches the setter * parameter type * @param object an object whose setter we are calling * * @throws UnsupportedOperationException if a method is not found by the property name * or no string parser found for the method parameter type */ public static void findAndCallSetter(String property, String value, Object object) { Pair> methodAndParser = getSetterWithParser(property, object.getClass()); uncheck(() -> methodAndParser._1().invoke(object, methodAndParser._2().apply(value))); } /** * Find a setter method (with one parameter only) by property name, and * return a string parser for this method's parameter type * * @param property property name in Camel case * @param klass object class * * @throws UnsupportedOperationException if a method is not found by the property name * or no string parser found for the method parameter type */ public static Function findStringParserForSetter(String property, Class klass) { return getSetterWithParser(property, klass)._2(); } /** * Return true if a getter exists for the given property in the given object. */ public static boolean getterExists(String property, Object object) { return getterExists(property, object.getClass()); } /** * Return true if a getter exists for the given property in the given class. */ public static boolean getterExists(String property, Class klass) { return findGetter(property, klass).isPresent(); } /** * Return true if a setter exists for the given property in the given object. */ public static boolean setterExists(String property, Object object) { return setterExists(property, object.getClass()); } /** * Return true if a setter exists for the given property in the given class. */ public static boolean setterExists(String property, Class klass) { return findSetter(property, klass).isPresent(); } /** * Return an array of field names derived from fields. This will not include static and transient fields. */ public static String[] getPropertiesFromFields(Class klass) { return getFields(klass).map(Field::getName).toArray(String[]::new); } /** * Return an array of field names derived from getters. This will not include * static, abstract, and {@link cl.core.types.Transient} methods. */ public static String[] getPropertiesFromGetters(Class klass) { return getGetters(klass).map(m -> Strings.pascalToCamel(m.getName().substring(3))) .toArray(String[]::new); } /** * Return an array of field names derived from setters. This will not include * static, abstract, and {@link cl.core.types.Transient} methods. */ public static String[] getPropertiesFromSetters(Class klass) { return getSetters(klass).map(m -> Strings.pascalToCamel(m.getName().substring(3))) .toArray(String[]::new); } private static Stream getFields(Class klass) { Predicate isSerializableField = f -> !Modifier.isStatic(f.getModifiers()) && !Modifier.isTransient(f.getModifiers()) && f.getAnnotation(Transient.class) == null && f.getName().charAt(0) != '$' && // reference to the outer object in inner classes in Scala !f.getName().startsWith("this"); // reference to the outer object in inteer classes in Java return Stream.of(klass.getDeclaredFields()).filter(isSerializableField); } private static Stream getGetters(Class klass) { return getNonTransientOrStaticMethods(klass) .filter(m -> m.getParameterCount() == 0 && m.getName().startsWith("get") && !"getClass".equals(m.getName())); } private static Stream getSetters(Class klass) { return getNonTransientOrStaticMethods(klass) .filter(m -> m.getParameterCount() == 1 && m.getName().startsWith("set")); } private static Field getFld(String fieldName, Class klass) throws NoSuchFieldException { Field field = klass.getDeclaredField(fieldName); field.setAccessible(true); return field; } private static T call( SupplierWithException methodSupplier, FunctionWithException methodToResultFunction) { return uncheck(() -> methodToResultFunction.apply(methodSupplier.get())); } private static String getSetterName(String propertyName) { return "set" + Strings.capitalize(propertyName); } private static String getGetterName(String propertyName) { return "get" + Strings.capitalize(propertyName); } private static Optional findGetter(String property, Class klass) { String getterName = getGetterName(property); return getNonTransientOrStaticMethods(klass) .filter(m -> m.getParameterCount() == 0 && m.getName().equals(getterName)) .findFirst(); } private static Optional findSetter(String property, Class klass) { String setterName = getSetterName(property); return getNonTransientOrStaticMethods(klass) .filter(m -> m.getParameterCount() == 1 && m.getName().equals(setterName)) .findFirst(); } private static Stream getNonTransientOrStaticMethods(Class klass) { return Stream.of(klass.getMethods()).filter(isNotTransientAbstractOrStatic); } private static Predicate isNotTransientAbstractOrStatic = m -> !Modifier.isStatic(m.getModifiers()) && !Modifier.isAbstract(m.getModifiers()) && m.getAnnotation(Transient.class) == null; private static Pair> getSetterWithParser(String property, Class klass) { return findSetter(property, klass).map(m -> { Class parameterType = m.getParameterTypes()[0]; StringParser p = null; if (parameterType.isPrimitive()) { if (parameterType == int.class) p = StringParsers.intParser; else if (parameterType == double.class) p = StringParsers.doubleParser; else if (parameterType == long.class) p = StringParsers.longParser; else if (parameterType == float.class) p = StringParsers.floatParser; else if (parameterType == boolean.class) p = StringParsers.booleanParser; else if (parameterType == char.class) p = StringParsers.charParser; else if (parameterType == byte.class) p = StringParsers.byteParser; else if (parameterType == short.class) p = StringParsers.shortParser; } else if (parameterType.isEnum()) { @SuppressWarnings("rawtypes") Class enumClass = parameterType; p = s -> Enum.valueOf(enumClass, s); } else { p = StringParsers.get(parameterType); } if (p == null) { throw new UnsupportedOperationException( "Cannot find string parser for method's '" + getSetterName(property) + "' parameter type '" + parameterType + "'"); } return new Pair<>(m, p.toFunction()); }).orElseThrow(() -> new UnsupportedOperationException("No method found by name " + getSetterName(property))); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy