blob: 8c10c7d86d52dc1c25aa56b288e414745c24a6f0 [file] [log] [blame]
/*
* 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.bval.util.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
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.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import org.apache.bval.util.Validate;
import org.apache.commons.weaver.privilizer.Privilizing;
/**
* Security-agnostic "blueprint" class for reflection-related operations. Intended for use by Apache BVal code.
*/
public class Reflection {
public static final class ClassHierarchy implements Iterable<Class<?>> {
private final Class<?> type;
public ClassHierarchy(Class<?> type) {
this.type = type;
}
@Override
public Iterator<Class<?>> iterator() {
return new Iterator<Class<?>>() {
Optional<Class<?>> next = Optional.of(type);
@Override
public boolean hasNext() {
return next.isPresent();
}
@Override
public Class<?> next() {
final Class<?> result = next.orElseThrow(NoSuchElementException::new);
next = Optional.ofNullable(result.getSuperclass());
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
public static final class FullHierarchy implements Iterable<Class<?>> {
private final Iterable<Class<?>> classes;
public FullHierarchy(Iterable<Class<?>> classes) {
this.classes = classes;
}
@Override
public Iterator<Class<?>> iterator() {
final Set<Class<?>> seenInterfaces = new HashSet<Class<?>>();
final Iterator<Class<?>> wrapped = classes.iterator();
return new Iterator<Class<?>>() {
Iterator<Class<?>> interfaces = Collections.emptyIterator();
@Override
public boolean hasNext() {
return interfaces.hasNext() || wrapped.hasNext();
}
@Override
public Class<?> next() {
if (interfaces.hasNext()) {
final Class<?> nextInterface = interfaces.next();
seenInterfaces.add(nextInterface);
return nextInterface;
}
final Class<?> nextSuperclass = wrapped.next();
final Set<Class<?>> currentInterfaces = new LinkedHashSet<>();
walkInterfaces(currentInterfaces, nextSuperclass);
interfaces = currentInterfaces.iterator();
return nextSuperclass;
}
private void walkInterfaces(final Set<Class<?>> addTo, final Class<?> c) {
for (final Class<?> iface : c.getInterfaces()) {
if (!seenInterfaces.contains(iface)) {
addTo.add(iface);
}
walkInterfaces(addTo, iface);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Inclusivity literals for {@link #hierarchy(Class, Interfaces)}.
* Taken from commons-lang3.
*/
public enum Interfaces {
INCLUDE, EXCLUDE
}
private static final Object[][] NATIVE_CODES = new Object[][]{
{ byte.class, "byte", "B" },
{ char.class, "char", "C" },
{ double.class, "double", "D" },
{ float.class, "float", "F" },
{ int.class, "int", "I" },
{ long.class, "long", "J" },
{ short.class, "short", "S" },
{ boolean.class, "boolean", "Z" },
{ void.class, "void", "V" }
};
/**
* Maps primitive {@code Class}es to their corresponding wrapper {@code Class}.
*/
private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_MAP;
static {
final Map<Class<?>, Class<?>> m = new HashMap<>();
m.put(Boolean.TYPE, Boolean.class);
m.put(Byte.TYPE, Byte.class);
m.put(Character.TYPE, Character.class);
m.put(Short.TYPE, Short.class);
m.put(Integer.TYPE, Integer.class);
m.put(Long.TYPE, Long.class);
m.put(Double.TYPE, Double.class);
m.put(Float.TYPE, Float.class);
m.put(Void.TYPE, Void.TYPE);
PRIMITIVE_WRAPPER_MAP = Collections.unmodifiableMap(m);
}
/**
* <p>Converts the specified primitive Class object to its corresponding
* wrapper Class object.</p>
*
* <p>NOTE: From v2.2, this method handles {@code Void.TYPE},
* returning {@code Void.TYPE}.</p>
*
* @param cls the class to convert, may be null
* @return the wrapper class for {@code cls} or {@code cls} if
* {@code cls} is not a primitive. {@code null} if null input.
* @since 2.1
*/
public static Class<?> primitiveToWrapper(final Class<?> cls) {
Class<?> convertedClass = cls;
if (cls != null && cls.isPrimitive()) {
convertedClass = PRIMITIVE_WRAPPER_MAP.get(cls);
}
return convertedClass;
}
public static Class<?> wrapperToPrimitive(final Class<?> cls) {
for (Map.Entry<Class<?>, Class<?>> primitiveEntry : PRIMITIVE_WRAPPER_MAP.entrySet()) {
if (primitiveEntry.getValue().equals(cls)) {
return primitiveEntry.getKey();
}
}
return null;
}
/**
* Get the named value from the specified {@link Annotation}.
* @param annotation
* @param name
* @return Object value
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static Object getAnnotationValue(final Annotation annotation, final String name)
throws IllegalAccessException, InvocationTargetException {
final Method valueMethod;
try {
valueMethod = annotation.annotationType().getDeclaredMethod(name);
} catch (final NoSuchMethodException ex) {
// do nothing
return null;
}
makeAccessible(valueMethod);
return valueMethod.invoke(annotation);
}
/**
* Get a {@link ClassLoader} preferring that of {@code clazz} over
* {@link Thread#getContextClassLoader()} of current {@link Thread}.
*
* @param clazz
* @return {@link ClassLoader}
*/
public static ClassLoader loaderFromClassOrThread(final Class<?> clazz) {
return Optional.of(clazz).map(Class::getClassLoader)
.orElseGet(() -> Thread.currentThread().getContextClassLoader());
}
/**
* Get a {@link ClassLoader} preferring
* {@link Thread#getContextClassLoader()} of current {@link Thread} over
* that of {@code fallbackClass}.
*
* @param fallbackClass
* @return {@link ClassLoader}
*/
public static ClassLoader loaderFromThreadOrClass(final Class<?> fallbackClass) {
return Optional.of(Thread.currentThread()).map(Thread::getContextClassLoader)
.orElseGet(() -> Validate.notNull(fallbackClass).getClassLoader());
}
public static Class<?> toClass(String className) throws ClassNotFoundException {
return toClass(className, loaderFromThreadOrClass(Reflection.class));
}
/**
* Return the class for the given string, correctly handling
* primitive types. If the given class loader is null, the context
* loader of the current thread will be used.
*
* @throws RuntimeException on load error
*/
public static Class<?> toClass(String className, ClassLoader loader) throws ClassNotFoundException {
return toClass(className, false, loader);
}
/**
* Return the class for the given string, correctly handling
* primitive types. If the given class loader is null, the context
* loader of the current thread will be used.
*
* @throws RuntimeException on load error
*/
public static Class<?> toClass(String className, boolean resolve, ClassLoader loader)
throws ClassNotFoundException {
Validate.notNull(className, "className was null");
// array handling
int dims = 0;
while (className.endsWith("[]")) {
dims++;
className = className.substring(0, className.length() - 2);
}
// check against primitive types
boolean primitive = false;
if (className.indexOf('.') == -1) {
for (int i = 0; !primitive && (i < NATIVE_CODES.length); i++) {
if (NATIVE_CODES[i][1].equals(className)) {
if (dims == 0) {
return (Class<?>) NATIVE_CODES[i][0];
}
className = (String) NATIVE_CODES[i][2];
primitive = true;
}
}
}
if (dims > 0) {
StringBuilder buf = new StringBuilder(className.length() + dims + 2);
for (int i = 0; i < dims; i++) {
buf.append('[');
}
if (!primitive) {
buf.append('L');
}
buf.append(className);
if (!primitive) {
buf.append(';');
}
className = buf.toString();
}
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
}
return Class.forName(className, resolve, loader);
}
/**
* Convenient point for {@link Privilizing} {@link System#getProperty(String)}.
* @param name
* @return String
*/
public static String getProperty(final String name) {
return System.getProperty(name);
}
/**
* Get the declared field from {@code clazz}.
* @param clazz
* @param fieldName
* @return {@link Field} or {@code null}
*/
public static Field getDeclaredField(final Class<?> clazz, final String fieldName) {
try {
return clazz.getDeclaredField(fieldName);
} catch (final NoSuchFieldException e) {
return null;
}
}
/**
* Convenient point for {@link Privilizing} {@link Class#getDeclaredFields()}.
* @param clazz
* @return {@link Field} array
*/
public static Field[] getDeclaredFields(final Class<?> clazz) {
return clazz.getDeclaredFields();
}
/**
* Get the declared constructor from {@code clazz}.
* @param clazz
* @param parameters
* @return {@link Constructor} or {@code null}
*/
public static <T> Constructor<T> getDeclaredConstructor(final Class<T> clazz, final Class<?>... parameters) {
try {
return clazz.getDeclaredConstructor(parameters);
} catch (final NoSuchMethodException e) {
return null;
}
}
/**
* Get the declared method from {@code clazz}.
* @param clazz
* @param name
* @param parameters
* @return {@link Method} or {@code null}
*/
public static Method getDeclaredMethod(final Class<?> clazz, final String name, final Class<?>... parameters) {
try {
return clazz.getDeclaredMethod(name, parameters);
} catch (final NoSuchMethodException e) {
return null;
}
}
/**
* Convenient point for {@link Privilizing} {@link Class#getDeclaredMethods()}.
* @param clazz
* @return {@link Method} array
*/
public static Method[] getDeclaredMethods(final Class<?> clazz) {
return clazz.getDeclaredMethods();
}
/**
* Convenient point for {@link Privilizing} {@link Class#getDeclaredConstructors()}.
* @param clazz
* @return {@link Constructor} array
*/
@SuppressWarnings("unchecked")
public static <T> Constructor<? extends T>[] getDeclaredConstructors(final Class<T> clazz) {
return (Constructor<? extends T>[]) clazz.getDeclaredConstructors();
}
/**
* Get the specified {@code public} {@link Method} from {@code clazz}.
* @param clazz
* @param methodName
* @return {@link Method} or {@code null}
*/
public static Method getPublicMethod(final Class<?> clazz, final String methodName, Class<?>... parameterTypes) {
try {
return clazz.getMethod(methodName, parameterTypes);
} catch (final NoSuchMethodException e) {
return null;
}
}
/**
* Perform a search against the class hierarchy.
* @param clazz
* @param search
* @return T or {@code null}
*/
public static <T> T find(final Class<?> clazz, Function<Class<?>, T> search) {
for (Class<?> t : hierarchy(clazz, Interfaces.INCLUDE)) {
final T value = search.apply(t);
if (value != null) {
return value;
}
}
return null;
}
/**
* Construct a new instance of {@code cls} using its default constructor.
* @param cls
* @return T
*/
public static <T> T newInstance(final Class<T> cls) {
try {
return cls.getConstructor().newInstance();
} catch (final Exception ex) {
throw new IllegalArgumentException("Cannot instantiate : " + cls, ex);
}
}
/**
* Set the accessibility of {@code o} to true.
* @param o
*/
public static void makeAccessible(final AccessibleObject o) {
if (o == null || o.isAccessible()) {
return;
}
final Member m = (Member) o;
// For public members whose declaring classes are public, we need do nothing:
if (Modifier.isPublic(m.getModifiers()) && Modifier.isPublic(m.getDeclaringClass().getModifiers())) {
return;
}
o.setAccessible(true);
}
/**
* Get an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order.
* Taken from commons-lang3.
*
* @param type the type to get the class hierarchy from
* @param interfacesBehavior switch indicating whether to include or exclude interfaces
* @return Iterable an Iterable over the class hierarchy of the given class
*/
public static Iterable<Class<?>> hierarchy(final Class<?> type, final Interfaces interfacesBehavior) {
if (type == null) {
return Collections.emptySet();
}
final Iterable<Class<?>> classes = new ClassHierarchy(type);
return interfacesBehavior == Interfaces.INCLUDE ? new FullHierarchy(classes) : classes;
}
}