/*
* Copyright 2013 Matt Sicker and 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 atg.tools.dynunit.internal.reflect;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Container for scanning annotated elements in a class. Because reflection is an expensive operation in Java (sigh),
* the caller must call initFields(), initMethods(), or init() to initialize the fields, methods, or both, respectively.
* <p/>
* This class is not a part of the public API and is subject to change. It is used for finding injection points. It is
* also used for caching annotations and such for faster lookup afterward.
*
* @author msicker
* @version 1.0.0
*/
public class ClassReflection<T> {
// TODO: cache annotation types
private static final Logger logger = LogManager.getLogger();
private final T object;
private final Class<T> klass;
private Field[] fields = null;
private Method[] methods = null;
@SuppressWarnings("unchecked") // seriously Java, get your generic shit together
public ClassReflection(@NotNull final T object) {
this.object = object;
klass = (Class<T>) object.getClass();
}
public void init() {
logger.entry();
initFields();
initMethods();
logger.exit();
}
public void initFields() {
logger.entry();
fields = klass.getFields();
logger.exit();
}
public void initMethods() {
logger.entry();
methods = klass.getMethods();
logger.exit();
}
@NotNull
public T getObject() {
logger.entry();
return logger.exit(object);
}
@NotNull
public Class<T> getObjectClass() {
logger.entry();
return logger.exit(klass);
}
@Nullable
public <A extends Annotation> Pair<? extends AnnotatedElement, A> getAnnotatedElement(@NotNull final Class<A> annotationClass) {
logger.entry(annotationClass);
A annotation = getClassAnnotation(annotationClass);
if (annotation != null) {
return logger.exit(Pair.of(klass, annotation));
}
for (final Field field : fields) {
annotation = getFieldAnnotation(field, annotationClass);
if (annotation != null) {
return logger.exit(Pair.of(field, annotation));
}
}
for (final Method method : methods) {
annotation = getMethodAnnotation(method, annotationClass);
if (annotation != null) {
return logger.exit(Pair.of(method, annotation));
}
}
return logger.exit(null);
}
public Annotation[] getClassAnnotations() {
logger.entry();
final Annotation[] annotations = klass.getAnnotations();
return logger.exit(annotations);
}
@Nullable
public <A extends Annotation> A getClassAnnotation(@NotNull final Class<A> annotationClass) {
return getElementAnnotation(klass, annotationClass);
}
public Field[] getFields() {
logger.entry();
return logger.exit(fields);
}
@NotNull
public Map<Field, Annotation[]> getFieldAnnotations() {
return getElementalAnnotations(fields);
}
@Nullable
public <A extends Annotation> A getFieldAnnotation(@NotNull final Field field,
@NotNull final Class<A> annotationClass) {
return getElementAnnotation(field, annotationClass);
}
public Method[] getMethods() {
logger.entry();
return logger.exit(methods);
}
@NotNull
public Map<Method, Annotation[]> getMethodAnnotations() {
return getElementalAnnotations(methods);
}
@Nullable
public <A extends Annotation> A getMethodAnnotation(@NotNull final Method method,
@NotNull final Class<A> annotationClass) {
return getElementAnnotation(method, annotationClass);
}
@Nullable
private static <E extends AnnotatedElement, A extends Annotation> A getElementAnnotation(@NotNull E element,
@NotNull Class<A> annotationClass) {
logger.entry(element, annotationClass);
final A annotation = element.getAnnotation(annotationClass);
return logger.exit(annotation);
}
@NotNull
private static <E extends AnnotatedElement> Map<E, Annotation[]> getElementalAnnotations(@NotNull E[] elements) {
logger.entry((Object[]) elements);
final Map<E, Annotation[]> elementAnnotations = new ConcurrentHashMap<E, Annotation[]>();
for (final E element : elements) {
elementAnnotations.put(element, element.getAnnotations());
}
return logger.exit(elementAnnotations);
}
}