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

org.omnifaces.utils.reflect.Reflections Maven / Gradle / Ivy

There is a newer version: 0.14
Show newest version
/*
 * Copyright 2018 OmniFaces
 *
 * 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.
 */
package org.omnifaces.utils.reflect;

import static java.lang.String.format;
import static java.util.Collections.unmodifiableList;
import static java.util.logging.Level.FINEST;
import static org.omnifaces.utils.Lang.capitalize;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Logger;

public final class Reflections {

	private static final Logger logger = Logger.getLogger(Reflections.class.getName());

	private static final String ERROR_LOAD_CLASS = "Cannot load class '%s'.";
	private static final String ERROR_INSTANTIATE = "Cannot instantiate class '%s'.";
	private static final String ERROR_ACCESS_FIELD = "Cannot access field '%s' of class '%s'.";
	private static final String ERROR_MODIFY_FIELD = "Cannot modify field '%s' of class '%s' with value %s.";
	private static final String ERROR_INVOKE_METHOD = "Cannot invoke method '%s' of class '%s' with arguments %s.";
	private static final String ERROR_MAP_FIELD = "Cannot map field '%s' from %s to %s.";

	private Reflections() {
		// Hide constructor.
	}

	/**
	 * Finds a field based on the field name.
	 * @param base the object in which the field is to be found
	 * @param fieldName The name the field to be found.
	 * @return The found field, if any.
	 */
	public static Optional findField(Object base, String fieldName) {
		return findField(base != null ? base.getClass() : null, fieldName);
	}

	/**
	 * Finds a field based on the field name.
	 * @param clazz The class object for which the field is to be found.
	 * @param fieldName The name the field to be found.
	 * @return The found field, if any.
	 */
	public static Optional findField(Class clazz, String fieldName) {
		for (Class cls = clazz; cls != null; cls = cls.getSuperclass()) {
			for (Field field : cls.getDeclaredFields()) {
				if (field.getName().equals(fieldName)) {
					return Optional.of(field);
				}
			}
		}

		return Optional.empty();
	}

	/**
	 * Finds all fields having all of given annotations.
	 * @param clazz The class object for which the annotated fields is to be found.
	 * @param annotations The annotations of the field.
	 * @return All fields having all of given annotations.
	 * @throws IllegalArgumentException When no annotations are specified.
	 */
	@SafeVarargs
	public static List listAnnotatedFields(Class clazz, Class... annotations) {
		if (annotations.length == 0) {
			throw new IllegalArgumentException("annotations");
		}

		List annotatedFields = new ArrayList<>();

		for (Class cls = clazz; cls != null; cls = cls.getSuperclass()) {
			for (Field field : cls.getDeclaredFields()) {
				if (Arrays.stream(annotations).allMatch(field::isAnnotationPresent)) {
					annotatedFields.add(field);
				}
			}
		}

		return annotatedFields;
	}

	/**
	 * Finds all enum fields having all of given annotations.
	 * @param clazz The class object for which the annotated enum fields is to be found.
	 * @param annotations The annotations of the field.
	 * @return All enum fields having all of given annotations.
	 * @throws IllegalArgumentException When no annotations are specified.
	 */
	@SafeVarargs
	@SuppressWarnings("unchecked")
	public static List>> listAnnotatedEnumFields(Class clazz, Class... annotations) {
		if (annotations.length == 0) {
			throw new IllegalArgumentException("annotations");
		}

		List>> annotatedEnumFields = new ArrayList<>();

		for (Field field : listAnnotatedFields(clazz, annotations)) {
			if (field.getType().isEnum()) {
				annotatedEnumFields.add((Class>) field.getType());
			}
			else if (field.getGenericType() instanceof ParameterizedType) {
				for (Type typeArgument : ((ParameterizedType) field.getGenericType()).getActualTypeArguments()) {
					if (typeArgument instanceof Class && ((Class) typeArgument).isEnum()) {
						annotatedEnumFields.add((Class>) typeArgument);
					}
				}
			}
		}

		return annotatedEnumFields;
	}

	/**
	 * Finds a method based on the method name, amount of parameters and limited typing and returns null
	 * is none is found.
	 * 

* Note that this supports overloading, but a limited one. Given an actual parameter of type Long, this will select * a method accepting Number when the choice is between Number and a non-compatible type like String. However, * it will NOT select the best match if the choice is between Number and Long. * * @param base the object in which the method is to be found * @param methodName name of the method to be found * @param params the method parameters * @return The found method, if any. */ public static Optional findMethod(Object base, String methodName, Object... params) { List methods = new ArrayList<>(); for (Class cls = base.getClass(); cls != null; cls = cls.getSuperclass()) { for (Method method : cls.getDeclaredMethods()) { if (method.getName().equals(methodName) && method.getParameterTypes().length == params.length) { methods.add(method); } } } if (methods.size() == 1) { return Optional.of(methods.get(0)); } else { return Optional.ofNullable(closestMatchingMethod(methods, params)); // Overloaded methods were found. Try to find closest match. } } private static Method closestMatchingMethod(List methods, Object... params) { for (Method method : methods) { Class[] candidateParams = method.getParameterTypes(); boolean match = true; for (int i = 0; i < params.length; i++) { if (!candidateParams[i].isInstance(params[i])) { match = false; break; } } // If all candidate parameters were expected and for none of them the actual parameter was NOT an instance, we have a match. if (match) { return method; } // Else, at least one parameter was not an instance. Go ahead a test then next methods. } return null; } /** * Returns the class object associated with the given class name, using the context class loader and if * that fails the defining class loader of the current class. * @param The expected class type. * @param className Fully qualified class name of the class for which a class object needs to be created. * @return The class object associated with the given class name. * @throws IllegalStateException If the class cannot be loaded. * @throws ClassCastException When T is of wrong type. */ @SuppressWarnings("unchecked") public static Class toClass(String className) { try { return (Class) (Class.forName(className, true, Thread.currentThread().getContextClassLoader())); } catch (Exception e) { try { return (Class) Class.forName(className); } catch (Exception ignore) { logger.log(FINEST, "Ignoring thrown exception; previous exception will be rethrown instead.", ignore); // Just continue to IllegalStateException on original ClassNotFoundException. } throw new IllegalStateException(format(ERROR_LOAD_CLASS, className), e); } } /** * Returns the class object associated with the given class name, using the context class loader and if * that fails the defining class loader of the current class. If the class cannot be loaded, then return null * instead of throwing illegal state exception. * @param The expected class type. * @param className Fully qualified class name of the class for which a class object needs to be created. * @return The found class object associated with the given class name, if any. * @throws ClassCastException When T is of wrong type. */ public static Optional> findClass(String className) { try { return Optional.of(toClass(className)); } catch (Exception ignore) { logger.log(FINEST, "Ignoring thrown exception; the sole intent is to return null instead.", ignore); return Optional.ofNullable(null); } } /** * Finds a constructor based on the given parameter types and returns null is none is found. * @param The generic object type. * @param clazz The class object for which the constructor is to be found. * @param parameterTypes The desired method parameter types. * @return The found constructor, if any. */ public static Optional> findConstructor(Class clazz, Class... parameterTypes) { try { return Optional.of(clazz.getConstructor(parameterTypes)); } catch (Exception ignore) { logger.log(FINEST, "Ignoring thrown exception; the sole intent is to return null instead.", ignore); return Optional.ofNullable(null); } } /** * Returns a new instance of the given class name using the default constructor. * @param The expected return type. * @param className Fully qualified class name of the class for which an instance needs to be created. * @return A new instance of the given class name using the default constructor. * @throws IllegalStateException If the class cannot be loaded. * @throws ClassCastException When T is of wrong type. */ @SuppressWarnings("unchecked") public static T instantiate(String className) { return (T) instantiate(toClass(className)); } /** * Returns a new instance of the given class object using the default constructor. * @param The generic object type. * @param clazz The class object for which an instance needs to be created. * @return A new instance of the given class object using the default constructor. * @throws IllegalStateException If the class cannot be found, or cannot be instantiated, or when a security manager * prevents this operation. */ public static T instantiate(Class clazz) { try { return clazz.newInstance(); } catch (Exception e) { throw new IllegalStateException(format(ERROR_INSTANTIATE, clazz), e); } } /** * Returns the value of the field of the given instance on the given field name. * @param The expected return type. * @param instance The instance to access the given field on. * @param fieldName The name of the field to be accessed on the given instance. * @return The value of the field of the given instance on the given field name. * @throws ClassCastException When T is of wrong type. * @throws IllegalStateException If the field cannot be accessed. */ public static T accessField(Object instance, String fieldName) { try { Field field = findField(instance, fieldName).orElseThrow(NoSuchFieldException::new); return accessField(instance, field); } catch (Exception e) { throw new IllegalStateException(format(ERROR_ACCESS_FIELD, fieldName, instance != null ? instance.getClass() : null), e); } } /** * Returns the value of the field of the given instance on the given field name. * @param The expected return type. * @param instance The instance to access the given field on. * @param field The field to be accessed on the given instance. * @return The value of the field of the given instance on the given field name. * @throws ClassCastException When T is of wrong type. * @throws IllegalStateException If the field cannot be accessed. */ @SuppressWarnings("unchecked") public static T accessField(Object instance, Field field) { try { field.setAccessible(true); return (T) field.get(instance); } catch (Exception e) { throw new IllegalStateException(format(ERROR_ACCESS_FIELD, field != null ? field.getName() : null, instance != null ? instance.getClass() : null), e); } } /** * Modifies the value of the field of the given instance on the given field name with the given value. * @param The field type. * @param instance The instance to access the given field on. * @param fieldName The name of the field to be accessed on the given instance. * @param value The new value of the field of the given instance on the given field name. * @return The old value of the field of the given instance on the given field name. * @throws ClassCastException When T is of wrong type. * @throws IllegalStateException If the field cannot be modified. */ public static T modifyField(Object instance, String fieldName, T value) { try { Field field = findField(instance, fieldName).orElseThrow(NoSuchFieldException::new); return modifyField(instance, field, value); } catch (Exception e) { throw new IllegalStateException(format(ERROR_MODIFY_FIELD, fieldName, instance != null ? instance.getClass() : null), e); } } /** * Modifies the value of the given field of the given instance with the given value. * @param The field type. * @param instance The instance to access the given field on. * @param field The field to be accessed on the given instance. * @param value The new value of the given field of the given instance. * @return The old value of the given field of the given instance. * @throws ClassCastException When T is of wrong type. * @throws IllegalStateException If the field cannot be modified. */ @SuppressWarnings("unchecked") public static T modifyField(Object instance, Field field, T value) { try { field.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); Object oldValue = field.get(instance); field.set(instance, value); return (T) oldValue; } catch (Exception e) { throw new IllegalStateException(format(ERROR_MODIFY_FIELD, field != null ? field.getName() : null, instance != null ? instance.getClass() : null, value), e); } } /** * Invoke a method of the given instance on the given method name with the given parameters and return the result. *

* Note: the current implementation assumes for simplicity that no one of the given parameters is null. If one of * them is still null, a NullPointerException will be thrown. * @param The expected return type. * @param instance The instance to invoke the given method on. * @param methodName The name of the method to be invoked on the given instance. * @param parameters The method parameters, if any. * @return The result of the method invocation, if any. * @throws IllegalStateException If the method cannot be invoked. * @throws ClassCastException When T is of wrong type. */ public static T invokeMethod(Object instance, String methodName, Object... parameters) { try { Method method = findMethod(instance, methodName, parameters).orElseThrow(NoSuchMethodException::new); return invokeMethod(instance, method, parameters); } catch (Exception e) { throw new IllegalStateException(format(ERROR_INVOKE_METHOD, methodName, instance != null ? instance.getClass() : null, Arrays.toString(parameters)), e); } } /** * Invoke given method of the given instance with the given parameters and return the result. * @param The expected return type. * @param instance The instance to invoke the given method on. * @param method The method to be invoked on the given instance. * @param parameters The method parameters, if any. * @return The result of the method invocation, if any. * @throws IllegalStateException If the method cannot be invoked. * @throws ClassCastException When T is of wrong type. */ @SuppressWarnings("unchecked") public static T invokeMethod(Object instance, Method method, Object... parameters) { try { method.setAccessible(true); return (T) method.invoke(instance, parameters); } catch (Exception e) { throw new IllegalStateException(format(ERROR_INVOKE_METHOD, method != null ? method.getName() : null, instance != null ? instance.getClass() : null, Arrays.toString(parameters)), e); } } /** * Invoke getter method of the given instance on the given property name and return the result. * @param The expected return type. * @param instance The instance to invoke the given getter method on. * @param propertyName The property name of the getter method to be invoked on the given instance. * @return The result of the method invocation, if any. * @throws IllegalStateException If the getter method cannot be invoked. * @throws ClassCastException When T is of wrong type. */ public static T invokeGetter(Object instance, String propertyName) { String capitalizedPropertyName = capitalize(propertyName); Optional booleanGetter = findMethod(instance, "is" + capitalizedPropertyName); if (booleanGetter.isPresent()) { return invokeMethod(instance, booleanGetter.get()); } else { return invokeMethod(instance, "get" + capitalizedPropertyName); } } /** * Invoke setter method of the given instance on the given property name with the given property value and return the result. * @param instance The instance to invoke the given setter method on. * @param propertyName The property name of the setter method to be invoked on the given instance. * @param propertyValue The property value to be set. * @throws IllegalStateException If the setter method cannot be invoked. */ public static void invokeSetter(Object instance, String propertyName, Object propertyValue) { String capitalizedPropertyName = capitalize(propertyName); invokeMethod(instance, "set" + capitalizedPropertyName, propertyValue); } /** * Map given member from given object to given object. * * @param The from/target object type. * @param member Member to be mapped. * @param from Source object. * @param to Target object. */ public static void map(Member member, T from, T to) { if (member instanceof Field) { Field field = (Field) member; try { field.setAccessible(true); field.set(to, field.get(from)); } catch (Exception e) { throw new IllegalStateException(format(ERROR_MAP_FIELD, field.getName(), from, to), e); } } else { throw new UnsupportedOperationException("Not implemented yet for " + member); } } /** * Returns the actual type arguments of the given subclass against which are declared on the given superclass. * The returned list is ordered. * @param The generic superclass type. * @param subclass The subclass to get the actual type arguments from. * @param superclass The superclass where the generic type arguments are declared. * @return The actual type arguments of the given subclass against which are declared on the given superclass. */ public static List> getActualTypeArguments(Class subclass, Class superclass) { Map, Type> typeMapping = new HashMap<>(); Type actualType = subclass.getGenericSuperclass(); while (!(actualType instanceof ParameterizedType) || !superclass.equals(((ParameterizedType) actualType).getRawType())) { if (actualType instanceof ParameterizedType) { Class rawType = (Class) ((ParameterizedType) actualType).getRawType(); TypeVariable[] typeParameters = rawType.getTypeParameters(); for (int i = 0; i < typeParameters.length; i++) { Type typeArgument = ((ParameterizedType) actualType).getActualTypeArguments()[i]; typeMapping.put(typeParameters[i], typeArgument instanceof TypeVariable ? typeMapping.get(typeArgument) : typeArgument); } actualType = rawType; } actualType = ((Class) actualType).getGenericSuperclass(); } List> actualTypeArguments = new ArrayList<>(); for (Type actualTypeArgument : ((ParameterizedType) actualType).getActualTypeArguments()) { actualTypeArguments.add((Class) (actualTypeArgument instanceof TypeVariable ? typeMapping.get(actualTypeArgument) : actualTypeArgument)); } return unmodifiableList(actualTypeArguments); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy