/******************************************************************************
* Copyright (C) 2014 Yevgeny Krasik *
* *
* 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 com.github.ykrasik.jaci.reflection;
import com.github.ykrasik.jaci.util.exception.SneakyException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Objects;
/**
* Utilities for dealing with reflection.
*
* @author Yevgeny Krasik
*/
public final class ReflectionUtils {
private static final Class<?>[] NO_ARGS_TYPE = {};
private static final Object[] NO_ARGS = {};
private ReflectionUtils() { }
private static ReflectionAccessor accessor;
/**
* Set the current instance of a {@link ReflectionAccessor} to be used for all reflection calls.
* Must be called for any reflection operations to become possible.
*
* @param accessor {@link ReflectionAccessor} to be used.
*/
public static void setReflectionAccessor(ReflectionAccessor accessor) {
ReflectionUtils.accessor = Objects.requireNonNull(accessor, "accessor");
}
/**
* Creates an instance of this class through reflection. Class must have a no-args constructor.
* Any exceptions thrown during the process will be re-thrown as unchecked.
*
* @param clazz Class to instantiate.
* @return An instance of the provided class.
* @throws RuntimeException If an error occurred while instantiating an object of the class (for example, if the
* class doesn't have a no-args constructor).
*/
public static Object createInstanceNoArgs(Class<?> clazz) {
assertReflectionAccessor();
try {
return accessor.newInstance(clazz);
} catch (Exception e) {
throw SneakyException.sneakyThrow(e);
}
}
/**
* Get all inner classes and interfaces declared by the given class.
*
* @param clazz Class to reflect inner classes from.
* @return An {@code Array} of inner classes and interfaces declared by the given class.
* @throws RuntimeException If any error occurs.
*/
public static Class<?>[] getDeclaredClasses(Class<?> clazz) {
assertReflectionAccessor();
try {
return accessor.getDeclaredClasses(clazz);
} catch (Exception e) {
throw SneakyException.sneakyThrow(e);
}
}
/**
* Returns a constructor of the given class that takes the given parameter types.
*
* @param clazz Class to reflect constructor for.
* @param parameterTypes Parameter types of the constructor.
* @param <T> Class type.
* @return A constructor of the given class that takes the given parameter types.
* @throws RuntimeException If the class doesn't have a constructor with the given parameter types.
*/
public static <T> ReflectionConstructor<T> getDeclaredConstructor(Class<T> clazz, Class<?>... parameterTypes) {
assertReflectionAccessor();
try {
return accessor.getDeclaredConstructor(clazz, parameterTypes);
} catch (Exception e) {
throw SneakyException.sneakyThrow(e);
}
}
/**
* Returns a method with the provided name that takes no-args.
*
* @param clazz Class to search.
* @param methodName Method name.
* @return A method with the provided name that takes no-args.
* @throws RuntimeException If the class doesn't contain a no-args method with the given name.
*/
public static ReflectionMethod getNoArgsMethod(Class<?> clazz, String methodName) {
assertReflectionAccessor();
try {
// TODO: Support inheritance?
return accessor.getDeclaredMethod(clazz, methodName, NO_ARGS_TYPE);
} catch (Exception e) {
throw SneakyException.sneakyThrow(e);
}
}
/**
* Returns an array containing {@code ReflectionMethod} objects reflecting all the
* public methods of the class or interface represented by this {@code
* Class} object, including those declared by the class or interface and
* those inherited from superclasses and superinterfaces.
*
* @param clazz Class to get methods from
* @return the array of {@code ReflectionMethod} objects representing the
* public methods of this class
*/
public static ReflectionMethod[] getMethods(Class<?> clazz) {
assertReflectionAccessor();
return accessor.getMethods(clazz);
}
/**
* Returns an array of {@code Field} objects reflecting all the fields
* declared by the class or interface represented by this
* {@code Class} object. This includes public, protected, default
* (package) access, and private fields, but excludes inherited fields.
*
* @param clazz Class to get fields from
* @return the array of {@code Field} objects representing all the
* declared fields of this class
*/
public static ReflectionField[] getDeclaredFields(Class<?> clazz) {
assertReflectionAccessor();
return accessor.getDeclaredFields(clazz);
}
/**
* Returns this element's annotation for the specified type if
* such an annotation is <em>present</em>, else null.
*
* @param <T> the type of the annotation to query for and return if present
* @param clazz Class to get annotation from
* @param annotationClass the Class object corresponding to the
* annotation type
* @return this element's annotation for the specified annotation type if
* present on this element, else null
* @throws NullPointerException if the given annotation class is null
* @since 1.5
*/
public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotationClass) {
assertReflectionAccessor();
return accessor.getAnnotation(clazz, annotationClass);
}
/**
* Determines if the class or interface represented by first Class parameter is either the same as, or is a superclass or
* superinterface of, the class or interface represented by the second Class parameter.
*
* @param c1 Class to check.
* @param c2 Class to check against.
* @return {@code true} if the first class parameter is either the same as or a superinterface of the second class parameter.
*/
public static boolean isAssignableFrom(Class<?> c1, Class<?> c2) {
assertReflectionAccessor();
return accessor.isAssignableFrom(c1, c2);
}
/**
* Invokes the method, using the provided instance as 'this'.
* Method must be no-args and have a return value of type {@code T}.
* If the method is private, it will be made accessible outside of it's class.
*
* @param instance Instance to use as 'this' for invocation.
* @param method Method to invoke.
* @param <T> Return type.
* @return Result of invoking the no-args method.
* @throws RuntimeException If an error occurred invoking the method.
*/
@SuppressWarnings("unchecked")
public static <T> T invokeNoArgs(Object instance, ReflectionMethod method) {
method.setAccessible(true);
try {
return (T) method.invoke(instance, NO_ARGS);
} catch (Exception e) {
throw SneakyException.sneakyThrow(e);
}
}
/**
* Assert that the given method returns the expected return type.
*
* @param method Method to assert.
* @param expectedReturnType Expected return type of the method.
* @throws IllegalArgumentException If the method's return type doesn't match the expected type.
*/
public static void assertReturnValue(ReflectionMethod method, Class<?> expectedReturnType) {
final Class<?> returnType = method.getReturnType();
if (returnType != expectedReturnType) {
final String message = "Class='"+method.getDeclaringClass()+"', method='"+method.getName()+"': Must return a value of type '"+expectedReturnType+"'!";
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given method takes no parameters.
*
* @param method Method to assert.
* @throws IllegalArgumentException If the method takes any parameters.
*/
public static void assertNoParameters(ReflectionMethod method) {
final List<ReflectionParameter> parameters = method.getParameters();
if (!parameters.isEmpty()) {
final String message = "Class='"+method.getDeclaringClass()+"', method='"+method.getName()+"': Must take no parameters!";
throw new IllegalArgumentException(message);
}
}
private static void assertReflectionAccessor() {
Objects.requireNonNull(accessor, "ReflectionAccessor hasn't been set!");
}
}