/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.deltaspike.data.impl.property;
import java.beans.Introspector;
import java.io.Serializable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;
/**
* Utility class for working with JDK Reflection and also CDI's
* {@link javax.enterprise.inject.spi.Annotated} metadata.
*/
public class Reflections
{
private Reflections()
{
}
/**
* <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/>
* <p>
* {@link Class#cast(Object)} should be used if possible
* </p>
*
* @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
* @see Class#cast(Object)
*/
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj)
{
return (T) obj;
}
/**
* 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;
}
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();
}
/**
* Set the accessibility flag on the {@link AccessibleObject} as described in
* {@link AccessibleObject#setAccessible(boolean)} within the context of a {@link PrivilegedAction}.
*
* @param <A>
* member the accessible object type
* @param member
* the accessible object
* @return the accessible object after the accessible flag has been altered
*/
public static <A extends AccessibleObject> A setAccessible(final A member)
{
AccessController.doPrivileged(new PrivilegedAction<Void>()
{
@Override
public Void run()
{
member.setAccessible(true);
return null;
}
});
return member;
}
/**
* <p>
* Invoke the specified method on the provided instance, passing any additional arguments included in this method as
* arguments to the specified method.
* </p>
* <p/>
* <p>
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the expected return type set to
* {@link Object} and no change to the method's accessibility.
* </p>
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static Object invokeMethod(Method method, Object instance, Object... args)
{
return invokeMethod(false, method, Object.class, instance, args);
}
/**
* <p>
* Invoke the specified method on the provided instance, passing any additional arguments included in this method as
* arguments to the specified method.
* </p>
* <p/>
* <p>
* This method attempts to set the accessible flag of the method in a {@link PrivilegedAction} before invoking the
* method if the first argument is true.
* </p>
* <p/>
* <p>
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the expected return type set to
* {@link Object}.
* </p>
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static Object invokeMethod(boolean setAccessible, Method method, Object instance, Object... args)
{
return invokeMethod(setAccessible, method, Object.class, instance, args);
}
/**
* <p>
* Invoke the specified method on the provided instance, passing any additional arguments included in this method as
* arguments to the specified method.
* </p>
* <p/>
* <p>
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the expected return type set to
* {@link Object} and honoring the accessibility of the method.
* </p>
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static <T> T invokeMethod(Method method, Class<T> expectedReturnType, Object instance, Object... args)
{
return invokeMethod(false, method, expectedReturnType, 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/>
* <p>
* This method wraps {@link Method#invoke(Object, Object...)}, converting the checked exceptions that
* {@link Method#invoke(Object, Object...)} specifies to runtime exceptions.
* </p>
* <p/>
* <p>
* If instructed, this method attempts to set the accessible flag of the method in a {@link PrivilegedAction} before
* invoking the method.
* </p>
*
* @param setAccessible
* flag indicating whether method should first be set as accessible
* @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(boolean setAccessible, Method method, Class<T> expectedReturnType,
Object instance, Object... args)
{
if (setAccessible && !method.isAccessible())
{
setAccessible(method);
}
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/>
* <p>
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#setFieldValue(boolean, Method, Class, Object, Object...)}, honoring the accessibility of the
* field.
* </p>
*/
public static void setFieldValue(Field field, Object instance, Object value)
{
setFieldValue(false, field, instance, value);
}
/**
* <p>
* Sets the value of a field on the instance to the specified value.
* </p>
* <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>
* <p/>
* <p>
* If instructed, this method attempts to set the accessible flag of the method in a {@link PrivilegedAction} before
* invoking the method.
* </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(boolean setAccessible, Field field, Object instance, Object value)
{
if (setAccessible && !field.isAccessible())
{
setAccessible(field);
}
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/>
* <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 Reflections.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;
}
}
/**
* 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);
}
/**
* Gets the property name from a getter method.
* <p/>
* We extend JavaBean conventions, allowing the getter method to have parameters
*
* @param method
* The getter method
* @return The name of the property. Returns null if method wasn't JavaBean getter-styled
*/
public static String getPropertyName(Method method)
{
String methodName = method.getName();
if (methodName.matches("^(get).*"))
{
return Introspector.decapitalize(methodName.substring(3));
}
else if (methodName.matches("^(is).*"))
{
return Introspector.decapitalize(methodName.substring(2));
}
else
{
return null;
}
}
}