net.radai.beanz.util.ReflectionUtil.java Source code

Java tutorial

Introduction

Here is the source code for net.radai.beanz.util.ReflectionUtil.java

Source

/*
 * Copyright (c) 2016 Radai Rosenblatt.
 * This file is part of Beanz.
 *
 * Beanz is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Beanz 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 Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Beanz.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.radai.beanz.util;

import net.radai.beanz.api.PropertyType;
import org.apache.commons.lang3.ClassUtils;

import java.lang.reflect.*;
import java.util.*;

/**
 * Created by Radai Rosenblatt
 */
public class ReflectionUtil {
    public static Class<?> erase(Type type) {
        if (type instanceof GenericArrayType) {
            return Object.class; //TODO - get more info?
        }
        if (type instanceof WildcardType) {
            throw new UnsupportedOperationException();
        }
        if (type instanceof TypeVariable) {
            throw new UnsupportedOperationException();
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return (Class) parameterizedType.getRawType();
        }
        return (Class) type;
    }

    public static Object instantiate(Type type) {
        Class<?> erased = erase(type);
        if (Collection.class.isAssignableFrom(erased)) {
            return instantiateCollection(erased);
        }
        try {
            return erased.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Collection<?> instantiateCollection(Class<?> collectionClass) {
        if (!Collection.class.isAssignableFrom(collectionClass)) {
            throw new IllegalArgumentException();
        }
        if (!Modifier.isAbstract(collectionClass.getModifiers())) {
            try {
                //noinspection unchecked
                return (Collection<?>) collectionClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
        if (List.class.isAssignableFrom(collectionClass)) {
            return new ArrayList<>();
        }
        if (Set.class.isAssignableFrom(collectionClass)) {
            return new HashSet<>();
        }
        throw new UnsupportedOperationException();
    }

    public static Object instatiateArray(Type elementType, int size) {
        Class<?> elementClass = erase(elementType);
        return Array.newInstance(elementClass, size);
    }

    public static Map<?, ?> instantiateMap(Class<?> mapClass) {
        if (!Map.class.isAssignableFrom(mapClass)) {
            throw new IllegalArgumentException();
        }
        if (!Modifier.isAbstract(mapClass.getModifiers())) {
            try {
                //noinspection unchecked
                return (Map<?, ?>) mapClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
        return new HashMap<>();
    }

    public static boolean isPrimitive(Type type) {
        return erase(type).isPrimitive();
    }

    public static boolean isArray(Type type) {
        if (type instanceof ParameterizedType) {
            return false;
        }
        if (type instanceof Class<?>) {
            Class<?> clazz = (Class<?>) type;
            return clazz.isArray();
        }
        if (type instanceof GenericArrayType) {
            return true;
        }
        throw new UnsupportedOperationException();
    }

    public static boolean isCollection(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return Collection.class.isAssignableFrom((Class<?>) parameterizedType.getRawType());
        }
        if (type instanceof Class<?>) {
            Class<?> clazz = (Class<?>) type;
            return Collection.class.isAssignableFrom(clazz);
        }
        if (type instanceof GenericArrayType) {
            return false;
        }
        throw new UnsupportedOperationException();
    }

    public static boolean isMap(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return Map.class.isAssignableFrom((Class<?>) parameterizedType.getRawType());
        }
        if (type instanceof Class<?>) {
            Class<?> clazz = (Class<?>) type;
            return Map.class.isAssignableFrom(clazz);
        }
        if (type instanceof GenericArrayType) {
            return false;
        }
        throw new UnsupportedOperationException();
    }

    public static boolean isEnum(Type type) {
        if (type instanceof ParameterizedType) {
            return false; //no such thing as Enum<Something>
        }
        if (type instanceof Class<?>) {
            Class<?> clazz = (Class<?>) type;
            return clazz.isEnum();
        }
        if (type instanceof GenericArrayType) {
            return false;
        }
        throw new UnsupportedOperationException();
    }

    public static PropertyType typeOf(Type type) {
        if (isArray(type)) {
            return PropertyType.ARRAY;
        }
        if (isCollection(type)) {
            return PropertyType.COLLECTION;
        }
        if (isMap(type)) {
            return PropertyType.MAP;
        }
        return PropertyType.SIMPLE;
    }

    public static Type getElementType(Type type) {
        if (isArray(type)) {
            if (type instanceof Class<?>) {
                Class<?> clazz = (Class<?>) type;
                return clazz.getComponentType();
            }
            if (type instanceof GenericArrayType) {
                GenericArrayType arrayType = (GenericArrayType) type;
                return arrayType.getGenericComponentType();
            }
            throw new UnsupportedOperationException();
        }
        if (isCollection(type)) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                if (typeArguments.length != 1) {
                    throw new UnsupportedOperationException();
                }
                return typeArguments[0];
            }
            if (type instanceof Class<?>) {
                //this is something like a plain List(), no generic information, so type unknown
                return null;
            }
            throw new UnsupportedOperationException();
        }
        if (isMap(type)) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                if (typeArguments.length != 2) {
                    throw new UnsupportedOperationException();
                }
                return typeArguments[1];
            }
            if (type instanceof Class<?>) {
                //this is something like a plain Map(), no generic information, so type unknown
                return null;
            }
        }
        throw new UnsupportedOperationException();
    }

