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

org.kuali.common.util.ReflectionUtils Maven / Gradle / Ivy

There is a newer version: 4.4.17
Show newest version
/**
 * Copyright 2010-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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 org.kuali.common.util;

import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static org.kuali.common.util.base.Exceptions.illegalState;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.util.MethodInvoker;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class ReflectionUtils extends org.springframework.util.ReflectionUtils {

	/**
	 * Returns the path from {@code java.lang.Object}, to {@code type}. The last element in the list is {@code type}.
	 */
	public static List> getTypeHierarchy(Class type) {
		List> list = Lists.newArrayList();
		if (type.getSuperclass() != null) {
			list.addAll(getTypeHierarchy(type.getSuperclass()));
		}
		list.add(type);
		return list;
	}

	/**
	 * Return a map containing every parameterized interface implemented by {@code type}, includes interfaces implemented by super classes
	 */
	public static Map, ParameterizedType> getAllParameterizedInterfaces(Class type) {
		List list = getAllGenericInterfaces(type);
		Map, ParameterizedType> map = Maps.newHashMap();
		for (Type element : list) {
			if (element instanceof ParameterizedType) {
				ParameterizedType pType = (ParameterizedType) element;
				Class interfaceClass = (Class) pType.getRawType();
				map.put(interfaceClass, pType);
			}
		}
		return map;
	}

	/**
	 * Return a list containing every interface implemented by {@code type}, includes interfaces implemented by super classes. Type objects returned accurately reflect the actual
	 * type parameters used in the source code.
	 */
	public static List getAllGenericInterfaces(Class type) {
		List> path = getTypeHierarchy(type);
		List list = Lists.newArrayList();
		for (Class element : path) {
			Type[] interfaces = element.getGenericInterfaces();
			list.addAll(ImmutableList.copyOf(interfaces));
		}
		return list;
	}

	/**
	 * Return true if this class is declared as final
	 */
	public static boolean isFinal(Class type) {
		return Modifier.isFinal(type.getModifiers());
	}

	/**
	 * Return true if this field is declared as final
	 */
	public static boolean isFinal(Field field) {
		return Modifier.isFinal(field.getModifiers());
	}

	/**
	 * Return true if this class is an immutable Guava collection
	 */
	public static boolean isImmutableGuavaCollection(Class type) {
		return ImmutableCollection.class.isAssignableFrom(type);
	}

	/**
	 * Return true if this class is an immutable Guava map
	 */
	public static boolean isImmutableGuavaMap(Class type) {
		return ImmutableMap.class.isAssignableFrom(type);
	}

	/**
	 * Return true if this field is a {@code java.util.Collection}
	 */
	public static boolean isCollection(Field field) {
		return Collection.class.isAssignableFrom(field.getType());
	}

	/**
	 * Return true if this field is a {@code java.util.Collection}
	 */
	public static boolean isStringCollection(Field field) {
		return isCollection(field) && hasMatchingParameterizedArgTypes(field, String.class);
	}

	/**
	 * Return true if this field is a {@code java.util.Map}
	 */
	public static boolean isMap(Field field) {
		return Map.class.isAssignableFrom(field.getType());
	}

	/**
	 * Return true if this field extends from {@code java.util.Map} and uses {@code String} for its keys
	 * 
	 * 
	 * Map<String,String>  returns true
	 * Map<String,Object>  returns true
	 * Map<String,Integer> returns true
	 * Map<Integer,String> returns false
	 * 
*/ public static boolean isStringKeyedMap(Field field) { return isMap(field) && hasMatchingParameterizedArgTypes(field, String.class); } /** * Return true if this field is a {@code java.lang.String} */ public static boolean isString(Field field) { // Safe to do this since java.lang.String is final return field.getType() == String.class; } /** * Return true if this field is a CharSequence */ public static boolean isCharSequence(Field field) { return isCharSequence(field.getType()); } /** * Return true if this field is a CharSequence */ public static boolean isCharSequence(Class type) { return CharSequence.class.isAssignableFrom(type); } /** * Return true iff this field is a Guava {@code com.google.common.base.Optional} */ public static boolean isOptional(Field field) { return Optional.class.isAssignableFrom(field.getType()); } /** * Return true iff this field is a Guava {@code com.google.common.base.Optional} */ public static boolean isOptionalString(Field field) { return isOptional(field) && hasMatchingParameterizedArgTypes(field, String.class); } /** *

* Return true if this field is a generic whose argument types match {@code expectedTypeArguments} *

* * For example to match a field declared as {@code Collection} * *
	 * hasMatchingParameterizedArgTypes(myField, String.class)
	 * 
*/ public static boolean hasMatchingParameterizedArgTypes(Field field, Class... expectedTypeArguments) { Type genericType = field.getGenericType(); if (genericType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericType; return hasMatchingActualTypeArguments(parameterizedType, expectedTypeArguments); } else { return false; } } protected static boolean hasMatchingActualTypeArguments(ParameterizedType type, Class... expectedTypeArguments) { Type[] actualTypeArguments = type.getActualTypeArguments(); for (int i = 0; i < expectedTypeArguments.length; i++) { Class expectedTypeArgument = expectedTypeArguments[i]; if (i >= actualTypeArguments.length) { return false; } if (!(actualTypeArguments[i] instanceof Class)) { return false; } Class actualTypeArgument = (Class) actualTypeArguments[i]; if (actualTypeArgument != expectedTypeArgument) { return false; } } return true; } /** *

* Throw an exception unless {@code child} is the same as {@code parent} OR descends from {@code parent}. If {@code child} is a primitive type, throw an exception unless * both {@code child} and {@code parent} are the exact same primitive type. *

* * @see equalsOrDescendsFrom * * @throws IllegalArgumentException * if {@code equalsOrDescendsFrom(child,parent)} returns {@code false} */ public static void validateIsSuperType(Class superType, Class type) { boolean expression = isSuperType(superType, type); Preconditions.checkArgument(expression, "[%s] must descend from (or be) [%s]", type.getCanonicalName(), superType.getCanonicalName()); } /** *

* Return true if {@code type} descends from {@code superType} OR is the same as {@code superType}. If {@code type} is a primitive type, return {@code true} only if both * {@code type} and {@code superType} are the exact same primitive type. *

*/ public static boolean isSuperType(Class superType, Class type) { return superType.isAssignableFrom(type); } /** * * @deprecated Use Annotations.get() instead */ @Deprecated public static Optional getAnnotation(Class type, Class annotationClass) { return Optional.fromNullable(type.getAnnotation(annotationClass)); } /** * * @deprecated Use Annotations.get() instead */ @Deprecated public static Optional getAnnotation(Field field, Class annotationClass) { return Optional.fromNullable(field.getAnnotation(annotationClass)); } public static List> getDeclarationHierarchy(Class type) { List> hierarchy = new ArrayList>(); Class declaringClass = type.getDeclaringClass(); if (declaringClass != null) { hierarchy.addAll(getDeclarationHierarchy(declaringClass)); } hierarchy.add(type); return hierarchy; } public static String getDeclarationPath(Class type) { List> hierarchy = getDeclarationHierarchy(type); List names = newArrayList(); for (Class element : hierarchy) { names.add(element.getSimpleName()); } return Joiner.on('.').join(names); } /** * Unconditionally attempt to get the value of this field on this bean. If the field is not accessible make it accessible, get the value, then revert the field back to being * inaccessible. */ public static Optional extractFieldValue(Field field, Object instance) { // Be thread safe synchronized (field) { // Preserve the original accessibility indicator boolean accessible = field.isAccessible(); // If it's not accessible, change it so it is if (!accessible) { field.setAccessible(true); } try { // Attempt to get the value of this field on the instance return fromNullable(field.get(instance)); } catch (IllegalAccessException e) { throw illegalState(e); } finally { // Always flip the accessible flag back to what it was before (if we need to) if (!accessible) { field.setAccessible(false); } } } } /** * Unconditionally attempt to get the value of this field on this bean. If the field is not accessible make it accessible, get the value, then revert the field back to being * inaccessible. * * @deprecated Use extractFieldValue instead */ @Deprecated public static Optional get(Field field, Object instance) { return get(field, instance); } /** * Unconditionally attempt to set a value on this field of this instance. If the field is not accessible, make it accessible, set the value, then revert the field to being * inaccessible. */ public static void set(Object instance, Field field, Object value) { // Be thread safe synchronized (field) { // Preserve the original accessibility indicator boolean accessible = field.isAccessible(); // If it's not accessible, change it so it is if (!accessible) { field.setAccessible(true); } try { // Attempt to set the value on this field of the instance field.set(instance, value); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } finally { // Always flip the accessible flag back to what it was before (if we need to) if (!accessible) { field.setAccessible(false); } } } } /** * Get fields declared directly on this type as an immutable set. */ public static Set getFields(Class type) { return ImmutableSet.copyOf(type.getDeclaredFields()); } /** * Get fields for a given type with the option to include all inherited fields * *

* NOTE: field.getName() is not necessarily unique for the elements in the set if includeInheritedFields is true *

*/ public static Set getFields(Class type, boolean includeInheritedFields) { if (includeInheritedFields) { return getAllFields(type); } else { return getFields(type); } } /** * Convert the set of fields into a Map keyed by field name. If there are fields that contain duplicate names in the set, which one makes it into the map is undefined * * @deprecated use getNameMap(List) instead */ @Deprecated public static Map getNameMap(Set fields) { Map map = Maps.newHashMap(); for (Field field : fields) { map.put(field.getName(), field); } return map; } /** * Convert the list of fields into a Map keyed by field name. If there are duplicate field names in the list, "last one in wins" */ public static Map getNameMap(List fields) { Map map = Maps.newHashMap(); for (Field field : fields) { map.put(field.getName(), field); } return map; } /** * Get a list of all fields contained anywhere in the type hierarchy keyed by field name. * * @throws IllegalArgumentException * if {@code type} contains duplicate field names */ public static Map getUniqueFieldNames(Class type) { Set fields = getAllFields(type); checkArgument(hasUniqueFieldNames(fields), "[%s] contains duplicate field names"); return getNameMap(Lists.newArrayList(fields)); } /** * Return the field corresponding * * @param type * @param fieldNames * @return */ public static Map getFields(Class type, Set fieldNames) { Map fields = Maps.newHashMap(); for (String fieldName : fieldNames) { try { fields.put(fieldName, type.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { throw new IllegalStateException(e); } catch (SecurityException e) { throw new IllegalStateException(e); } } return fields; } /** *

* Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable set *

* *

* NOTE: field.getName() is not necessarily unique for the elements in the set *

*/ public static Set getAllFields(Class type) { Set fields = Sets.newHashSet(); for (Class c = type; c != null; c = c.getSuperclass()) { Set set = getFields(c); fields.addAll(set); } return ImmutableSet.copyOf(fields); } /** *

* Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable list *

* *

* NOTE: field.getName() is not necessarily unique for the elements in the list *

*/ public static List getAllFieldsList(Class type) { List fields = newArrayList(); for (Class c = type; c != null; c = c.getSuperclass()) { Set set = getFields(c); fields.addAll(set); } return ImmutableList.copyOf(Lists.reverse(fields)); } /** * Return true if every single field in the recursive type hiearchy has a unique name, false otherwise */ public static boolean hasUniqueFieldNames(Class type) { return hasUniqueFieldNames(getAllFields(type)); } /** * Return true if the fields in this set can be uniquely represented by field name alone */ public static boolean hasUniqueFieldNames(Set fields) { return getNameMap(newArrayList(fields)).size() == fields.size(); } public static boolean hasUniqueFieldNames(List fields) { return getNameMap(fields).size() == fields.size(); } @SuppressWarnings("unchecked") public static Map describe(Object bean) { try { return BeanUtils.describe(bean); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } } public static void copyProperty(Object bean, String name, Object value) { try { BeanUtils.copyProperty(bean, name, value); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } public static Object invokeMethod(Class targetClass, String targetMethod, Object... arguments) { MethodInvoker invoker = new MethodInvoker(); invoker.setTargetClass(targetClass); invoker.setTargetMethod(targetMethod); invoker.setArguments(arguments); return invoke(invoker); } public static Object invokeMethod(Object targetObject, String targetMethod, Object... arguments) { MethodInvoker invoker = new MethodInvoker(); invoker.setTargetObject(targetObject); invoker.setTargetMethod(targetMethod); invoker.setArguments(arguments); return invoke(invoker); } public static Object invoke(MethodInvoker invoker) { try { invoker.prepare(); return invoker.invoke(); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } } public static Class getClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } } @SuppressWarnings("unchecked") public static Class getTypedClass(String className) { try { return (Class) Class.forName(className); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } } public static T newInstance(String className) { @SuppressWarnings("unchecked") Class clazz = (Class) getClass(className); return (T) newInstance(clazz); } public static T newInstance(Class instanceClass) { try { return (T) instanceClass.newInstance(); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Unexpected error", e); } catch (InstantiationException e) { throw new IllegalArgumentException("Unexpected error", e); } } }