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

com.goikosoft.reflection.ReflectionUtils Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License (MIT)

Copyright (c) 2020 Dario Goikoetxea

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */
package com.goikosoft.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;


import org.apache.commons.beanutils.PropertyUtils;


/**
 * Simple static facade for common reflection functions. Custom exceptions are created in order to make
 * it simpler to catch all the exceptions related with it. All exceptions are nested under ReflectionException
 * and ReflectionRuntimeException for unchecked exceptions.
 * 
 * 

All the exceptions thrown will have the original exception as cause, and getMessage functions are overriden * in order to print the causality chain automatically. * *

Functionalities: *

    *
  • Directly read / write fields.
  • *
  • Using getters / setters of fields using Apache Commons BeanUtils
  • *
  • Execute methods by name and parameters. It allows to specify the class, just in case the provided parameter is a subclass of the necesary argument
  • *
  • Create new instances using constructors with or without parameters
  • *
  • Clone instances. It's a deep-copy of parameters. May be dangerous. It can only clone fields whose types have constructors without parameters or cloneables
  • *
  • Combine instances. It's a deep-copy of parameters. Copìes all non-null fields from source instance to destino instance
  • *
* * @author Dario Goikoetxea. * */ public class ReflectionUtils { /** * Creates a new instance using the constructor without parameters * * @param Class to instantiate * @param clazz Class object representing the class to instantiate * @return new instance * @throws ClassException There's no access to the class or it is abstract * @throws NonExistentMethodException Constructor does not exist * @throws ForbiddenMethodException Constructor not accessible * @throws InvocationException Constructor throws exception * @throws MethodException Constructor does not exist or there's no access to it. Also if constructor throws exception * @throws NullType if clazz is null */ public static T initReflection(Class clazz) throws ClassException, NonExistentMethodException, ForbiddenMethodException, MethodException, InvocationException, NullType { return initReflection(clazz, (List>) null, null); } /** * Creates a new instance using the constructor with single parameter * * @param Class to instantiate * @param clazz Class object representing the class to instantiate * @param paramType constructor parameter type * @param value constructor parameter value * @return new instance * @throws ClassException There's no access to the class or it is abstract * @throws NonExistentMethodException Constructor does not exist * @throws ForbiddenMethodException Constructor not accessible * @throws InvocationException Constructor throws exception * @throws MethodException Constructor does not exist or there's no access to it. Also if constructor throws exception * @throws InvalidArgument Illegal argument passed to constructor * @throws NullType If clazz is null * @throws NullArgument If paramType and value are null. */ public static T initReflection(Class clazz, Class paramType, Object value) throws ClassException, NonExistentMethodException, ForbiddenMethodException, MethodException, InvocationException, InvalidArgument, NullType, NullArgument { if(value==null) { if(paramType==null) return initReflection(clazz, (List>) null, null); // Ambos son null. Pasamos null a la función de listas }else { if(paramType==null) paramType = value.getClass(); } List> listaClases = new ArrayList>(); List args = new ArrayList(); listaClases.add(paramType); args.add(value); return initReflection(clazz, listaClases, args); } /** * Creates a new instance using the constructor with multiple parameters * * @param Class to instantiate * @param clazz Class object representing the class to instantiate * @param classes list of types declared for constructor parameters * @param args arguments for the constructor. If classes is null, it will be infered from this * @return new instance * @throws ClassException There's no access to the class or it is abstract * @throws NonExistentMethodException Constructor does not exist * @throws ForbiddenMethodException Constructor not accessible * @throws InvocationException Constructor throws exception * @throws MethodException Constructor does not exist or there's no access to it. Also if constructor throws exception * @throws InvalidArgument Illegal argument passed to constructor * @throws NullType If clazz is null * @throws NullArgument If args is empty or null but classes is not or classes is not provided and a single element of args is null */ public static T initReflection(Class clazz, List> classes, List args) throws ClassException, NonExistentMethodException, ForbiddenMethodException, MethodException, InvocationException, InvalidArgument, NullType, NullArgument { if(clazz==null) throw new NullType("Se proporcionó una clase nula a initReflection."); if(classes!=null && args==null && !classes.isEmpty()) throw new NullArgument("Se proporcionó una lista de clases pero no argumentos."); if(args!=null && classes!=null && classes.size()!=args.size()) throw new InvalidArgument("Se proporcionó una lista de clases de distinto tamaño que la de argumentos"); try { Constructor cons; if(args!=null && !args.isEmpty()) { cons = searchConstructorByParamClassesOrSuper(clazz, genListOfClasses(classes, args)); return cons.newInstance(args.toArray()); }else { cons = clazz.getConstructor(); return cons.newInstance(); } } catch (NoSuchMethodException e) { // Excepciones a la hora de obtener el constructor throw new NonExistentMethodException("El constructor no existe", null, clazz, e); } catch (SecurityException e) { // Excepciones a la hora de obtener el constructor throw new ForbiddenMethodException("No hay acceso al constructor", null, clazz, e); } catch (InstantiationException e) { throw new ClassException("No se pudo instanciar el objeto debido a que la clase es abstracta", clazz, e); } catch (IllegalAccessException e) { throw new ClassException("No hay acceso a la definición de la clase", clazz, e); } catch (IllegalArgumentException e) { throw new InvalidArgument("Argumento ilegal pasado al constructor. No debería pasar", e); } catch (InvocationTargetException e) { throw new InvocationException("Excepción dentro del constructor", null, clazz, e); } catch (RuntimeException e) { throw new MethodException("Excepcion no controlada al instanciar el objeto", (Method)null, clazz, e); } } /** * Creates a new instance using the constructor with multiple parameters * * @param Class to instantiate * @param clazz Class object representing the class to instantiate * @param args arguments for the constructor. If classes is null, it will be infered from this * @return new instance * @throws ClassException There's no access to the class or it is abstract * @throws NonExistentMethodException Constructor does not exist * @throws ForbiddenMethodException Constructor not accessible * @throws InvocationException Constructor throws exception * @throws MethodException Constructor does not exist or there's no access to it. Also if constructor throws exception * @throws InvalidArgument Illegal argument passed to constructor * @throws NullType If clazz is null * @throws NullArgument If one of args is null */ public static T initReflection(Class clazz, List args) throws ClassException, NonExistentMethodException, ForbiddenMethodException, MethodException, InvocationException, InvalidArgument, NullType, NullArgument { return initReflection(clazz, (List>)null, args); } /** * Stores value into a property of the bean using the setter * * @param bean the instance to use the setter * @param property name of the property * @param value value to store * @throws NonExistentMethodException Setter does not exist * @throws ForbiddenMethodException Setter is not accessible * @throws InvocationException Setter throws exception * @throws PropertyException setProperty throws exception * @throws NullObject bean is null * @throws NullArgument property is null * @see PropertyUtils#setProperty(Object, String, Object) */ public static void setterReflection(Object bean, String property, Object value) throws ForbiddenMethodException, NonExistentMethodException, InvocationException, PropertyException, NullObject, NullArgument { if(bean==null) throw new NullObject("Se intenta establecer una propiedad de un Bean null y no hay permiso para inicializarlo"); if(property==null) throw new NullArgument("No se ha indicado la propiedad a utilizar en el setter"); try { PropertyUtils.setProperty(bean, property, value); } catch (IllegalAccessException e) { throw new ForbiddenMethodException("No se tiene acceso al setter solicitado", property, bean.getClass(), e); } catch (InvocationTargetException e) { throw new InvocationException("InvocationTargetException escribiendo atributo " + property, null, bean.getClass(), e); } catch (NoSuchMethodException e) { throw new NonExistentMethodException("No Existe setter solicitado", property, bean.getClass(), e); } catch (RuntimeException e) { throw new PropertyException("Excepcion no controlada al settear una property", property, bean.getClass(), e); } } /** * Uses the getter of a bean. The given object must be a bean * * @param bean the instance to use the setter * @param property name of the property * @return the value returned by the getter * @throws NonExistentMethodException Getter does not exist * @throws ForbiddenMethodException Getter is not accessible * @throws InvocationException Getter throws exception * @throws PropertyException getProperty throws exception * @throws NullObject bean is null * @throws NullArgument property is null * * @see PropertyUtils#getProperty(Object,String) */ public static Object getterReflection(Object bean, String property) throws ForbiddenMethodException, NonExistentMethodException, InvocationException, PropertyException, NullObject, NullArgument { if(bean==null) throw new NullObject("Se intenta leer una propiedad de un Bean null"); if(property==null) throw new NullArgument("No se ha indicado la propiedad a utilizar en el getter"); try { return PropertyUtils.getProperty(bean, property); } catch (IllegalAccessException e) { throw new ForbiddenMethodException("No se tiene acceso al getter solicitado", property, bean.getClass(), e); } catch (InvocationTargetException e) { throw new InvocationException("InvocationTargetException leyendo atributo " + property, null, bean.getClass(), e); } catch (NoSuchMethodException e) { throw new NonExistentMethodException("No existe getter solicitado", property, bean.getClass(), e); } catch (RuntimeException e) { throw new PropertyException("Excepcion no controlada al gettear una property", property, bean.getClass(), e); } } /** * Uses the getter of a bean. The given object must be a bean. The method casts the return object to T * * @param desired return type * @param bean the instance to use the setter * @param property name of the property * @param clazz Class object representing the desired return type * @return the value returned by the getter * @throws NonExistentMethodException Getter does not exist * @throws ForbiddenMethodException Getter is not accessible * @throws InvocationException Getter throws exception * @throws PropertyException getProperty throws exception * @throws IncompatibleType if return type cannot be cast to T * @throws NullObject bean is null * @throws NullArgument property is null * * @see PropertyUtils#getProperty(Object,String) */ public static T getterReflection(Object bean, String property, Class clazz) throws ForbiddenMethodException, NonExistentMethodException, InvocationException, PropertyException, IncompatibleType, NullObject, NullType, NullArgument { if(clazz==null) throw new NullType("No se porporcionó ninguna clase a getterReflection"); Object valor = getterReflection(bean, property); if(valor==null) return null; try { return clazz.cast(valor); } catch (ClassCastException e) { throw new IncompatibleType("La propiedad almacenada no es compatible con la clase proporcionada. Getter: "+property+". Clase objeto base: "+bean.getClass(), valor.getClass(), clazz, e); } } /** * Obtains the return type of a getter * * @param bean the instance to use the setter * @param property name of the property * @return the type returned by the getter * @throws InvocationException Getter throws exception * @throws PropertyException getProperty throws exception * @throws NullObject bean is null * @throws NullArgument property is null * * @see PropertyUtils#getPropertyType(Object,String) */ public static Class getBeanPropertyType(Object bean, String property) throws PropertyException, NullArgument, NullObject, InvocationException { if(bean==null) throw new NullObject("Se intenta leer una propiedad de un Bean null"); if(property==null) throw new NullArgument("No se ha indicado la propiedad a utilizar en el getter"); try { return PropertyUtils.getPropertyType(bean, property); } catch (IllegalAccessException e) { throw new PropertyException("No se tiene acceso de lectura al atributo solicitado", property, bean.getClass(), e); } catch (IllegalArgumentException e) { throw new PropertyException("Alguna de las referencias a propiedades anidadas devolvió null", property, bean.getClass(), e); } catch (InvocationTargetException e) { throw new InvocationException("InvocationTargetException leyendo atributo: "+property, null, bean.getClass(), e); } catch (NoSuchMethodException e) { throw new PropertyException("No existe atributo solicitada: ", property, bean.getClass(), e); } catch (RuntimeException e) { throw new PropertyException("Excepcion no controlada leyendo atributo", property, bean.getClass(), e); } } /** * Returns the type of a field of the given class * * @param clazz Class object representing the class of the field * @param field Field name * @return type of the field * @throws PropertyException Exception reading property * @throws NullType clazz is null * @throws NullArgument field is null */ public static Class getFieldType(Class clazz, String field) throws PropertyException, NullType, NullArgument { Field campo = searchField(clazz, field, true, false, null); if(campo==null) return null; // FIXME: Should never happen. Remove? return campo.getType(); } public static void setPropertyReflection(Object object, String atributo, Object valor, boolean forceAccess) throws PropertyException, NullObject, NullArgument, NullType, IncompatibleType { if(object==null) throw new NullObject("Se intenta leer una propiedad de un Bean null"); if(atributo==null || atributo.isEmpty()) throw new NullArgument("No se ha indicado la propiedad a utilizar en el getter"); Field campo = searchField(object.getClass(), atributo, true, true, Object.class); writeField(campo, object, valor, forceAccess); } public static Object getPropertyReflection(Object object, String atributo, boolean forceAccess) throws PropertyException, NullObject, NullArgument, NullType { if(object==null) throw new NullObject("Se intenta leer una propiedad de un Bean null"); if(atributo==null || atributo.isEmpty()) throw new NullArgument("No se ha indicado el atributo a obtener"); Field campo = searchField(object.getClass(), atributo, forceAccess, forceAccess, null); return readField(campo, object, forceAccess); } public static T getPropertyReflection(Object objeto, String propiedad, boolean forceAccess, Class clase) throws NullObject, NullType, PropertyException, NullArgument, IncompatibleType { if(clase==null) throw new NullType("No se porporcionó ninguna clase a getterReflection"); Object valor = getPropertyReflection(objeto, propiedad, forceAccess); if(valor==null) return null; try { return clase.cast(valor); } catch (ClassCastException e) { throw new IncompatibleType("La propiedad almacenada no es compatible con la clase proporcionada. Getter: "+propiedad+". Clase objeto base: "+objeto.getClass(), valor.getClass(), clase, e); } } public static Object runMethod(Object objeto, String nombreMetodo) throws NullObject, MethodException,AmbiguousMethodException, NullType, NullArgument, InvocationException, InvalidArgument { return runMethod(objeto, nombreMetodo, null, null); } public static Object runMethod(Object objeto, String nombreMetodo, List args) throws NullObject, MethodException, AmbiguousMethodException, NullType, NullArgument, InvocationException, InvalidArgument { return runMethod(objeto, nombreMetodo, args, null); } public static Object runMethod(Object objeto, String nombreMetodo, List args, List> listaClases) throws NullObject, MethodException, AmbiguousMethodException, NullType, NullArgument, InvocationException, InvalidArgument { if(objeto==null) throw new NullObject("Se intenta ejecutar un método de un objeto nulo"); Class clase = objeto.getClass(); Method metodo = searchMethodByParamClassesOrSuper(clase, nombreMetodo, genListOfClasses(listaClases, args)); return runMethod(objeto, metodo, args); } public static Object runMethod(Object objeto, Method metodo, List args) throws NullObject, MethodException, AmbiguousMethodException, InvocationException, InvalidArgument { if(objeto==null) throw new NullObject("Se intenta ejecutar un método de un objeto nulo"); try { if(args!=null && !args.isEmpty()) { return metodo.invoke(objeto, args.toArray()); }else { return metodo.invoke(objeto); } } catch (IllegalAccessException e) { throw new MethodException("No hay permiso para ejecutar el método", metodo, objeto.getClass(), e); } catch (IllegalArgumentException e) { throw new InvalidArgument("Argumentos ilegales en el método "+metodo.toString()+" en el objeto del tipo "+objeto.getClass(), e); } catch (InvocationTargetException e) { throw new InvocationException("Excepción producida dentro de método ", metodo, objeto.getClass(), e); } catch (RuntimeException e) { throw new MethodException("Excepción no controlada invocando método", metodo, objeto.getClass(), e); } } public static TipoSalida runMethod(Object objeto, Class tipoSalida, String nombreMetodo) throws NullObject, NullType, MethodException, AmbiguousMethodException, IncompatibleType, InvocationException, InvalidArgument, NullArgument { return runMethod(objeto, tipoSalida, nombreMetodo, null, null); } public static TipoSalida runMethod(Object objeto, Class tipoSalida, String nombreMetodo, List args) throws NullObject, NullType, MethodException, AmbiguousMethodException, IncompatibleType, InvocationException, InvalidArgument, NullArgument { return runMethod(objeto, tipoSalida, nombreMetodo, args, null); } public static TipoSalida runMethod(Object objeto, Class tipoSalida, String nombreMetodo, List args, List> listaClases) throws NullObject, NullType, MethodException, AmbiguousMethodException, InvalidArgument, IncompatibleType, InvocationException, NullArgument { if(tipoSalida==null) throw new NullType("Se intenta ejecutar el ejecutarMetodoReflection tipado pero se proporciono tipoSalida nulo."); if(objeto==null) throw new NullObject("Se intenta ejecutar un método de un objeto nulo"); //Buscar el método y comprobar que es asignable al tipo de salida antes de ejecutarlo. Si no es idempotente podríamos tener problemas. Class clase = objeto.getClass(); Method metodo = searchMethodByParamClassesOrSuper(clase, nombreMetodo, genListOfClasses(listaClases, args)); return runMethod(objeto, tipoSalida, metodo, args); } public static TipoSalida runMethod(Object objeto, Class tipoSalida, Method metodo, List args) throws NullObject, NullType, MethodException, AmbiguousMethodException, InvalidArgument, IncompatibleType, InvocationException { if(tipoSalida==null) throw new NullType("Se intenta ejecutar el ejecutarMetodoReflection tipado pero se proporciono tipoSalida nulo."); if(objeto==null) throw new NullObject("Se intenta ejecutar un método de un objeto nulo"); if(metodo==null) throw new MethodException("No se prooprcionó un métodoo"); //Buscar el método y comprobar que es asignable al tipo de salida antes de ejecutarlo. Si no es idempotente podríamos tener problemas. try { // Comprobación de que el método es asignable a la salida Class tipoRetorno = metodo.getReturnType(); if(!tipoSalida.isAssignableFrom(tipoRetorno)){ throw new IncompatibleType("La salida del método "+metodo.toString()+" ("+tipoRetorno.toString()+") en el objeto del tipo "+objeto.getClass()+" no es compatible con la salida solicitada", tipoRetorno, tipoSalida); } if(args!=null && !args.isEmpty()) { return tipoSalida.cast(metodo.invoke(objeto, args.toArray())); }else { return tipoSalida.cast(metodo.invoke(objeto)); } } catch (IllegalAccessException e) { throw new MethodException("No hay permiso para ejecutar el método", metodo, objeto.getClass(), e); } catch (IllegalArgumentException e) { throw new InvalidArgument("Argumentos ilegales en el método "+metodo.toString()+" en el objeto del tipo "+objeto.getClass(), e); } catch (InvocationTargetException e) { throw new InvocationException("Excepción producida dentro de método ",metodo, objeto.getClass(), e); } catch (ClassCastException e) { throw new IncompatibleType("Error convirtiendo la salida de "+metodo.toString()+" en el objeto del tipo "+objeto.getClass()+". No debería pasar porque lo habíamos comprobado", null, tipoSalida, e); } catch (RuntimeException e) { throw new MethodException("Excepción no controlada invocando método", metodo, objeto.getClass(), e); } } /** * Search for methods declared in clazz or any of it's superclases by using the instances of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * * This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param methodName name of the method to search * @param instances list of instances that will be passed as arguments to the method later. This does not invoke the method, but guarantees that instances will be compatible * @return Method matching the given parametters. Guaranteed to be not null. * @throws NullArgument if one of the instances passed is null * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Method searchMethodByParamInstances(Class clazz, String methodName, List instances) throws NullArgument, NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { return searchMethodByParamClasses(clazz, methodName, genListOfClasses(instances)); } /** * Search for methods declared in clazz or any of it's superclases by using the Class objects of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments inside classes list, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * * This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param methodName name of the method to search * @param classes parameters minimal class types * @return Method matching the given parametters. Guaranteed to be not null. * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Method searchMethodByParamClasses(Class clazz, String methodName, List> classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { try { if(classes!=null && !classes.isEmpty()) { Class[] arrayClaseDummy = {}; Class[] arrayClases = classes.toArray(arrayClaseDummy); return clazz.getMethod(methodName, arrayClases); }else { return clazz.getMethod(methodName); } } catch (NoSuchMethodException e) { throw new NonExistentMethodException("El método no existe", getPrettyMethodName(methodName, classes), clazz, e); } catch (SecurityException e) { throw new ForbiddenMethodException("SecurityException buscando el método", getPrettyMethodName(methodName, classes), clazz, e); } catch (RuntimeException e) { throw new MethodException("Excepción no controlada buscando el método", getPrettyMethodName(methodName, classes), clazz, e); } } private static String getPrettyMethodName(String methodName, List> classes) { List args = new ArrayList(); for(Class clazz : classes) { args.add(clazz.toString()); } return methodName + "("+String.join(", ", args)+")"; } /** * Search for methods declared in clazz or any of it's superclases by using the instances of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * * This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param methodName name of the method to search * @param instances list of instances that will be passed as arguments to the method later. This does not invoke the method, but guarantees that instances will be compatible * @return Method matching the given parametters. Guaranteed to be not null. * @throws NullArgument if one of the instances passed is null * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Method searchMethodByParamInstancesOrSuper(Class clazz, String methodName, List instances) throws NullArgument, AmbiguousMethodException, NonExistentMethodException, ForbiddenMethodException, MethodException { return searchMethodByParamClassesOrSuper(clazz, methodName, genListOfClasses(instances)); } /** * Search for methods declared in clazz or any of it's superclases by using the Class objects of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments inside classes list, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * * This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param methodName name of the method to search * @param classes parameters minimal class types * @return Method matching the given parametters. Guaranteed to be not null. * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Method searchMethodByParamClassesOrSuper(Class clazz, String methodName, List> classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { try { if(classes!=null && !classes.isEmpty()) { Class[] arrayClaseDummy = {}; Class[] arrayClases = classes.toArray(arrayClaseDummy); return clazz.getMethod(methodName, arrayClases); }else { return clazz.getMethod(methodName); } } catch (NoSuchMethodException e) { if(classes == null || classes.isEmpty()) throw new NonExistentMethodException("El método sin argumentos no existe", methodName, clazz, e); return guessMethod(clazz, methodName, classes); } catch (SecurityException e) { throw new ForbiddenMethodException("SecurityException buscando el método", methodName, clazz, e); } catch (NullPointerException e) { throw new MethodException("method name or class are null", methodName, clazz, e); } } private static Method guessMethod(Class clazz, String methodName, List> classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException{ List validMethods = getAllMethodMatching(clazz, methodName, classes); if(validMethods.isEmpty()) throw new NonExistentMethodException("El método no existe", methodName, clazz); Method a = getBestMatch(validMethods, classes); if(a == null) throw new MethodException("error extraño. Hay métodos válidos pero getBestMatch no devolvió ninguno"); return a; } /** * Returns the closest method to the given arguments. If you methods have the same distance, first one in the list * has precedence. * * @param clazz Class representing the type where the method is declared * @param methodName name of the method * @param classes parameters minimal class types * @return all the methods compatible with given param types * @throws SecurityException * @throws NullPointerException */ private static List getAllMethodMatching(Class clazz, String methodName, List> classes) throws SecurityException, NullPointerException{ Method[] methods = clazz.getMethods(); List validMethods = new ArrayList(); int numArgs; if(classes == null) numArgs = 0; else numArgs = classes.size(); for(Method method : methods) { if(!method.getName().equals(methodName)) continue; if(method.getParameterCount() != numArgs) continue; // Ignoring variable arity Class[] types = method.getParameterTypes(); if(matches(types, classes)) { validMethods.add(method); } } return validMethods; } /** * Returns the closest method to the given arguments. If you methods have the same distance, first one in the list * has precedence. * * @param validMethods list of possible methods * @param classes types of the arguments * @return the best match */ private static Method getBestMatch(List validMethods, List> classes) throws AmbiguousMethodException { if(validMethods.size() == 1) return validMethods.get(0); int distance = Integer.MAX_VALUE; Method currMethod = null; List ambiguous = new ArrayList(); outer_loop: for(Method method : validMethods) { Class[] methodTypes = method.getParameterTypes(); int methodDistance = 0; for(int i=0; i < methodTypes.length; i++) { if(!AssignableUtils.isAssignableFrom(methodTypes[i], classes.get(i))) continue outer_loop; // Incompatible. Should not happen, but just in case methodDistance += classDistance(methodTypes[i], classes.get(i)); } if( methodDistance < distance) { currMethod = method; distance = methodDistance; ambiguous.clear(); } else if(methodDistance == distance) { ambiguous.add(method); } } if(!ambiguous.isEmpty()) { ambiguous.add(currMethod); try { ambiguous = reduceAmbiguous(ambiguous, classes); } catch(RuntimeException e) { throw new AmbiguousMethodException("exception trying to dissambiguate", currMethod, currMethod.getDeclaringClass(), e); } ambiguous = reduceAmbiguous(ambiguous, classes); if(ambiguous.size() > 1) throw new AmbiguousMethodException("multiple methods found", currMethod, currMethod.getDeclaringClass()); currMethod = ambiguous.get(0); } return currMethod; } private static List reduceAmbiguous(List methods, List> classes) { List finalList = new ArrayList(); List discarded = new ArrayList(); outer_loop: for(int i=0; i < methods.size(); i++) { if(discarded.contains(i)) continue; Method m1 = methods.get(i); for(int j=i+1; j < methods.size(); j++) { if(discarded.contains(j)) continue; Method m2 = methods.get(j); if(overridenWithDifferentReturn(m1,m2)) { Method concrete = getMostConcrete(m1, m2); if(concrete == null) continue; // Unknown if(concrete == m1) { // m1 is more concrete. Candidate to be added in this batch discarded.add(j); continue; // m1 is more concrete. Candidate to be added in this batch } // m2 is closer. m1 should never be added. We'll see later if m2 has a more concrete method or what. continue outer_loop; }else if(onlyPrimitivesDifferent(m1,m2)) { Method concrete = getClosestPrimitives(m1, m2, classes); if(concrete == null) continue; // Unknown if(concrete == m1) { // m1 is more concrete. Candidate to be added in this batch discarded.add(j); continue; // m1 is more concrete. Candidate to be added in this batch } // m2 is closer. m1 should never be added. We'll see later if m2 has a more concrete method or what. continue outer_loop; } } finalList.add(m1); } return finalList; } /** * Returns true if m2 overrides m1 but has a different return type * @param m1 first method * @param m2 second method * @return true is m2 overrides m1 with different return type * @throws IndexOutOfBoundsException if m1 has a different number of arguments than m2 */ public static boolean overridenWithDifferentReturn(Method m1, Method m2) throws IndexOutOfBoundsException{ Class rt1 = m1.getReturnType(); Class rt2 = m1.getReturnType(); if(rt1.equals(rt2)) return false; Class[] p1 = m1.getParameterTypes(); Class[] p2 = m2.getParameterTypes(); if(p1.length != p2.length) return false; for(int i=0; i< p1.length; i++) { if(!p1.equals(p2)) return false; } return true; } private static Method getMostConcrete(Method m1, Method m2) throws IllegalArgumentException { Class d1 = m1.getDeclaringClass(); Class d2 = m2.getDeclaringClass(); if(d1.equals(d2)) throw new IllegalArgumentException("both methods declared in same class"); if(d1.isAssignableFrom(d2)) { // d2 is more concrete return m2; } if(d2.isAssignableFrom(d1)) { // d2 is more concrete return m1; } throw new IllegalArgumentException("methods don't seem to belong to same class"); } private static boolean onlyPrimitivesDifferent(Method m1, Method m2) { Class[] p1 = m1.getParameterTypes(); Class[] p2 = m2.getParameterTypes(); if(p1.length != p2.length) return false; boolean atleastOne = false; for(int i=0; i< p1.length; i++) { if(p1[i].equals(p2[i])) continue; if(!AssignableUtils.isPrimitiveAndWrapper(p1[i], p2[i])) return false; atleastOne = true; } return atleastOne; } private static Method getClosestPrimitives(Method m1, Method m2, List> classes) throws IllegalArgumentException{ Class[] p1 = m1.getParameterTypes(); Class[] p2 = m2.getParameterTypes(); if(p1.length != p2.length) throw new IllegalArgumentException("incompatible methods"); int p1Fails = 0; int p2Fails = 0; for(int i=0; i< p1.length; i++) { if(!AssignableUtils.isPrimitiveAndWrapper(p1[i], p2[i])) continue; if(p1[i].isAssignableFrom(classes.get(i))) { p2Fails ++; } p1Fails ++; } if(p1Fails < p2Fails) { return m1; } if(p2Fails < p1Fails) { return m2; } return null; // Cannot dissambiguate } /** *

Calculates the distance between two classes. The distance is applied as follows: * *

If parent and children are the same instance, or one is a primitive wrapper of the other then the distance is 0. * If not, this methods calls classDistance recursively on children superclass * and all directly implemented interfaces. The distance will be the smalles positive distance * plus one. * * @param parent the parent class. It must be a superinterface, a superclass of or the same than children * @param children the children class. It must be a subclass of, implement interface or be the same than parent Class * @return calculated distance. IT is allways zero or positive * @throws IllegalArgumentException If children is not assignable to father */ private static int classDistance(Class parent, Class children) throws IllegalArgumentException{ if(parent.equals(children)) return 0; if(!AssignableUtils.isAssignableFrom(parent, children)) throw new IllegalArgumentException("children is not assignable to father"); // Should do b4 equals? if(AssignableUtils.isPrimitiveAndWrapper(parent, children)) return 0; Integer minDistance = null; Class superClass = children.getSuperclass(); if(superClass != null && parent.isAssignableFrom(superClass)) { minDistance = classDistance(parent, superClass); } for(Class directInterface : children.getInterfaces()) { if(!AssignableUtils.isAssignableFrom(parent, directInterface)) continue; int interfaceDistance = classDistance(parent, directInterface); if(minDistance == null || interfaceDistance < minDistance) minDistance = interfaceDistance; } if(minDistance == null) throw new IllegalArgumentException("we found no distance. this is an odd behaviour and definetly a bug, or means this method is not well-thought at all"); return minDistance + 1; } /** *

Search for methods declared in clazz or any of it's superclases by using the instances of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * *

This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param instances list of instances that will be passed as arguments to the method later. This does not invoke the method, but guarantees that instances will be compatible * @return Method matching the given parametters. Guaranteed to be not null. * @throws NullArgument if one of the instances passed is null * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Constructor searchConstructorByParamInstances(Class clazz, List instances) throws NullArgument, NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { return searchConstructorByParamClasses(clazz, genListOfClasses(instances)); } /** *

Search for methods declared in clazz or any of it's superclases by using the Class objects of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments inside classes list, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * *

This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param classes types of the arguments * @return Method matching the given parametters. Guaranteed to be not null. * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Constructor searchConstructorByParamClasses(Class clazz, List classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { try { if(classes!=null && !classes.isEmpty()) { Class[] arrayClaseDummy = {}; Class[] arrayClases = classes.toArray(arrayClaseDummy); return clazz.getConstructor(arrayClases); }else { return clazz.getConstructor(); } } catch (NoSuchMethodException e) { throw new NonExistentMethodException("El método no existe", "constructor", clazz, e); } catch (SecurityException e) { throw new ForbiddenMethodException("SecurityException buscando el método", "constructor", clazz, e); } catch (RuntimeException e) { throw new MethodException("Excepción no controlada buscando el método", "constructor", clazz, e); } } /** *

Search for methods declared in clazz or any of it's superclases by using the instances of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * *

This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param instances list of instances that will be passed as arguments to the method later. This does not invoke the method, but guarantees that instances will be compatible * @return Method matching the given parametters. Guaranteed to be not null. * @throws NullArgument if one of the instances passed is null * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Constructor searchConstructorByParamInstancesOrSuper(Class clazz, List instances) throws NullArgument, AmbiguousMethodException, NonExistentMethodException, ForbiddenMethodException, MethodException { return searchConstructorByParamClassesOrSuper(clazz, genListOfClasses(instances)); } /** *

Search for methods declared in clazz or any of it's superclases by using the Class objects of the parameters we * want to pass as a search template. This search DOES NOT allow null arguments inside classes list, and will throw NullArgument in case * one of the instances of the list is null, however, it allows null or empty lists (will call constructor without arguments) * *

This does not invoke the method, but guarantees that instances will be compatible * * @param clazz class to search declared methods * @param classes minimal class of the arguments * @return Method matching the given parametters. Guaranteed to be not null. * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching */ public static Constructor searchConstructorByParamClassesOrSuper(Class clazz, List> classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException { try { if(classes!=null && !classes.isEmpty()) { Class[] arrayClaseDummy = {}; Class[] arrayClases = classes.toArray(arrayClaseDummy); return clazz.getConstructor(arrayClases); }else { return clazz.getConstructor(); } } catch (NoSuchMethodException e) { if(classes == null || classes.isEmpty()) throw new NonExistentMethodException("El constructor sin argumentos no existe", "constructor", clazz, e); return guessConstructor(clazz, classes); } catch (SecurityException e) { throw new ForbiddenMethodException("SecurityException buscando el constructor", "constructor", clazz, e); } catch (NullPointerException e) { throw new MethodException("class is null", "constructor", clazz, e); } } private static Constructor guessConstructor(Class clazz, List> classes) throws NonExistentMethodException, AmbiguousMethodException, ForbiddenMethodException, MethodException{ List> validMethods = getAllConstructorsMatching(clazz, classes); if(validMethods.isEmpty()) throw new NonExistentMethodException("El constructor solicitado no existe", null, clazz); Constructor a = getBestConstructorMatch(validMethods, classes); if(a == null) throw new MethodException("error extraño. Hay métodos válidos pero getBestMatch no devolvió ninguno"); return a; } /** * Returns the closest method to the given arguments. If you methods have the same distance, first one in the list * has precedence. * * @param validMethods * @param classes * @return */ private static List> getAllConstructorsMatching(Class clazz, List> classes) throws SecurityException, NullPointerException{ @SuppressWarnings("unchecked") Constructor[] methods = (Constructor[]) clazz.getConstructors(); // This is actually safe. List> validMethods = new ArrayList>(); int numArgs; if(classes == null) numArgs = 0; else numArgs = classes.size(); for(Constructor method : methods) { if(method.getParameterCount() != numArgs) continue; // Ignoring variable arity Class[] types = method.getParameterTypes(); if(matches(types, classes)) { validMethods.add(method); } } return validMethods; } /** * Returns the closest method to the given arguments. If you methods have the same distance, first one in the list * has precedence. * * @param validMethods * @param classes * @return */ private static Constructor getBestConstructorMatch(List> validMethods, List> classes) throws AmbiguousMethodException { if(validMethods.size() == 1) return validMethods.get(0); int distance = Integer.MAX_VALUE; Constructor currMethod = null; List> ambiguous = new ArrayList>(); outer_loop: for(Constructor method : validMethods) { Class[] methodTypes = method.getParameterTypes(); int methodDistance = 0; for(int i=0; i < methodTypes.length; i++) { if(!methodTypes[i].isAssignableFrom(classes.get(i))) continue outer_loop; // Incompatible. Should not happen, but just in case methodDistance += classDistance(methodTypes[i], classes.get(i)); } if( methodDistance < distance) { currMethod = method; distance = methodDistance; ambiguous.clear(); } else if(methodDistance == distance) { ambiguous.add(method); } } if(!ambiguous.isEmpty()) { ambiguous.add(currMethod); try { ambiguous = reduceAmbiguousConstructor(ambiguous, classes); } catch(RuntimeException e) { throw new AmbiguousMethodException("exception trying to dissambiguate", "constructor", currMethod.getDeclaringClass(), e); } if(ambiguous.size() > 1) throw new AmbiguousMethodException("multiple constructors found", null); currMethod = ambiguous.get(0); } return currMethod; } private static List> reduceAmbiguousConstructor(List> methods, List> classes) { List> finalList = new ArrayList>(); List discarded = new ArrayList(); outer_loop: for(int i=0; i < methods.size(); i++) { if(discarded.contains(i)) continue; Constructor m1 = methods.get(i); for(int j=i+1; j < methods.size(); j++) { if(discarded.contains(j)) continue; Constructor m2 = methods.get(j); if(onlyPrimitivesDifferent(m1,m2)) { Constructor concrete = getClosestPrimitives(m1, m2, classes); if(concrete == null) continue; // Unknown if(concrete == m1) { // m1 is more concrete. Candidate to be added in this batch discarded.add(j); continue; // m1 is more concrete. Candidate to be added in this batch } // m2 is closer. m1 should never be added. We'll see later if m2 has a more concrete method or what. continue outer_loop; } } finalList.add(m1); } return finalList; } private static boolean onlyPrimitivesDifferent(Constructor m1, Constructor m2) { Class[] p1 = m1.getParameterTypes(); Class[] p2 = m2.getParameterTypes(); if(p1.length != p2.length) return false; boolean atleastOne = false; for(int i=0; i< p1.length; i++) { if(p1[i].equals(p2[i])) continue; if(!AssignableUtils.isPrimitiveAndWrapper(p1[i], p2[i])) return false; atleastOne = true; } return atleastOne; } private static Constructor getClosestPrimitives(Constructor m1, Constructor m2, List> classes) throws IllegalArgumentException{ Class[] p1 = m1.getParameterTypes(); Class[] p2 = m2.getParameterTypes(); if(p1.length != p2.length) throw new IllegalArgumentException("incompatible methods"); int p1Fails = 0; int p2Fails = 0; for(int i=0; i< p1.length; i++) { if(!AssignableUtils.isPrimitiveAndWrapper(p1[i], p2[i])) continue; if(p1[i].isAssignableFrom(classes.get(i))) { p2Fails ++; } p1Fails ++; } if(p1Fails < p2Fails) { return m1; } if(p2Fails < p1Fails) { return m2; } return null; // Cannot dissambiguate } /** *

Returns true if every element of methodTypes is assignable from the element placed in the same index of arguments * Assumes length of both parameters is the same, and that none of them is null. * Empty lists allowed, but you must ensure they have the same length BEFORE passing them to this function, or the * matching will not be accurate. *

* Accepts null parameters ONLY if both are null or empty. If you provide a non-empty methodTypes and a null arguments, it will * throw NullPointerException and vice-versa *

* IF both lists are non-null but different size, IllegalArgumentException will be thrown * * @param methodParamTypes the actual types declared for the method * @param arguments the desired types * @return true if matches, false otherwise * @throws IllegalArgumentException If the length of both lists is not the same * @throws NullPointerException If only one of the parameters is null and the other is not empty */ private static boolean matches(Class[] methodParamTypes, List> arguments) throws NullPointerException, IllegalArgumentException{ if((arguments == null || arguments.isEmpty()) && (methodParamTypes == null || methodParamTypes.length == 0)) return true; if(methodParamTypes.length != arguments.size()) throw new IllegalArgumentException("diferent list lengths passed to matches"); for(int i=0; i < methodParamTypes.length; i++) { if(!AssignableUtils.isAssignableFrom(methodParamTypes[i], arguments.get(i))) return false; } return true; } /** * Gets the return type of the matching method * * @param clazz class to search declared methods * @param methodName name of the method to search * @return the return type of the matching method * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching * @throws AmbiguousMethodException if there is more thn one method with the same matching score * @throws NullType * @throws NullArgument */ public static Class getReturnTypeMethod(Class clazz, String methodName) throws MethodException, NullType, NullArgument { return getReturnTypeMethod(clazz, methodName, null); } /** * Gets the return type of the matching method * * @param clazz class to search declared methods * @param methodName name of the method to search * @param parameterTypes the parameters of the methods * @return the return type of the matching method * @throws NonExistentMethodException If there's no method matching the requeriments * @throws ForbiddenMethodException If there's no access to the found method. * @throws MethodException If there's a RuntimeException searching * @throws AmbiguousMethodException if there is more thn one method with the same matching score * @throws NullType * @throws NullArgument * @see #searchMethodByParamClassesOrSuper(Class, String, List) */ public static Class getReturnTypeMethod(Class clazz, String methodName, List> parameterTypes) throws MethodException, AmbiguousMethodException, NullType, NullArgument { if(clazz==null) throw new NullType("No se porporcionó ninguna clase a getTipoMetodo"); if(methodName==null) throw new NullArgument("No se porporcionó ninguna nombre de método"); Method metodo = searchMethodByParamClassesOrSuper(clazz, methodName, parameterTypes); if(metodo == null) return null; return metodo.getReturnType(); } /** * Reads the value into the provided field of the given instance * * @param field field to read * @param instance the instance to write the field in * @param forceAccess true to force accesss if field is not public * @return the value stored in the field * @throws PropertyException If PropertyUtils.getProperty throws an exception * @throws NullObject if instance is null * @throws NullArgument if field is null */ public static Object readField(Field field, Object instance, boolean forceAccess) throws PropertyException, NullArgument, NullObject { if(instance==null) throw new NullObject("Se proporcionó un origen nulo para leer atributo"); if(field==null) throw new NullArgument("Se proporcionó un campo nulo para leer atributo"); try { return field.get(instance); } catch (IllegalArgumentException e) { throw new PropertyException("Se proporcionó un campo para leer que no pertenece a la clase", field.toString(), instance.getClass(), e); } catch (IllegalAccessException e) { if(!forceAccess) throw new PropertyException("El campo para leer no es visible y no se permite forzar acceso", field.toString(), instance.getClass(), e); try { field.setAccessible(true); return field.get(instance); } catch(SecurityException e1) { throw new PropertyException("El campo para leer no es visible y no se ha podido forzar el acceso", field.toString(), instance.getClass(), e1); } catch (IllegalArgumentException e1) { throw new PropertyException("Se proporcionó un campo para leer que no pertenece a la clase", field.toString(), instance.getClass(), e1); } catch (IllegalAccessException e1) { throw new PropertyException("Campo para leer no accesible tras forzar acceso. Raro", field.toString(), instance.getClass(), e1); } catch (ExceptionInInitializerError e1) { throw new PropertyException("ExceptionInInitializerError leyendo atributo", field.toString(), instance.getClass(), e1); } catch (RuntimeException e1) { throw new PropertyException("Excepción no controlada leyendo atributo", field.toString(), instance.getClass(), e1); } } catch (ExceptionInInitializerError e) { throw new PropertyException("ExceptionInInitializerError leyendo atributo", field.toString(), instance.getClass(), e); } catch (RuntimeException e) { throw new PropertyException("Excepción no controlada leyendo atributo", field.toString(), instance.getClass(), e); } } /** * Stores a value into the provided field of the given instance * * @param field field to write * @param instance the instance to write the field in * @param value the value * @param forceAccess true to force accesss if field is not public * @throws ForbiddenFieldException field is not accessible * @throws PropertyException Si PropertyUtils.getProperty lanza alguna excepción * @throws IncompatibleType value does not match the field type * @throws NullObject instance is null * @throws NullArgument field is null */ public static void writeField(Field field, Object instance, Object value, boolean forceAccess) throws ForbiddenFieldException, PropertyException, IncompatibleType, NullObject, NullArgument { if(instance==null) throw new NullObject("Se proporcionó un destino nulo para escribir atributo."); if(field==null) throw new NullObject("Se proporcionó un campo nulo como destino de escritura"); try { field.set(instance, value); return; } catch (IllegalArgumentException e) { if(value!=null) { Class fldType = field.getType(); if(!fldType.isAssignableFrom(value.getClass())) throw new IncompatibleType("Se proporcionó un valor para almacenar en un campo cuyo tipo no es compatible. Atributo: "+field.toString(), fldType, value.getClass(), e); } throw new PropertyException("Se proporcionó un campo para escribir que no pertenece a la clase", field.toString(), instance.getClass(), e); } catch (IllegalAccessException e) { if(!forceAccess) throw new PropertyException("El campo para escribir no es visible y no se permite forzar acceso", field.toString(), instance.getClass(), e); try { field.setAccessible(true); field.set(instance, value); return; } catch(SecurityException e1) { throw new ForbiddenFieldException("El campo para escribir no es visible y no se ha podido forzar el acceso", field.toString(), instance.getClass(), e1); } catch (IllegalArgumentException e1) { Class tipoCampo = field.getType(); if(value!=null) { if(!tipoCampo.isAssignableFrom(value.getClass())) throw new IncompatibleType("Se proporcionó un valor para almacenar en un campo cuyo tipo no es compatible. Atributo: "+field.toString(), tipoCampo, value.getClass(), e1); } throw new PropertyException("Se proporcionó un campo para escribir que no pertenece a la clase", field.toString(), instance.getClass(), e1); } catch (IllegalAccessException e1) { throw new ForbiddenFieldException("Campo para escribir no accesible tras forzar acceso. Raro", field.toString(), instance.getClass(), e1); }catch (ExceptionInInitializerError e1) { throw new PropertyException("ExceptionInInitializerError escribiendo atributo", field.toString(), instance.getClass(), e1); } catch (RuntimeException e1) { throw new PropertyException("Excepción no controlada escribiendo atributo", field.toString(), instance.getClass(), e1); } } catch (ExceptionInInitializerError e) { throw new PropertyException("ExceptionInInitializerError escribiendo atributo", field.toString(), instance.getClass(), e); } catch (RuntimeException e) { throw new PropertyException("Excepción no controlada escribiendo atributo", field.toString(), instance.getClass(), e); } } /** * https://github.com/dancerjohn/LibEx/blob/master/libex/src/main/java/org/libex/reflect/ReflectionUtils.java * MIT License Copyright (c) 2017 John Joseph Butler * * @param type class to search in * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return array of fields matching * @throws NullPointerException */ public static Field[] getFieldsUpTo(Class type, Class exclusiveParent) throws NullPointerException { Field[] result = type.getDeclaredFields(); Class parentClass = type.getSuperclass(); if (parentClass != null && (exclusiveParent == null || !parentClass.equals(exclusiveParent))) { Field[] parentClassFields = getFieldsUpTo(parentClass, exclusiveParent); result = concat(result, parentClassFields, Field.class); } return result; } /** * https://github.com/dancerjohn/LibEx/blob/master/libex/src/main/java/org/libex/reflect/ReflectionUtils.java * MIT License Copyright (c) 2017 John Joseph Butler * * @param type class to search in * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return array of methods matching * @throws NullPointerException */ public static Method[] getMethodsUpTo(Class type, Class exclusiveParent) throws NullPointerException { Method[] result = type.getDeclaredMethods(); Class parentClass = type.getSuperclass(); if (parentClass != null && (exclusiveParent == null || !parentClass.equals(exclusiveParent))) { Method[] parentClassFields = getMethodsUpTo(parentClass, exclusiveParent); result = concat(result, parentClassFields, Method.class); } return result; } /** * https://github.com/dancerjohn/LibEx/blob/master/libex/src/main/java/org/libex/reflect/ReflectionUtils.java * MIT License Copyright (c) 2017 John Joseph Butler * * @param type class to search in * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return list of fields with annotations */ public static List getAnnotatedFieldsUpTo(Class type, Class annotation, Class exclusiveParent) throws NullPointerException { Field[] result = getFieldsUpTo(type, exclusiveParent); List finalList = new ArrayList(); for(Field method : result) { if(!method.isAnnotationPresent(annotation)) continue; finalList.add(method); } //return finalList.toArray(dummyArrayField); return finalList; } /** * https://github.com/dancerjohn/LibEx/blob/master/libex/src/main/java/org/libex/reflect/ReflectionUtils.java * MIT License Copyright (c) 2017 John Joseph Butler * * @param type class to search in * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return methods with annotations * @throws NullPointerException if type or annotation are null */ public static List getAnnotatedMethodsUpTo(Class type, Class annotation, Class exclusiveParent) throws NullPointerException { Method[] result = getMethodsUpTo(type, exclusiveParent); List finalList = new ArrayList(); for(Method method : result) { if(!method.isAnnotationPresent(annotation)) continue; finalList.add(method); } //return finalList.toArray(dummyArrayMethod); return finalList; } /** *

Returns a {@code Field} object that reflects the specified public member * field of the class or interface represented by this {@code Class} * object. The {@code name} parameter is a {@code String} specifying the * simple name of the desired field. * *

The field to be reflected is determined by the algorithm that * follows. Let C be the class or interface represented by this object: * *

    *
  1. If C declares a public field with the name specified, that is the * field to be reflected.
  2. *
  3. If no field was found in step 1 above, this algorithm is applied * recursively to each direct superinterface of C. The direct * superinterfaces are searched in the order they were declared.
  4. *
  5. If no field was found in steps 1 and 2 above, and C has a * superclass S, then this algorithm is invoked recursively upon S. * If C has no superclass, then a {@code NoSuchFieldException} * is thrown.
  6. *
* *

If this {@code Class} object represents an array type, then this * method does not find the {@code length} field of the array type. * *

This docstring is copied from original Java * {@link Class#getField(String)} method. * * @param clazz {@code Class} object that represents the class to search * fields in. * @param name the field name * @return the {@code Field} object of this class specified by * {@code name} * * @throws NonExistentFieldException If the field doesn't exist. * @throws ForbiddenFieldException If SecurityException is thrown accesing the field. * @throws PropertyException If a RuntimeException is thrown by {@link Class#getField(String)} * @throws NullType if {@code clazz} is {@code null} * @throws NullArgument if {@code name} is {@code null} * @see Class#getField(String) */ public static Field searchPublicField(Class clazz, String name) throws NonExistentFieldException, ForbiddenFieldException, PropertyException, NullType, NullArgument{ if(clazz==null) throw new NullType("Se proporcionó una clase null a buscarAtributo."); if(name==null) throw new NullArgument("Se proporcionó un atributo null a buscarAtributo."); try { return clazz.getField(name); } catch (NoSuchFieldException e) { throw new NonExistentFieldException("No existe un atributo público", name, clazz, e); } catch (SecurityException e) { throw new ForbiddenFieldException("No se tiene acceso al atributo solicitado", name, clazz, e); } catch (RuntimeException e) { // Should never happen. Just in case. throw new PropertyException("Excepción no controlada buscando atributo", name, clazz, e); } } /** *

Returns a {@code Field} object that reflects the specified atribute * of the class or interface represented by this {@code Class} object. * The {@code name} parameter is a {@code String} specifying the * simple name of the desired field. * *

The field to be reflected is determined by the algorithm that * follows. Let C be the class or interface represented by this object: * *

    *
  1. If {@link #searchPublicField(Class, String)} returns a Field, * that is the Field to be returned
  2. *
  3. If no field was found in step 1 above and allowPrivate is {@code true}, * it returns the result of {@link #searchFieldAllowsPrivate(Class, String, Class)} .
  4. *
  5. If no field was found in steps 1 and 2 above, then a * {@code PropertyException} is thrown.
  6. *
* *

If this {@code Class} object represents an array type, then this * method does not find the {@code length} field of the array type. * * * @param clazz class to search in * @param field name of the field * @param allowPrivate true to allow private and protected fields, false otherwise * @param forceAccess if true, will make field accessible if it is not accessible * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return the field * @throws NonExistentFieldException If the field doesn't exist. * @throws ForbiddenFieldException If SecurityException is thrown accesing the field. * @throws PropertyException If a RuntimeException is thrown in getDeclaredField * @throws NullType si la clase proporcionada es null * @throws NullArgument si el nombre de la property es nulo */ public static Field searchField(Class clazz, String field, boolean allowPrivate, boolean forceAccess, Class exclusiveParent) throws NonExistentFieldException, ForbiddenFieldException, PropertyException, NullType, NullArgument { // TODO: Improve efficiency. Exception and repeated search overhead. Calling searchPublicField first and then searchFieldAllowsPrivate produces overhead if searchPublicField fails. try { return searchPublicField( clazz, field); } catch (NullType | NullArgument e) { throw e; }catch (ReflectionException | ReflectionRuntimeException e) { if(!allowPrivate) throw e; } Field campo = searchFieldAllowsPrivate(clazz, field, exclusiveParent); try { if(forceAccess) campo.setAccessible(true); return campo; } catch(SecurityException e) { throw new PropertyException("Imposible modificar el nivel de acceso de un atributo", field, clazz, e); } catch(RuntimeException e) { throw new PropertyException("Excepción no controlada modificando nivel de acceso de atributo", field, clazz, e); } } /** *

Search for all the possible getters of a class. * *

A getter is constructed using the field name, setting the first letter to uppercase and appending that to get or is (in case of booleans). * *

Example for a String field: * * simpleString maps to getSimpleString * *

Example for boolean field: * * running maps to isRunning OR getRunning * *

Getters MUST have a no arguments * * @param clazz class to search in * @param field original field name. * @param allowNonPublic if false, methods that are not accesible won't be included * @param forceAccess if true, will mark not accesible methods as accesible * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return list of getters * @throws PropertyException if it is impossible to make a method accesible * @throws NullType if clazz is null * @throws NullArgument if field is null or empty or composed solely on empty spaces */ public static List getGettersUpTo(Class clazz, String field, boolean allowNonPublic, boolean forceAccess, Class exclusiveParent) throws PropertyException, NullType, NullArgument { if(clazz==null) throw new NullType("null class provided."); if(field==null) throw new NullArgument("null field name provided."); field = field.trim(); if(field.isEmpty()) throw new NullArgument("empty field name provided."); String capitalized = field.substring(0,1).toUpperCase() + field.substring(1); String isGetter = "is" + capitalized; String getGetter = "get" + capitalized; List methods = new ArrayList(); Method[] allMethods = getMethodsUpTo(clazz, exclusiveParent); for(Method candidate : allMethods) { if(candidate.getParameterCount() > 0) continue; if(!allowNonPublic && !Modifier.isPublic(candidate.getModifiers())) continue; if(candidate.getName().equals(getGetter)) { methods.add(candidate); } else if(candidate.getName().equals(isGetter) && AssignableUtils.isAssignableFrom(Boolean.class, candidate.getReturnType())) { methods.add(candidate); } else { continue; } try { if(forceAccess && !candidate.isAccessible()) candidate.setAccessible(true); } catch(SecurityException e) { throw new PropertyException("Imposible modificar el nivel de acceso de un atributo", field, clazz, e); } catch(RuntimeException e) { throw new PropertyException("Excepción no controlada modificando nivel de acceso de atributo", field, clazz, e); } } return methods; } /** *

Search for all the possible setters of a class. * *

A getter is constructed using the field name, setting the first letter to uppercase and appending that to set * *

Example for a String field: * * simpleString maps to setSimpleString * *

Setters MUST have a single argument. * * @param clazz class to search in * @param field original field name. * @param allowNonPublic if false, methods that are not accesible won't be included * @param forceAccess if true, will mark not accesible methods as accesible * @param exclusiveParent last superclass to search. Will not search for methods in higher classes * @return This method may return multiple setters (overriden, with distinct types, etc) * @throws PropertyException if it is impossible to make a method accesible * @throws NullType if clazz is null * @throws NullArgument if field is null or empty or composed solely on empty spaces */ public static List getSettersUpTo(Class clazz, String field, boolean allowNonPublic, boolean forceAccess, Class exclusiveParent) throws PropertyException, NullType, NullArgument { if(clazz==null) throw new NullType("null class provided."); if(field==null) throw new NullArgument("null field name provided."); field = field.trim(); if(field.isEmpty()) throw new NullArgument("empty field name provided."); String capitalized = field.substring(0,1).toUpperCase() + field.substring(1); String setter = "set" + capitalized; List methods = new ArrayList(); Method[] allMethods = getMethodsUpTo(clazz, exclusiveParent); for(Method candidate : allMethods) { if(candidate.getParameterCount() != 1) continue; if(!allowNonPublic && !Modifier.isPublic(candidate.getModifiers())) continue; if(!candidate.getName().equals(setter)) { continue; } methods.add(candidate); try { if(forceAccess && !candidate.isAccessible()) candidate.setAccessible(true); } catch(SecurityException e) { throw new PropertyException("Imposible modificar el nivel de acceso de un atributo", field, clazz, e); } catch(RuntimeException e) { throw new PropertyException("Excepción no controlada modificando nivel de acceso de atributo", field, clazz, e); } } return methods; } /** * Recursive version of {@link Class#getDeclaredField(String)}. * * @param clazz class to search in * @param fieldName field name. * @param exclusiveParent last parent. No superclasses will be searched in * @return the field * @throws NonExistentFieldException If the field doesn't exist. * @throws ForbiddenFieldException If SecurityException is thrown accesing the field. * @throws PropertyException If a RuntimeException is thrown in getDeclaredField * @throws NullType si la clase proporcionada es null * @throws NullArgument si el nombre de la property es nulo * @see Class.getDeclaredField */ private static Field searchFieldAllowsPrivate(Class clazz, String fieldName, Class exclusiveParent) throws NonExistentFieldException, ForbiddenFieldException, PropertyException, NullType, NullArgument { if(clazz==null) throw new NullType("Se proporcionó una clase null a buscarAtributo. Atributo: "+fieldName); if(fieldName==null) throw new NullArgument("Se proporcionó un atributo null a buscarAtributo. Clase: "+clazz); try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { Class parent = clazz.getSuperclass(); if (parent != null && (exclusiveParent == null || !parent.equals(exclusiveParent))) { return searchFieldAllowsPrivate(parent, fieldName, exclusiveParent); } throw new NonExistentFieldException("No existe el atributo", fieldName, clazz, e); } catch (SecurityException e) { // TODO: Should throw SecurityException directly? Class padre = clazz.getSuperclass(); if (padre != null && (exclusiveParent == null || !padre.equals(exclusiveParent))) { return searchFieldAllowsPrivate(padre, fieldName, exclusiveParent); } throw new ForbiddenFieldException("No hay acceso al atributo", fieldName, clazz, e); } catch (RuntimeException e) { throw new PropertyException("Excepción no controlada buscando atributo", fieldName, clazz, e); } } /** * Generates a list of classes from the provided list of instances.
* It does NOT accept null arguments. * * @param args list of instances * @return List of classes or null if args is null. * @throws NullArgument if one element of args is null */ protected static List> genListOfClasses(List args) throws NullArgument { if(args==null) return null; List> listaClases = new ArrayList>(); for(Object argumento : args) { if(argumento==null) throw new NullArgument("No se permiten argumentos nulos si no se especifica el tipo del mismo. Utiliza la función que proporciona una lista de clases en su lugar."); listaClases.add(argumento.getClass()); } return listaClases; } protected static List> genListOfClasses(List> classes, List instances) throws NullArgument, IllegalArgumentException { if(classes == null || classes.isEmpty()) return genListOfClasses(instances); if(classes.size() != instances.size()) throw new IllegalArgumentException("different classes and instance size."); return classes; } /** * Basic array concatenation * * @param Tipo del nuevo array * @param first Primer array a concatenar * @param last Segundo array a concatenbar * @param type * @return * @throws NullPointerException Si alguno de los argumentos es null */ private static T[] concat(T[] first, T[] last, Class type) throws NullPointerException { @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(type, first.length + last.length); System.arraycopy(first, 0, result, 0, first.length); System.arraycopy(last, 0, result, first.length, last.length); return result; } }