    public static Type getKeyType(Type type) {
        if (isMap(type)) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                if (typeArguments.length != 2) {
                    throw new UnsupportedOperationException();
                }
                return typeArguments[0];
            }
            if (type instanceof Class<?>) {
                //this is something like a plain Map(), no generic information, so type unknown
                return null;
            }
        }
        throw new UnsupportedOperationException();
    }

    public static boolean isStatic(Method method) {
        return Modifier.isStatic(method.getModifiers());
    }

    public static boolean isStatic(Field field) {
        return Modifier.isStatic(field.getModifiers());
    }

    public static boolean isFinal(Method method) {
        return Modifier.isFinal(method.getModifiers());
    }

    public static boolean isFinal(Field field) {
        return Modifier.isFinal(field.getModifiers());
    }

    public static boolean isAssignable(Type from, Type to) {
        //TODO - be smarter about generics (List<String> is not really assignable to List<Integer> ...)
        return ClassUtils.isAssignable(erase(from), erase(to), true);
    }

    public static boolean isGetter(Method method) {
        Type returnType = method.getGenericReturnType();
        if (returnType.equals(void.class)) {
            return false; //should return something
        }
        Type[] argumentTypes = method.getGenericParameterTypes();
        if (argumentTypes != null && argumentTypes.length != 0) {
            return false; //should not accept any arguments
        }
        String name = method.getName();
        if (name.startsWith("get")) {
            if (name.length() < 4) {
                return false; //has to be getSomething
            }
            String fourthChar = name.substring(3, 4);
            return fourthChar.toUpperCase(Locale.ROOT).equals(fourthChar); //getSomething (upper case)
        } else if (name.startsWith("is")) {
            if (name.length() < 3) {
                return false; //isSomething
            }
            String thirdChar = name.substring(2, 3);
            //noinspection SimplifiableIfStatement
            if (!thirdChar.toUpperCase(Locale.ROOT).equals(thirdChar)) {
                return false; //has to start with uppercase (or something that uppercases to itself, like a number?)
            }
            return returnType.equals(boolean.class) || returnType.equals(Boolean.class);
        } else {
            return false;
        }
    }

    public static boolean isSetter(Method method) {
        Type returnType = method.getGenericReturnType();
        if (!returnType.equals(void.class)) {
            return false; //should not return anything
        }
        Type[] argumentTypes = method.getGenericParameterTypes();
        if (argumentTypes == null || argumentTypes.length != 1) {
            return false; //should accept exactly one argument
        }
        String name = method.getName();
        if (name.startsWith("set")) {
            if (name.length() < 4) {
                return false; //setSomething
            }
            String fourthChar = name.substring(3, 4);
            return fourthChar.toUpperCase(Locale.ROOT).equals(fourthChar); //setSomething (upper case)
        }
        return false;
    }

    public static String propNameFrom(Method method) {
        String methodName = method.getName();
        if (methodName.startsWith("get") || methodName.startsWith("set")) {
            if (methodName.length() > 4) {
                return methodName.substring(3, 4).toLowerCase(Locale.ROOT) + methodName.substring(4);
            } else {
                return methodName.substring(3, 4).toLowerCase(Locale.ROOT);
            }
        }
        if (methodName.startsWith("is")) {
            if (methodName.length() > 3) {
                return methodName.substring(2, 3).toLowerCase(Locale.ROOT) + methodName.substring(3);
            } else {
                return methodName.substring(2, 3).toLowerCase(Locale.ROOT);
            }
        }
        throw new IllegalArgumentException("method name " + methodName + " does not contain a property name");
    }

    public static Method findSetter(Class<?> clazz, String propName) {
        String expectedName = "set" + propName.substring(0, 1).toUpperCase(Locale.ROOT) + propName.substring(1);
        for (Method method : clazz.getMethods()) {
            if (!method.getName().equals(expectedName)) {
                continue;
            }
            if (!method.getReturnType().equals(void.class)) {
                continue;
            }
            Type[] argTypes = method.getGenericParameterTypes();
            if (argTypes == null || argTypes.length != 1) {
                continue;
            }
            return method;
        }
        return null;
    }

    public static Method findGetter(Class<?> clazz, String propName) {
        Set<String> expectedNames = new HashSet<>(
                Arrays.asList("get" + propName.substring(0, 1).toUpperCase(Locale.ROOT) + propName.substring(1),
                        "is" + propName.substring(0, 1).toUpperCase(Locale.ROOT) + propName.substring(1) //bool props
                ));
        for (Method method : clazz.getMethods()) {
            String methodName = method.getName();
            if (!expectedNames.contains(methodName)) {
                continue;
            }
            if (method.getParameterCount() != 0) {
                continue; //getters take no arguments
            }
            Type returnType = method.getGenericReturnType();
            if (returnType.equals(void.class)) {
                continue; //getters return something
            }
            if (methodName.startsWith("is")
                    && !(returnType.equals(Boolean.class) || returnType.equals(boolean.class))) {
                continue; //isSomething() only valid for booleans
            }
            return method;
        }
        return null;
    }

    public static Field findField(Class<?> clazz, String propName) {
        Class<?> c = clazz;
        while (c != null) {
            for (Field f : c.getDeclaredFields()) {
                if (!f.getName().equals(propName)) {
                    continue;
                }
                int modifiers = f.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    continue;
                }
                return f;
            }
            c = c.getSuperclass();
        }
        return null;
    }

    public static String prettyPrint(Type type) {
        if (type instanceof GenericArrayType) {
            GenericArrayType genericArrayType = (GenericArrayType) type;
            return prettyPrint(genericArrayType.getGenericComponentType()) + "[]";
        }
        if (type instanceof WildcardType) {
            WildcardType wildcardType = (WildcardType) type;
            return wildcardType.toString();
        }
        if (type instanceof TypeVariable) {
            TypeVariable<?> typeVariable = (TypeVariable) type;
            return typeVariable.getName();
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            StringBuilder sb = new StringBuilder();
            sb.append(prettyPrint(parameterizedType.getRawType()));
            Type[] typeArguments = parameterizedType.getActualTypeArguments();
            if (typeArguments != null && typeArguments.length > 0) {
                sb.append("<");
                for (Type typeArgument : typeArguments) {
                    sb.append(prettyPrint(typeArgument)).append(", ");
                }
                sb.delete(sb.length() - 2, sb.length()); // last ", "
                sb.append(">");
            }
            return sb.toString();
        }
        Class<?> clazz = (Class<?>) type;
        if (clazz.isArray()) {
            return prettyPrint(clazz.getComponentType()) + "[]";
        }
        return clazz.getSimpleName();
    }

    public static String prettyPrint(Method method) {
        StringBuilder sb = new StringBuilder();
        Class<?> declaredOn = method.getDeclaringClass();
        sb.append(prettyPrint(declaredOn)).append(".").append(method.getName()).append("(");
        if (method.getParameterCount() > 0) {
            for (Type paramType : method.getGenericParameterTypes()) {
                sb.append(prettyPrint(paramType)).append(", ");
            }
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append(")");
        return sb.toString();
    }
}