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

org.nuiton.util.ReflectUtil Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/*
 * #%L
 * Nuiton Utils
 * %%
 * Copyright (C) 2004 - 2010 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.nuiton.i18n.I18n.t;

/**
 * Introspection sur une classe. Détermine si un attribut est constant,
 * recherche de constantes d'un type donné, conversion d'une classe en
 * Enum...
 *
 * Created: 30 décembre 2007
 *
 * @author Tony Chemit - [email protected]
 */
public class ReflectUtil {


    /** Logger */
    private static final Log log = LogFactory.getLog(ReflectUtil.class);

    /**
     * Pour déterminer si un champ d'une classe est une constante
     * (modifiers sont static, final et public)
     *
     * @param field le champs à tester
     * @return {@code true} si les modifiers sont final, static et public
     */
    public static boolean isConstantField(Field field) {
        int modifiers = field.getModifiers();
        return Modifier.isPublic(modifiers) &&
               Modifier.isStatic(modifiers) &&
               Modifier.isFinal(modifiers);
    }

    /**
     * Recherche dans une classe donnée {@code klazz}, les constantes d'un
     * certain type {@code searchingClass} et les retourne.
     *
     * L'algorithme parcourt aussi les superclasses.
     *
     * @param             enumeration's type
     * @param klass          la classe contenant les constantes
     * @param searchingClass le type des champs constants à récupérer
     * @return la liste des champs du type requis dans
     * @throws RuntimeException si problème lors de la récupération
     */
    @SuppressWarnings({"unchecked"})
    public static  List getConstants(Class klass,
                                           Class searchingClass) {
        List result = new ArrayList();
        for (Field field : klass.getDeclaredFields()) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            if (searchingClass.isAssignableFrom(field.getType()) &&
                isConstantField(field)) {
                try {
                    result.add((T) field.get(null));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        Class superClass = klass.getSuperclass();
        if (superClass != null) {
            result.addAll(getConstants(superClass, searchingClass));
        }
        return result;
    }

    /**
     * @param        enumeration's type
     * @param klass     the required class
     * @param fieldName the required constant name
     * @return the constant value
     */
    @SuppressWarnings({"unchecked"})
    public static  T getConstant(Class klass, String fieldName) {
        try {
            T result = null;
            Field f = klass.getDeclaredField(fieldName);
            if (isConstantField(f)) {
                f.setAccessible(true);
                result = (T) f.get(null);
            }
            return result;
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Convertit une classe non typée, en une classe d'enum
     *
     * @param   enumeration's type
     * @param type la classe a typer
     * @return la classe typee
     * @throws IllegalArgumentException si le type est null ou non une extension
     *                                  de la classe Enum.
     */
    @SuppressWarnings({"unchecked"})
    public static > Class getEnumClass(Class type) throws IllegalArgumentException {
        if (type == null) {
            throw new IllegalArgumentException(t("nuitonutil.error.null.parameter", "type"));
        }
        if (!type.isEnum()) {
            throw new IllegalArgumentException(t("nuitonutil.error.not.an.enum", type));
        }
        return (Class) type;
    }

    /**
     * Cherche une methode selon son nom et ses paramètres d'invocation.
     *
     * @param klass      la classe dans laquelle rechercher la méthode
     * @param methodName le nom de la méthode recherchée
     * @param strict     un drapeau pour déclancher une exception si la méthode
     *                   n'est pas trouvée
     * @param arguments  les arguments d'invocation de la méthode
     * @return la méthode trouvée
     * @throws IllegalArgumentException si la méthode n'est pas trouvée et que
     *                                  le drapeau {@code strict} est à {@code true}
     * @since 1.3.1
     */
    public static Method getDeclaredMethod(Class klass,
                                           String methodName,
                                           boolean strict,
                                           Object... arguments) throws IllegalArgumentException {
        HashSet> classes = new HashSet>();
        Method method;
        try {
            method = getDeclaredMethod(klass, methodName, classes, arguments);
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("Inspected classes : " + classes);
            }
            classes.clear();
        }
        if (method == null && strict) {
            throw new IllegalArgumentException(
                    "could not find method " + methodName + " on type " +
                    klass.getName());
        }

        return method;
    }

    /**
     * Obtain the boxed type of any incoming type.
     *
     * If incoming type is not a primitive type, then just returns himself.
     *
     * @param type the type to box
     * @return the boxed type
     * @see Class#isPrimitive()
     * @since 1.3.1
     */
    public static Class boxType(Class type) {
        if (!type.isPrimitive()) {
            return type;
        }
        if (boolean.class.equals(type)) {
            return Boolean.class;
        }
        if (char.class.equals(type)) {
            return Character.class;
        }
        if (byte.class.equals(type)) {
            return Byte.class;
        }
        if (short.class.equals(type)) {
            return Short.class;
        }
        if (int.class.equals(type)) {
            return Integer.class;
        }
        if (long.class.equals(type)) {
            return Long.class;
        }
        if (float.class.equals(type)) {
            return Float.class;
        }
        if (double.class.equals(type)) {
            return Double.class;
        }
        if (void.class.equals(type)) {
            return Void.class;
        }
        // should never come here...
        return type;

    }

    /**
     * Obtain the unboxed type of any incoming type.
     *
     * If incoming type is a primitive type, then just returns himself.
     *
     * @param type the type to unbox
     * @return the unboxed type
     * @see Class#isPrimitive()
     * @since 1.3.1
     */
    public static Class unboxType(Class type) {
        if (type.isPrimitive()) {
            return type;
        }
        if (Boolean.class.equals(type)) {
            return boolean.class;
        }
        if (Character.class.equals(type)) {
            return char.class;
        }
        if (Byte.class.equals(type)) {
            return byte.class;
        }
        if (Short.class.equals(type)) {
            return short.class;
        }
        if (Integer.class.equals(type)) {
            return int.class;
        }
        if (Long.class.equals(type)) {
            return long.class;
        }
        if (Float.class.equals(type)) {
            return float.class;
        }
        if (Double.class.equals(type)) {
            return double.class;
        }
        if (Void.class.equals(type)) {
            return void.class;
        }

        // not a primitive type
        return type;
    }

    /**
     * Obtain all the declared fields of the given {@code objectClass} with a
     * deep scan inside super classes and interfaces.
     *
     * Note: The type {@link Object} will not be scanned.
     *
     * @param objectClass the object class to scan
     * @return the set of all declared fields found
     * @since 2.0
     */
    public static Set getAllDeclaredFields(Class objectClass) {
        Set result = new HashSet();
        Set> visitedClasses = new HashSet>();

        try {
            getAllDeclaredFields(objectClass, visitedClasses, result);
        } finally {
            visitedClasses.clear();
        }
        return result;
    }

    /**
     * Obtain all the declared methods of the given {@code objectClass} with a
     * deep scan inside super classes and interfaces.
     *
     * Note: The type {@link Object} will not be scanned.
     *
     * @param objectClass the object class to scan
     * @return the set of all declared methods found
     * @since 2.0
     */
    public static Set getAllDeclaredMethods(Class objectClass) {
        Set result = new HashSet();
        Set> visitedClasses = new HashSet>();

        try {
            getAllDeclaredMethods(objectClass, visitedClasses, result);
        } finally {
            visitedClasses.clear();
        }
        return result;
    }

    /**
     * Obtain all the fields with the given annotation type.
     *
     * Note: This method will not scan deeply the given type
     * (no scan of super-classes nor interfaces).
     *
     * @param objectClass     the type to scan
     * @param annotationClass the type of annotation to scan
     * @param              type of annotation to scan
     * @return the dictionnary of fields with the given annotation
     * @since 2.0
     */
    public static  Map getFieldAnnotation(Class objectClass,
                                                                          Class annotationClass) {
        Map result = getFieldAnnotation(objectClass,
                                                  annotationClass,
                                                  false
        );
        return result;
    }

    /**
     * Obtain all the fields with the given annotation type.
     *
     * Note: This method will scan deeply the given type
     * if parameter {@code deepVisit} is setted to {@code true}.
     *
     * Note: The type {@link Object} will not be scanned.
     *
     * @param objectClass     the type to scan
     * @param annotationClass the type of annotation to scan
     * @param deepVisit       flag to visit deeply the class (if set to
     *                        {@code true}, will also scan super classes
     *                        and interfaces)
     * @param              type of annotation to scan
     * @return the dictionnary of fields with the given annotation
     * @since 2.0
     */
    public static  Map getFieldAnnotation(Class objectClass,
                                                                          Class annotationClass,
                                                                          boolean deepVisit) {

        Map result = new HashMap();
        Set> visitedClasses = new HashSet>();

        try {
            getFieldAnnotation(objectClass,
                               annotationClass,
                               deepVisit,
                               visitedClasses,
                               result
            );
        } finally {
            visitedClasses.clear();
        }

        return result;
    }

    /**
     * Obtain all the methods with the given annotation type.
     *
     * Note: This method will not scan deeply the given type
     * (no scan of super-classes nor interfaces).
     *
     * @param objectClass     the type to scan
     * @param annotationClass the type of annotation to scan
     * @param              type of annotation to scan
     * @return the dictionnary of methods with the given annotation
     * @since 2.0
     */
    public static  Map getMethodAnnotation(Class objectClass,
                                                                            Class annotationClass) {
        Map result = getMethodAnnotation(objectClass,
                                                    annotationClass,
                                                    false
        );
        return result;
    }

    /**
     * Obtain all the methods with the given annotation type.
     *
     * Note: This method will scan deeply the given type
     * if parameter {@code deepVisit} is setted to {@code true}.
     *
     * Note: The type {@link Object} will not be scanned.
     *
     * @param objectClass     the type to scan
     * @param annotationClass the type of annotation to scan
     * @param              type of annotation to scan
     * @param deepVisit       flag to visit deeply the class (if set to
     *                        {@code true}, will also scan super classes
     *                        and interfaces)
     * @return the dictionnary of methods with the given annotation
     * @since 2.0
     */
    public static  Map getMethodAnnotation(Class objectClass,
                                                                            Class annotationClass,
                                                                            boolean deepVisit) {
        Map result = new HashMap();
        Set> visitedClasses = new HashSet>();

        try {
            getMethodAnnotation(objectClass,
                                annotationClass,
                                deepVisit,
                                visitedClasses,
                                result
            );
        } finally {
            visitedClasses.clear();
        }
        return result;
    }

    protected static Method getDeclaredMethod(Class klass,
                                              String methodName,
                                              Set> visitedClasses,
                                              Object... arguments) {
        if (visitedClasses.contains(klass)) {

            // this means class was already unsucessfull visited
            return null;
        }
        visitedClasses.add(klass);
        Method method = null;
        for (Method m : klass.getDeclaredMethods()) {
            if (!methodName.equals(m.getName())) {
                continue;
            }

            // same method name

            Class[] types = m.getParameterTypes();
            if (arguments.length != types.length) {
                continue;
            }

            // same number arguments

            Class[] prototype = m.getParameterTypes();
            if (log.isDebugEnabled()) {
                log.debug("Found a method with same parameters size : " +
                          m.getName() + " : " + Arrays.toString(prototype));
            }
            int index = 0;
            boolean parametersMatches = true;
            for (Object argument : arguments) {
                Class type = prototype[index++];
                if (argument == null) {

                    // can not say anything, let says it is ok...
                    continue;
                }
                Class runtimeType = argument.getClass();
                if (log.isDebugEnabled()) {
                    log.debug("Test parameter [" + (index - 1) + "] : " +
                              type + " vs  " + runtimeType);
                }

                type = boxType(type);
                runtimeType = boxType(runtimeType);

                if (!type.equals(runtimeType) &&
                    !type.isAssignableFrom(runtimeType)) {

                    // not same type
                    parametersMatches = false;
                    if (log.isDebugEnabled()) {
                        log.debug("Types are not matching.");
                    }
                    break;
                }
            }
            if (parametersMatches) {

                // same parameters types, this is a match
                method = m;
            }
            break;
        }
        if (method == null) {

            // try on super class
            if (klass.getSuperclass() != null) {
                method = getDeclaredMethod(klass.getSuperclass(),
                                           methodName,
                                           visitedClasses,
                                           arguments
                );
            }
        }

        if (method == null) {

            // try on interfaces
            Class[] interfaces = klass.getInterfaces();
            for (Class anInterface : interfaces) {
                method = getDeclaredMethod(anInterface,
                                           methodName,
                                           visitedClasses,
                                           arguments
                );
                if (method != null) {
                    break;
                }
            }
        }
        return method;

    }

    protected static void getAllDeclaredFields(Class objectClass,
                                               Set> visitedClasses,
                                               Set result) {

        if (visitedClasses.contains(objectClass) ||
            Object.class.equals(objectClass)) {

            // already scanned
            // or arrives to Object.class
            return;
        }

        // mark as scanned
        visitedClasses.add(objectClass);

        Field[] declaredFields = objectClass.getDeclaredFields();
        result.addAll(Arrays.asList(declaredFields));

        Class superclass = objectClass.getSuperclass();
        if (superclass != null) {

            // scan also the superclass
            getAllDeclaredFields(superclass,
                                 visitedClasses,
                                 result
            );
        }

        Class[] interfaces = objectClass.getInterfaces();

        for (Class anInterface : interfaces) {

            // scan also the interface
            getAllDeclaredFields(anInterface,
                                 visitedClasses,
                                 result
            );
        }

    }

    protected static void getAllDeclaredMethods(Class objectClass,
                                                Set> visitedClasses,
                                                Set result) {

        if (visitedClasses.contains(objectClass) ||
            Object.class.equals(objectClass)) {

            // already scanned
            // or arrives to Object.class
            return;
        }

        // mark as scanned
        visitedClasses.add(objectClass);

        Method[] declaredFields = objectClass.getDeclaredMethods();
        result.addAll(Arrays.asList(declaredFields));

        Class superclass = objectClass.getSuperclass();
        if (superclass != null) {

            // scan also the superclass
            getAllDeclaredMethods(superclass,
                                  visitedClasses,
                                  result
            );
        }

        Class[] interfaces = objectClass.getInterfaces();

        for (Class anInterface : interfaces) {

            // scan also the interface
            getAllDeclaredMethods(anInterface,
                                  visitedClasses,
                                  result
            );
        }

    }

    protected static  void getFieldAnnotation(Class objectClass,
                                                                    Class annotationClass,
                                                                    boolean deepVisit,
                                                                    Set> visitedClasses,
                                                                    Map result) {

        if (visitedClasses.contains(objectClass) ||
            Object.class.equals(objectClass)) {

            // already scanned
            // or arrives to Object.class
            return;
        }

        // mark as scanned
        visitedClasses.add(objectClass);

        for (Field field : objectClass.getDeclaredFields()) {
            A annotation = field.getAnnotation(annotationClass);
            if (annotation != null) {
                result.put(field, annotation);
            }
        }

        if (deepVisit) {

            Class superclass = objectClass.getSuperclass();
            if (superclass != null) {

                // scan also the superclass
                getFieldAnnotation(superclass,
                                   annotationClass,
                                   deepVisit,
                                   visitedClasses,
                                   result
                );
            }

            Class[] interfaces = objectClass.getInterfaces();

            for (Class anInterface : interfaces) {

                // scan also the interface
                getFieldAnnotation(anInterface,
                                   annotationClass,
                                   deepVisit,
                                   visitedClasses,
                                   result
                );
            }
        }
    }

    protected static  void getMethodAnnotation(Class objectClass,
                                                                     Class annotationClass,
                                                                     boolean deepVisit,
                                                                     Set> visitedClasses,
                                                                     Map result) {

        if (visitedClasses.contains(objectClass) ||
            Object.class.equals(objectClass)) {

            // already scanned
            // or arrives to Object.class
            return;
        }

        // mark as scanned
        visitedClasses.add(objectClass);

        for (Method method : objectClass.getDeclaredMethods()) {
            A annotation = method.getAnnotation(annotationClass);
            if (annotation != null) {
                result.put(method, annotation);
            }
        }

        if (deepVisit) {

            Class superclass = objectClass.getSuperclass();
            if (superclass != null) {

                // scan also the superclass
                getMethodAnnotation(superclass,
                                    annotationClass,
                                    deepVisit,
                                    visitedClasses,
                                    result
                );
            }

            Class[] interfaces = objectClass.getInterfaces();

            for (Class anInterface : interfaces) {

                // scan also the interface
                getMethodAnnotation(anInterface,
                                    annotationClass,
                                    deepVisit,
                                    visitedClasses,
                                    result
                );
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy