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

com.araguacaima.specification.common.ReflectionUtils Maven / Gradle / Ivy

/*
 * Copyright 2017 araguacaima
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.araguacaima.specification.common;

import com.araguacaima.specification.util.DataTypesConverter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.Transformer;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

@SuppressWarnings({"UnusedReturnValue"})

public class ReflectionUtils implements Serializable {
    public static final Collection COMMONS_TYPES_PREFIXES = new ArrayList() {
        {
            add("java.lang");
            add("java.util");
            add("java.math");
            add("java.io");
            add("java.sql");
            add("java.text");
            add("java.net");
            add("org.joda.time");
        }
    };
    public static final Collection COMMONS_JAVA_TYPES_EXCLUSIONS = new ArrayList() {
        {
            add("java.util.Currency");
            add("java.util.Calendar");
            add("org.joda.time.Period");
        }
    };
    public static final Transformer FIELD_NAME_TRANSFORMER = Field::getName;
    public static final Predicate METHOD_IS_GETTER_PREDICATE = method -> method.getName().matches
            ("get[A-Z]+.*") || method.getName().matches(
            "is[A-Z]+.*");
    public static final MethodFilter USER_DECLARED_METHODS =
            (method -> !method.isBridge() && !method.isSynthetic());
    private static final DataTypesConverter dataTypesConverter = new DataTypesConverter();
    private static final Field[] EMPTY_FIELD_ARRAY = new Field[0];

    private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];

    private static final Map, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256);


    private static final Map, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256);

    public static LinkedList> recursivelyGetAllSuperClasses(Class clazz) {
        if (clazz == null || Object.class.getName().equals(clazz.getName()) || isCollectionImplementation(
                clazz) || getFullyQualifiedJavaTypeOrNull(clazz.getName(), true) != null) {
            return new LinkedList<>();
        } else {
            LinkedList> result = new LinkedList<>();
            Class superclass = clazz.getSuperclass();
            if (superclass != null && !Object.class.getName().equals(superclass.getName())) {
                result.add(superclass);
                result.addAll(recursivelyGetAllSuperClasses(superclass));
            }
            return result;
        }
    }

    public static boolean isCollectionImplementation(Class clazz) {
        return clazz != null && (Collection.class.isAssignableFrom(clazz) || Object[].class.isAssignableFrom(clazz)
                || clazz.isArray());
    }

    public static String getFullyQualifiedJavaTypeOrNull(String type, boolean considerLists) {
        if (type == null) {
            return null;
        }
        DataTypesConverter.DataTypeView dataTypeView = dataTypesConverter.getDataTypeView(type);
        type = dataTypeView.getTransformedDataType();
        String transformedType;
        boolean complexType = DataTypesConverter.DataTypeView.COMPLEX_TYPE.equals(dataTypeView.getDataType());
        if (complexType) {
            transformedType = type;
        } else {
            transformedType = StringUtils.capitalize(type);
        }
        Class clazz;
        if (considerLists) {
            if (isList(type)) {
                String generics = getExtractedGenerics(type);
                final String javaType = getFullyQualifiedJavaTypeOrNull(generics, false);
                if (StringUtils.isBlank(javaType)) {
                    return null;
                } else {
                    return "java.util.List<" + javaType + ">";
                }
            }
        }
        if (!complexType) {
            for (String javaTypePrefix : COMMONS_TYPES_PREFIXES) {
                try {
                    clazz = Class.forName(transformedType.contains(".") ? transformedType : javaTypePrefix + "." +
                            transformedType);
                    if (considerLists) {
                        if (isList(type)) {
                            return "java.util.List<" + clazz.getName() + ">";
                        }
                    }
                    if (!COMMONS_JAVA_TYPES_EXCLUSIONS.contains(clazz.getName())) {
                        return clazz.getName();
                    }
                } catch (ClassNotFoundException ignored) {

                }
            }
        }
        try {
            Method method = Class.class.getDeclaredMethod("getPrimitiveClass", String.class);
            method.setAccessible(true);
            clazz = (Class) method.invoke(Class.forName("java.lang.Class"), StringUtils.uncapitalize(type));
            if (considerLists) {
                if (isList(type)) {
                    return "java.util.List<" + clazz.getName() + ">";
                }
            }
            return clazz.getName();
        } catch (InvocationTargetException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException
                | NullPointerException ignored) {
        }
        return null;
    }

    public static boolean isList(String type) {
        try {
            boolean result = type.equals("List") || type.startsWith("List<") || type.startsWith("java.util.List<") || type.equals(
                    "Collection") || type.startsWith("Collection<") || type.startsWith("java.util.Collection<");
            if (!result) {
                String firstPartType = StringUtils.defaultIfBlank(returnNativeClass(type), type.split("<")[0]);
                Class.forName(firstPartType);
            }
            return true;
        } catch (Throwable ignored) {
            return false;
        }
    }

    public static String returnNativeClass(String type) {
        Class clazz;
        type = type.split("<")[0];
        for (String javaTypePrefix : COMMONS_TYPES_PREFIXES) {
            try {
                clazz = Class.forName(type.contains(".") ? type : javaTypePrefix + "." + type);
                return clazz.getName();
            } catch (ClassNotFoundException ignored) {

            }
        }
        return null;
    }

    public static String getExtractedGenerics(String s) {
        String s1 = s.trim();
        try {
            String firstPartType = s1.split("<")[1];
            if (firstPartType.endsWith(">")) {
                return firstPartType.substring(0, firstPartType.length() - 1);
            }
        } catch (Throwable ignored) {
        }
        return s1;
    }

    public static Collection getAllFieldsNamesOfType(Object object, final Class type) {
        return getAllFieldsNamesOfType(object.getClass(), null, type);
    }

    public static Collection getAllFieldsNamesOfType(Class clazz,
                                                             Collection excludeFields,
                                                             final Class type) {
        return CollectionUtils.collect(getAllFieldsOfType(clazz, excludeFields, type), FIELD_NAME_TRANSFORMER);
    }

    public static Collection getAllFieldsOfType(Class clazz,
                                                       Collection excludeFields,
                                                       final Class type) {
        final Collection result = new ArrayList<>();
        if (clazz != null) {
            IterableUtils.forEach(getAllFieldsIncludingParents(clazz, excludeFields), field -> {
                if (field.getType().getName().equals(type.getName())) {
                    result.add(field);
                }
            });
        }
        return result;
    }

    public static Collection getAllFieldsIncludingParents(Class clazz, Collection excludeFields) {
        Collection result = getAllFieldsIncludingParents(clazz);
        if (CollectionUtils.isEmpty(excludeFields)) {
            return result;
        }
        CollectionUtils.filterInverse(result, field -> fieldIsContainedIn(field, excludeFields));
        return result;
    }

    public static Collection getAllFieldsIncludingParents(Class clazz) {
        return getAllFieldsIncludingParents(clazz,
                null,
                Modifier.STATIC | Modifier.VOLATILE | Modifier.NATIVE | Modifier.TRANSIENT);
    }

    public static Collection getAllFieldsIncludingParents(Class clazz,
                                                                 final Integer modifiersInclusion,
                                                                 final Integer modifiersExclusion) {
        final Collection fields = new ArrayList<>();
        FieldFilter fieldFilterModifierInclusion = field -> modifiersInclusion == null || (field.getModifiers() &
                modifiersInclusion) != 0;
        FieldFilter fieldFilterModifierExclusion = field -> modifiersExclusion == null || (field.getModifiers() &
                modifiersExclusion) == 0;
        doWithFields(clazz,
                fields::add,
                modifiersInclusion == null ? (modifiersExclusion == null ? null : fieldFilterModifierExclusion) :
                        fieldFilterModifierInclusion);
        return fields;
    }

    public static Object invokeGetter(Object object, final String fieldName) {
        Object value = null;
        Method method = IterableUtils.find(getGetterMethods(object.getClass()),
                innerMethod -> innerMethod.getName().toUpperCase().equals(("get" + fieldName).toUpperCase()));
        Object[] args = {};
        try {
            value = method.invoke(object, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            try {
                Field field = getField(object, fieldName);
                field.setAccessible(true);
                value = field.get(object);
            } catch (IllegalAccessException ignored) {

            }
        } catch (NullPointerException e) {
            try {
                Field field = getField(object, fieldName);
                if (field != null) {
                    field.setAccessible(true);
                    value = field.get(object);
                }
            } catch (IllegalAccessException ignored) {
            }
        }
        return value;
    }


    private static boolean fieldIsNotContainedIn(Field field, Collection excludeFields) {
        return !fieldIsContainedIn(field, excludeFields);
    }

    private static boolean fieldIsContainedIn(Field field, Collection excludeFields) {
        return IterableUtils.find(excludeFields, fieldName -> fieldNameEqualsToPredicate(field, fieldName)) != null;
    }

    private static boolean fieldNameEqualsToPredicate(Field field, String fieldName) {
        return !StringUtils.isBlank(fieldName) && field.getName().equals(fieldName);
    }

    public static Field getField(Object object, final String fieldName) {
        return getField(object.getClass(), fieldName);
    }

    public static Field getField(Class clazz, String fieldName) {
        return IterableUtils.find(getAllFieldsIncludingParents(clazz),
                field -> fieldNameEqualsToPredicate(field, fieldName));
    }

    public static Collection getGetterMethods(Class clazz) {
        return CollectionUtils.select(getAllMethodsIncludingParents(clazz), METHOD_IS_GETTER_PREDICATE);
    }

    public static Collection getAllMethodsIncludingParents(Class clazz) {
        return getAllMethodsIncludingParents(clazz, null, Modifier.VOLATILE | Modifier.NATIVE | Modifier.TRANSIENT);
    }

    public static Collection getAllMethodsIncludingParents(Class clazz,
                                                                   final Integer modifiersInclusion,
                                                                   final Integer modifiersExclusion) {
        final Collection methods = new ArrayList<>();
        MethodFilter fieldFilterModifierInclusion = method -> modifiersInclusion == null || (method.getModifiers() &
                modifiersInclusion) != 0;
        MethodFilter fieldFilterModifierExclusion = method -> modifiersExclusion == null || (method.getModifiers() &
                modifiersExclusion) == 0;
        doWithMethods(clazz,
                methods::add,
                modifiersInclusion == null ? modifiersExclusion == null ? null : fieldFilterModifierExclusion :
                        fieldFilterModifierInclusion);
        return methods;
    }

    public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) {
        // Keep backing up the inheritance hierarchy.
        Class targetClass = clazz;
        do {
            Field[] fields = getDeclaredFields(targetClass);
            for (Field field : fields) {
                if (ff != null && !ff.matches(field)) {
                    continue;
                }
                try {
                    fc.doWith(field);
                } catch (IllegalAccessException ex) {
                    throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
                }
            }
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);
    }

    public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) {
        // Keep backing up the inheritance hierarchy.
        Method[] methods = getDeclaredMethods(clazz, false);
        for (Method method : methods) {
            if (mf != null && !mf.matches(method)) {
                continue;
            }
            try {
                mc.doWith(method);
            } catch (IllegalAccessException ex) {
                throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
            }
        }
        if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
            doWithMethods(clazz.getSuperclass(), mc, mf);
        } else if (clazz.isInterface()) {
            for (Class superIfc : clazz.getInterfaces()) {
                doWithMethods(superIfc, mc, mf);
            }
        }
    }

    public static Collection getDeclaredFields(Object object) {
        Field[] declaredFields = getDeclaredFields(object.getClass());
        return Arrays.asList(declaredFields);
    }

    private static Field[] getDeclaredFields(Class clazz) {
        Field[] result = declaredFieldsCache.get(clazz);
        if (result == null) {
            try {
                result = clazz.getDeclaredFields();
                declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result));
            } catch (Throwable ex) {
                throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
                        "] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
            }
        }
        return result;
    }

    private static Method[] getDeclaredMethods(Class clazz, boolean defensive) {
        Method[] result = declaredMethodsCache.get(clazz);
        if (result == null) {
            try {
                Method[] declaredMethods = clazz.getDeclaredMethods();
                List defaultMethods = findConcreteMethodsOnInterfaces(clazz);
                if (defaultMethods != null) {
                    result = new Method[declaredMethods.length + defaultMethods.size()];
                    System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
                    int index = declaredMethods.length;
                    for (Method defaultMethod : defaultMethods) {
                        result[index] = defaultMethod;
                        index++;
                    }
                } else {
                    result = declaredMethods;
                }
                declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
            } catch (Throwable ex) {
                throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
                        "] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
            }
        }
        return (result.length == 0 || !defensive) ? result : result.clone();
    }

    private static List findConcreteMethodsOnInterfaces(Class clazz) {
        List result = null;
        for (Class ifc : clazz.getInterfaces()) {
            for (Method ifcMethod : ifc.getMethods()) {
                if (!Modifier.isAbstract(ifcMethod.getModifiers())) {
                    if (result == null) {
                        result = new ArrayList<>();
                    }
                    result.add(ifcMethod);
                }
            }
        }
        return result;
    }

    @FunctionalInterface
    public interface FieldFilter {

        /**
         * Determine whether the given field matches.
         *
         * @param field the field to check
         */
        boolean matches(Field field);
    }

    @FunctionalInterface
    public interface MethodFilter {

        /**
         * Determine whether the given method matches.
         *
         * @param method the method to check
         */
        boolean matches(Method method);
    }

    @FunctionalInterface
    public interface FieldCallback {

        /**
         * Perform an operation using the given field.
         *
         * @param field the field to operate on
         */
        void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
    }

    @FunctionalInterface
    public interface MethodCallback {

        /**
         * Perform an operation using the given method.
         *
         * @param method the method to operate on
         */
        void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy