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

com.cloudhopper.commons.util.ClassUtil Maven / Gradle / Ivy

package com.cloudhopper.commons.util;

/*
 * #%L
 * ch-commons-util
 * %%
 * Copyright (C) 2012 Cloudhopper by Twitter
 * %%
 * Licensed 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.
 * #L%
 */

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;

/**
 * This class implements utilities for working with classes.
 *
 * @author joelauer (twitter: @jjlauer or http://twitter.com/jjlauer)
 * @author john woolf (twitter: @jwoolf330 or http://twitter.com/jwoolf330)
 */
public class ClassUtil {

    /**
     * Finds an instance of an Enum constant on a class. Useful for safely
     * getting the value of an enum constant without an exception being thrown
     * like the Enum.valueOf() method causes. Searches for enum constant
     * where case is sensitive.
     */
    public static Object findEnumConstant(Class type, String constantName) {
        return findEnumConstant(type, constantName, true);
    }


    /**
     * Finds an instance of an Enum constant on a class. Useful for safely
     * getting the value of an enum constant without an exception being thrown
     * like the Enum.valueOf() method causes. Also, this method optionally allows
     * the caller to choose whether case matters during the search.
     */
    public static Object findEnumConstant(Class type, String constantName, boolean caseSensitive) {
        if (!type.isEnum()) {
            return null;
        }
        for (Object obj : type.getEnumConstants()) {
            String name = obj.toString();
            if ((caseSensitive && name.equals(constantName)) || (!caseSensitive && name.equalsIgnoreCase(constantName))) {
                return obj;
            }
        }
        // otherwise, it wasn't found
        return null;
    }


    /**
     * Returns an array of class objects representing the entire class hierarchy
     * with the most-super class as the first element followed by all subclasses
     * in the order they are declared. This method does not include the generic
     * Object type in its list. If this class represents the Object type, this
     * method will return a zero-size array.
     */
    public static Class[] getClassHierarchy(Class type) {
        ArrayDeque> classes = new ArrayDeque>();
        // class to start our search from, we'll loop thru the entire class hierarchy
        Class classType = type;
        // keep searching up until we reach an Object class type
        while (classType != null && !classType.equals(Object.class)) {
            // keep adding onto front
            classes.addFirst(classType);
            classType = classType.getSuperclass();
        }
        return classes.toArray(new Class[0]);
    }


    /**
     * Checks if the class implements public "bean" methods (get and set) for the
     * name. For example, for a name such as "firstName", this method will
     * check if the class has both getFirstName() and setFirstName() methods.
     * Also, this method will validate the return type matches the paramType
     * on the getXXX() method and that the setXXX() method only accepts that
     * paramType.  The search is optionally case sensitive.
     * @param type The class to search
     * @param propertyName The property name to search for, if "firstName", then
     *      this method will internally add the "get" and "set" to the beginning.
     * @param propertyType The class type of the property
     * @param caseSensitive If the search is case sensitive or not
     * @return True if the "bean" methods are correct, otherwise false.
     */
    public static boolean hasBeanMethods(Class type, String propertyName, Class propertyType, boolean caseSensitive) {
        try {
            // if this succeeds without an exception, then the properties exist!
            @SuppressWarnings("unused")
            Method[] methods = getBeanMethods(type, propertyName, propertyType, caseSensitive);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    
    /**
     * Gets the methods within this class that implements public "bean" methods
     * (get and set) for the property name. Returns an array of methods where
     * index=0 is the get method and index=1 is the set method.
     * For example, for a name such as "firstName", this method will
     * check if the class has both getFirstName() and setFirstName() methods.
     * Also, this method will validate the return type matches the propertyType
     * on the getXXX() method and that the setXXX() method only accepts that
     * propertyType.  The search is optionally case sensitive.
     * @param type The class to search
     * @param propertyName The name to search for
     * @param paramType The class type of the property
     * @param caseSensitive If the search is case sensitive or not
     * @return An array of Methods were index=0 is the get method and index=1 is the set method
     * @throws java.lang.IllegalAccessException If the method was found, but is not public.
     * @throws java.lang.NoSuchMethodException If the method was not found
     */
    public static Method[] getBeanMethods(Class type, String propertyName, Class propertyType, boolean caseSensitive)
        throws IllegalAccessException, NoSuchMethodException {
        Method methods[] = new Method[2];
        // search for the "get"
        methods[0] = getMethod(type, "get"+propertyName, propertyType, null, caseSensitive);
        // search for the "set"
        methods[1] = getMethod(type, "set"+propertyName, null, propertyType, caseSensitive);
        return methods;
    }




    /**
     * Gets the public method within the type that matches the method name, return type,
     * and single parameter type. Optionally is a case sensitive search. Useful
     * for searching for "bean" methods on classes.
     * @param type The class to search for the method
     * @param name The name of the method to search for
     * @param returnType The expected return type or null if its expected to be a void method
     * @param paramType The expected parameter type or null if no parameters are expected
     * @param caseSensitive True if its a case sensitive search, otherwise false
     * @return The method matching the search criteria
     * @throws java.lang.IllegalAccessException If the method was found, but is not public.
     * @throws java.lang.NoSuchMethodException If the method was not found
     */
    public static Method getMethod(Class type, String name, Class returnType, Class paramType, boolean caseSensitive)
        throws IllegalAccessException, NoSuchMethodException {
        
        // flag to help modify the exception to make it a little easier for debugging
        boolean methodNameFound = false;

        // start our search
        Class classType = type;

        while (classType != null && !classType.equals(Object.class)) {

            for (Method m : classType.getDeclaredMethods()) {

                if ((!caseSensitive && m.getName().equalsIgnoreCase(name)) || (caseSensitive && m.getName().equals(name))) {

                    // we found the method name, but its possible the signature won't
                    // match below, we'll set this flag to help construct a better exception
                    // below
                    methodNameFound = true;

                    // should we validate the return type?
                    if (returnType != null) {
                        // if the return types don't match, then this must be invalid
                        // since the JVM doesn't allow the same return type
                        if (!m.getReturnType().equals(returnType)) {
                            throw new NoSuchMethodException("Method '" + name + "' was found in " + type.getSimpleName() + ".class"
                                    + ", but the returnType " + m.getReturnType().getSimpleName() + ".class did not match expected " + returnType.getSimpleName() + ".class");
                        }
                    // make sure the return type is VOID
                    } else {
                        if (!m.getReturnType().equals(void.class)) {
                            throw new NoSuchMethodException("Method '" + name + "' was found in " + type.getSimpleName() + ".class"
                                    + ", but the returnType " + m.getReturnType().getSimpleName() + ".class was expected to be void");
                        }
                    }

                    // return type was okay, check the parameters
                    Class[] paramTypes = m.getParameterTypes();

                    // should we check the parameter type?
                    if (paramType != null) {
                        // must have exactly 1 parameter
                        if (paramTypes.length != 1) {
                            // this might not be the method we want, keep searching
                            continue;
                        } else {
                            // if the parameters don't match, keep searching
                            if (!paramTypes[0].equals(paramType)) {
                                continue;
                            }
                        }
                    // if paramType was null, then make sure no parameters are expected
                    } else {
                        if (paramTypes.length != 0) {
                            continue;
                        }
                    }

                    // if we got here, then everything matches so far
                    // now its time to check if the method is accessible
                    if (!Modifier.isPublic(m.getModifiers())) {
                        throw new IllegalAccessException("Method '" + name + "' was found in " + type.getSimpleName() + ".class "+
                                ", but its not accessible since its " + Modifier.toString(m.getModifiers()));
                    }

                    // everything was okay
                    return m;
                }
            }

            // move onto the superclass
            classType = classType.getSuperclass();
        }

        String signature = "public " + (returnType == null ? "void" : returnType.getName()) + " " + name + "(" + (paramType == null ? "" : paramType.getName()) + ")";
        
        if (methodNameFound) {
            throw new NoSuchMethodException("Method '" + signature + "' was found in " + type.getSimpleName() + ".class, but signature match failed");
        } else {
            throw new NoSuchMethodException("Method '" + signature + "' was not found in " + type.getSimpleName() + ".class");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy