/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.seam.solder.reflection;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
/**
* Utility class for working with JDK Reflection and also CDI's
* {@link Annotated} metadata.
*
* @author Stuart Douglas
* @author Pete Muir
*
*/
public class Reflections
{
/**
* An empty array of type {@link Annotation}, useful converting lists to
* arrays.
*/
public static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
/**
* An empty array of type {@link Object}, useful for converting lists to
* arrays.
*/
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* <p>
* Perform a runtime cast. Similar to {@link Class#cast(Object)}, but useful
* when you do not have a {@link Class} object for type you wish to cast to.
* </p>
*
* <p>
* {@link Class#cast(Object)} should be used if possible
* </p>
*
* @see Class#cast(Object)
*
* @param <T> the type to cast to
* @param obj the object to perform the cast on
* @return the casted object
* @throws ClassCastException if the type T is not a subtype of the object
*/
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj)
{
return (T) obj;
}
/**
* Get all the declared fields on the class hierarchy. This <b>will</b>
* return overridden fields.
*
* @param clazz The class to search
* @return the set of all declared fields or an empty set if there are none
*/
public static Set<Field> getAllDeclaredFields(Class<?> clazz)
{
HashSet<Field> fields = new HashSet<Field>();
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
for (Field a : c.getDeclaredFields())
{
fields.add(a);
}
}
return fields;
}
/**
* Search the class hierarchy for a field with the given name. Will return
* the nearest match, starting with the class specified and searching up the
* hierarchy.
*
* @param clazz The class to search
* @param name The name of the field to search for
* @return The field found, or null if no field is found
*/
public static Field findDeclaredField(Class<?> clazz, String name)
{
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
try
{
return c.getDeclaredField(name);
}
catch (NoSuchFieldException e)
{
// No-op, we continue looking up the class hierarchy
}
}
return null;
}
/**
* Search the annotatedType for the field, returning the
* {@link AnnotatedField}
*
* @param annotatedType The annotatedType to search
* @param field the field to search for
* @return The {@link AnnotatedField} found, or null if no field is found
*/
public static <X> AnnotatedField<? super X> getField(AnnotatedType<X> annotatedType, Field field)
{
for (AnnotatedField<? super X> annotatedField : annotatedType.getFields())
{
if (annotatedField.getDeclaringType().getJavaClass().equals(field.getDeclaringClass()) && annotatedField.getJavaMember().getName().equals(field.getName()))
{
return annotatedField;
}
}
return null;
}
/**
* Search for annotations with the specified meta annotation type
*
* @param annotations The annotation set to search
* @param metaAnnotationType The type of the meta annotation to search for
* @return The set of annotations with the specified meta annotation, or an
* empty set if none are found
*/
public static Set<Annotation> getAnnotationsWithMetaAnnotation(Set<Annotation> annotations, Class<? extends Annotation> metaAnnotationType)
{
Set<Annotation> set = new HashSet<Annotation>();
for (Annotation annotation : annotations)
{
if (annotation.annotationType().isAnnotationPresent(metaAnnotationType))
{
set.add(annotation);
}
}
return set;
}
/**
* Extract any qualifiers from the set of annotations
*
* @param annotations The set of annotations to search
* @param beanManager The beanManager to use to establish if an annotation is
* a qualifier
* @return The qualifiers present in the set, or an empty set if there are
* none
*/
public static Set<Annotation> getQualifiers(Set<Annotation> annotations, BeanManager beanManager)
{
Set<Annotation> set = new HashSet<Annotation>();
for (Annotation annotation : annotations)
{
if (beanManager.isQualifier(annotation.annotationType()))
{
set.add(annotation);
}
}
return set;
}
/**
* Determine if a method exists in a specified class hierarchy
*
* @param clazz The class to search
* @param name The name of the method
* @return true if a method is found, otherwise false
*/
public static boolean methodExists(Class<?> clazz, String name)
{
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
for (Method m : c.getDeclaredMethods())
{
if (m.getName().equals(name))
{
return true;
}
}
}
return false;
}
/**
* Get all the declared methods on the class hierarchy. This <b>will</b>
* return overridden methods.
*
* @param clazz The class to search
* @return the set of all declared methods or an empty set if there are none
*/
public static Set<Method> getAllDeclaredMethods(Class<?> clazz)
{
HashSet<Method> methods = new HashSet<Method>();
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
for (Method a : c.getDeclaredMethods())
{
methods.add(a);
}
}
return methods;
}
/**
* Search the class hierarchy for a method with the given name and arguments.
* Will return the nearest match, starting with the class specified and
* searching up the hierarchy.
*
* @param clazz The class to search
* @param name The name of the method to search for
* @param args The arguments of the method to search for
* @return The method found, or null if no method is found
*/
public static Method findDeclaredMethod(Class<?> clazz, String name, Class<?>... args)
{
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
try
{
return c.getDeclaredMethod(name, args);
}
catch (NoSuchMethodException e)
{
// No-op, continue the search
}
}
return null;
}
/**
* Search the class hierarchy for a constructor with the given arguments.
* Will return the nearest match, starting with the class specified and
* searching up the hierarchy.
*
* @param clazz The class to search
* @param args The arguments of the constructor to search for
* @return The constructor found, or null if no constructor is found
*/
public static Constructor<?> findDeclaredConstructor(Class<?> clazz, Class<?>... args)
{
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
try
{
return c.getDeclaredConstructor(args);
}
catch (NoSuchMethodException e)
{
// No-op, continue the search
}
}
return null;
}
/**
* Get all the declared constructors on the class hierarchy. This <b>will</b>
* return overridden constructors.
*
* @param clazz The class to search
* @return the set of all declared constructors or an empty set if there are
* none
*/
public static Set<Constructor<?>> getAllDeclaredConstructors(Class<?> clazz)
{
HashSet<Constructor<?>> constructors = new HashSet<Constructor<?>>();
for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass())
{
for (Constructor<?> constructor : c.getDeclaredConstructors())
{
constructors.add(constructor);
}
}
return constructors;
}
/**
* Get the type of the member
*
* @param member The member
* @return The type of the member
* @throws UnsupportedOperationException if the member is not a field,
* method, or constructor
*/
public static Class<?> getMemberType(Member member)
{
if (member instanceof Field)
{
return ((Field) member).getType();
}
else if (member instanceof Method)
{
return ((Method) member).getReturnType();
}
else if (member instanceof Constructor<?>)
{
return ((Constructor<?>) member).getDeclaringClass();
}
else
{
throw new UnsupportedOperationException("Cannot operate on a member of type " + member.getClass());
}
}
/**
* <p>
* Loads and initializes a class for the given name.
* </p>
*
* <p>
* If the Thread Context Class Loader is available, it will be used,
* otherwise the classloader used to load {@link Reflections} will be used
* </p>
*
* <p>
* It is also possible to specify additional classloaders to attempt to load
* the class with. If the first attempt fails, then these additional loaders
* are tried in order.
* </p>
*
* @param name the name of the class to load
* @param loaders additional classloaders to use to attempt to load the class
* @return the class object
* @throws ClassNotFoundException if the class cannot be found
*/
public static Class<?> classForName(String name, ClassLoader... loaders) throws ClassNotFoundException
{
try
{
if (Thread.currentThread().getContextClassLoader() != null)
{
return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
}
else
{
return Class.forName(name);
}
}
catch (ClassNotFoundException e)
{
for (ClassLoader l : loaders)
{
try
{
return Class.forName(name, true, l);
}
catch (ClassNotFoundException ex)
{
}
}
}
if (Thread.currentThread().getContextClassLoader() != null)
{
throw new ClassNotFoundException("Could not load class " + name + " with the context class loader " + Thread.currentThread().getContextClassLoader().toString() + " or any of the additional ClassLoaders: " + Arrays.toString(loaders));
}
else
{
throw new ClassNotFoundException("Could not load class " + name + " using Class.forName or using any of the additional ClassLoaders: " + Arrays.toString(loaders));
}
}
private static String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args)
{
StringBuilder message = new StringBuilder(String.format("Exception invoking method [%s] on object [%s], using arguments [", method.getName(), obj));
if (args != null)
for (int i = 0; i < args.length; i++)
message.append((i > 0 ? "," : "") + args[i]);
message.append("]");
return message.toString();
}
/**
* <p>
* Invoke the method on the instance, with any arguments specified.
* </p>
*
* <p>
* This method wraps {@link Method#invoke(Object, Object...)}, converting the
* checked exceptions that {@link Method#invoke(Object, Object...)} specifies
* to runtime exceptions.
* </p>
*
* @param method the method to invoke
* @param instance the instance to invoke the method or null if the method is
* static
* @param args the arguments to the method
* @return the result of invoking the method, or null if the method's return
* type is void
* @throws RuntimeException if this <code>Method</code> object enforces Java
* language access control and the underlying method is
* inaccessible or if the underlying method throws an exception or
* if the initialization provoked by this method fails.
* @throws IllegalArgumentException if the method is an instance method and
* the specified <code>instance</code> argument is not an instance of the class
* or interface declaring the underlying method (or of a subclass
* or implementor thereof); if the number of actual and formal
* parameters differ; if an unwrapping conversion for primitive
* arguments fails; or if, after possible unwrapping, a parameter
* value cannot be converted to the corresponding formal parameter
* type by a method invocation conversion.
* @throws NullPointerException if the specified <code>instance</code> is null and the
* method is an instance method.
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
*
* @see Method#invoke(Object, Object...)
*
*/
public static Object invokeMethod(Method method, Object instance, Object... args)
{
return invokeMethod(method, Object.class, instance, args);
}
/**
* <p>
* Invoke the method on the instance, with any arguments specified, casting
* the result of invoking the method to the expected return type.
* </p>
*
* <p>
* This method wraps {@link Method#invoke(Object, Object...)}, converting the
* checked exceptions that {@link Method#invoke(Object, Object...)} specifies
* to runtime exceptions.
* </p>
*
* @param method the method to invoke
* @param instance the instance to invoke the method
* @param args the arguments to the method
* @return the result of invoking the method, or null if the method's return
* type is void
* @throws RuntimeException if this <code>Method</code> object enforces Java
* language access control and the underlying method is
* inaccessible or if the underlying method throws an exception or
* if the initialization provoked by this method fails.
* @throws IllegalArgumentException if the method is an instance method and
* the specified <code>instance</code> argument is not an instance of the class
* or interface declaring the underlying method (or of a subclass
* or implementor thereof); if the number of actual and formal
* parameters differ; if an unwrapping conversion for primitive
* arguments fails; or if, after possible unwrapping, a parameter
* value cannot be converted to the corresponding formal parameter
* type by a method invocation conversion.
* @throws NullPointerException if the specified <code>instance</code> is null and the
* method is an instance method.
* @throws ClassCastException if the result of invoking the method cannot be
* cast to the expectedReturnType
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
*
* @see Method#invoke(Object, Object...)
*
*/
public static <T> T invokeMethod(Method method, Class<T> expectedReturnType, Object instance, Object... args)
{
try
{
return expectedReturnType.cast(method.invoke(instance, args));
}
catch (IllegalAccessException ex)
{
throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex);
}
catch (IllegalArgumentException ex)
{
throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, instance, args), ex);
}
catch (InvocationTargetException ex)
{
throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex.getCause());
}
catch (NullPointerException ex)
{
NullPointerException ex2 = new NullPointerException(buildInvokeMethodErrorMessage(method, instance, args));
ex2.initCause(ex.getCause());
throw ex2;
}
catch (ExceptionInInitializerError e)
{
ExceptionInInitializerError e2 = new ExceptionInInitializerError(buildInvokeMethodErrorMessage(method, instance, args));
e2.initCause(e.getCause());
throw e2;
}
}
/**
* <p>
* Set the value of a field on the instance to the specified value.
* </p>
*
* <p>
* This method wraps {@link Field#set(Object, Object)}, converting the
* checked exceptions that {@link Field#set(Object, Object)} specifies to
* runtime exceptions.
* </p>
*
* @param field the field on which to operate, or null if the field is static
* @param instance the instance on which the field value should be set upon
* @param value the value to set the field to
* @throws RuntimeException if the underlying field is inaccessible.
* @throws IllegalArgumentException if the specified <code>instance</code> is not an
* instance of the class or interface declaring the underlying
* field (or a subclass or implementor thereof), or if an
* unwrapping conversion fails.
* @throws NullPointerException if the specified <code>instance</code> is null and the field
* is an instance field.
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
*
* @see Field#set(Object, Object)
*
*/
public static void setFieldValue(Field field, Object instance, Object value)
{
try
{
field.set(instance, value);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(buildSetFieldValueErrorMessage(field, instance, value), e);
}
catch (NullPointerException ex)
{
NullPointerException ex2 = new NullPointerException(buildSetFieldValueErrorMessage(field, instance, value));
ex2.initCause(ex.getCause());
throw ex2;
}
catch (ExceptionInInitializerError e)
{
ExceptionInInitializerError e2 = new ExceptionInInitializerError(buildSetFieldValueErrorMessage(field, instance, value));
e2.initCause(e.getCause());
throw e2;
}
}
private static String buildSetFieldValueErrorMessage(Field field, Object obj, Object value)
{
return String.format("Exception setting [%s] field on object [%s] to value [%s]", field.getName(), obj, value);
}
private static String buildGetFieldValueErrorMessage(Field field, Object obj)
{
return String.format("Exception reading [%s] field from object [%s].", field.getName(), obj);
}
public static Object getFieldValue(Field field, Object instance)
{
return getFieldValue(field, instance, Object.class);
}
/**
* <p>
* Get the value of the field, on the specified instance, casting the value
* of the field to the expected type.
* </p>
*
* <p>
* This method wraps {@link Field#get(Object)}, converting the checked
* exceptions that {@link Field#get(Object)} specifies to runtime exceptions.
* </p>
*
* @param <T> the type of the field's value
* @param field the field to operate on
* @param instance the instance from which to retrieve the value
* @param expectedType the expected type of the field's value
* @return the value of the field
* @throws RuntimeException if the underlying field is inaccessible.
* @throws IllegalArgumentException if the specified <code>instance</code> is not an
* instance of the class or interface declaring the underlying
* field (or a subclass or implementor thereof).
* @throws NullPointerException if the specified <code>instance</code> is null and the field
* is an instance field.
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
*/
public static <T> T getFieldValue(Field field, Object instance, Class<T> expectedType)
{
try
{
return expectedType.cast(field.get(instance));
}
catch (IllegalAccessException e)
{
throw new RuntimeException(buildGetFieldValueErrorMessage(field, instance), e);
}
catch (NullPointerException ex)
{
NullPointerException ex2 = new NullPointerException(buildGetFieldValueErrorMessage(field, instance));
ex2.initCause(ex.getCause());
throw ex2;
}
catch (ExceptionInInitializerError e)
{
ExceptionInInitializerError e2 = new ExceptionInInitializerError(buildGetFieldValueErrorMessage(field, instance));
e2.initCause(e.getCause());
throw e2;
}
}
/**
* Extract the raw type, given a type.
*
* @param <T> the type
* @param type the type to extract the raw type from
* @return the raw type, or null if the raw type cannot be determined.
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getRawType(Type type)
{
if (type instanceof Class<?>)
{
return (Class<T>) type;
}
else if (type instanceof ParameterizedType)
{
if (((ParameterizedType) type).getRawType() instanceof Class<?>)
{
return (Class<T>) ((ParameterizedType) type).getRawType();
}
}
return null;
}
/**
* Check if a class is serializable.
*
* @param clazz The class to check
* @return true if the class implements serializable or is a primitive
*/
public static boolean isSerializable(Class<?> clazz)
{
return clazz.isPrimitive() || Serializable.class.isAssignableFrom(clazz);
}
private Reflections()
{
}
}