package io.micronaut.inject.annotation;
import io.micronaut.context.annotation.*;
import io.micronaut.core.annotation.*;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.util.StringUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
@Internal
class AnnotationMetadataSupport {
private static final Map<String, Map<String, Object>> ANNOTATION_DEFAULTS = new ConcurrentHashMap<>(20);
private static final Map<Class<? extends Annotation>, Optional<Constructor<InvocationHandler>>> ANNOTATION_PROXY_CACHE = new ConcurrentHashMap<>(20);
private static final Map<String, Class<? extends Annotation>> ANNOTATION_TYPES = new ConcurrentHashMap<>(20);
static {
Arrays.asList(
Nullable.class,
NonNull.class,
PreDestroy.class,
PostConstruct.class,
Named.class,
Singleton.class,
Inject.class,
Qualifier.class,
Scope.class,
Prototype.class,
Executable.class,
Bean.class,
Primary.class,
Value.class,
Property.class,
Provided.class,
Requires.class,
Secondary.class,
Type.class,
Context.class,
EachBean.class,
EachProperty.class,
Configuration.class,
ConfigurationProperties.class,
ConfigurationBuilder.class,
Introspected.class,
Parameter.class,
Replaces.class,
Requirements.class,
Factory.class).forEach(ann ->
ANNOTATION_TYPES.put(ann.getName(), ann)
);
}
static Map<String, Object> getDefaultValues(String annotation) {
return ANNOTATION_DEFAULTS.computeIfAbsent(annotation, s -> Collections.emptyMap());
}
static Optional<Class<? extends Annotation>> getAnnotationType(String name) {
return getAnnotationType(name, AnnotationMetadataSupport.class.getClassLoader());
}
static Optional<Class<? extends Annotation>> getAnnotationType(String name, ClassLoader classLoader) {
final Class<? extends Annotation> type = ANNOTATION_TYPES.get(name);
if (type != null) {
return Optional.of(type);
} else {
final Optional<Class> aClass = ClassUtils.forName(name, classLoader);
return aClass.flatMap((Function<Class, Optional<Class<? extends Annotation>>>) aClass1 -> {
if (Annotation.class.isAssignableFrom(aClass1)) {
ANNOTATION_TYPES.put(name, aClass1);
return Optional.of(aClass1);
}
return Optional.empty();
});
}
}
static Optional<Class<? extends Annotation>> getRegisteredAnnotationType(String name) {
final Class<? extends Annotation> type = ANNOTATION_TYPES.get(name);
if (type != null) {
return Optional.of(type);
}
return Optional.empty();
}
@SuppressWarnings("unchecked")
static Map<String, Object> getDefaultValues(Class<? extends Annotation> annotation) {
return getDefaultValues(annotation.getName());
}
static boolean hasDefaultValues(String annotation) {
return ANNOTATION_DEFAULTS.containsKey(annotation);
}
static void registerDefaultValues(String annotation, Map<String, Object> defaultValues) {
if (StringUtils.isNotEmpty(annotation)) {
ANNOTATION_DEFAULTS.put(annotation, defaultValues);
}
}
static void registerDefaultValues(AnnotationClassValue<?> annotation, Map<String, Object> defaultValues) {
if (defaultValues != null) {
registerDefaultValues(annotation.getName(), defaultValues);
}
registerAnnotationType(annotation);
}
@SuppressWarnings("unchecked")
static void registerAnnotationType(AnnotationClassValue<?> annotationClassValue) {
final String name = annotationClassValue.getName();
if (!ANNOTATION_TYPES.containsKey(name)) {
annotationClassValue.getType().ifPresent((Consumer<Class<?>>) aClass -> {
if (Annotation.class.isAssignableFrom(aClass)) {
ANNOTATION_TYPES.put(name, (Class<? extends Annotation>) aClass);
}
});
}
}
@SuppressWarnings("unchecked")
static Optional<Constructor<InvocationHandler>> getProxyClass(Class<? extends Annotation> annotation) {
return ANNOTATION_PROXY_CACHE.computeIfAbsent(annotation, aClass -> {
Class proxyClass = Proxy.getProxyClass(annotation.getClassLoader(), annotation);
return ReflectionUtils.findConstructor(proxyClass, InvocationHandler.class);
});
}
static <T extends Annotation> T buildAnnotation(Class<T> annotationClass, @Nullable AnnotationValue<T> annotationValue) {
Optional<Constructor<InvocationHandler>> proxyClass = getProxyClass(annotationClass);
if (proxyClass.isPresent()) {
Map<String, Object> values = new HashMap<>(getDefaultValues(annotationClass));
if (annotationValue != null) {
final Map<CharSequence, Object> annotationValues = annotationValue.getValues();
annotationValues.forEach((key, o) -> values.put(key.toString(), o));
}
int hashCode = AnnotationUtil.calculateHashCode(values);
Optional instantiated = InstantiationUtils.tryInstantiate(proxyClass.get(), (InvocationHandler) new AnnotationProxyHandler(hashCode, annotationClass, annotationValue));
if (instantiated.isPresent()) {
return (T) instantiated.get();
}
}
throw new AnnotationMetadataException("Failed to build annotation for type: " + annotationClass.getName());
}
private static class AnnotationProxyHandler implements InvocationHandler {
private final int hashCode;
private final Class<?> annotationClass;
private final AnnotationValue<?> annotationValue;
AnnotationProxyHandler(int hashCode, Class<?> annotationClass, @Nullable AnnotationValue<?> annotationValue) {
this.hashCode = hashCode;
this.annotationClass = annotationClass;
this.annotationValue = annotationValue;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!annotationClass.isInstance(obj)) {
return false;
}
Annotation other = (Annotation) annotationClass.cast(obj);
final AnnotationValue<?> otherValues = getAnnotationValues(other);
if (this.annotationValue == null && otherValues == null) {
return true;
} else if (this.annotationValue == null || otherValues == null) {
return false;
} else {
return annotationValue.equals(otherValues);
}
}
private AnnotationValue<?> getAnnotationValues(Annotation other) {
if (other instanceof AnnotationProxyHandler) {
return ((AnnotationProxyHandler) other).annotationValue;
}
return null;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
String name = method.getName();
if ((args == null || args.length == 0) && "hashCode".equals(name)) {
return hashCode;
} else if ((args != null && args.length == 1) && "equals".equals(name)) {
return equals(args[0]);
} else if ("annotationType".equals(name)) {
return annotationClass;
} else if (annotationValue != null && annotationValue.contains(name)) {
return annotationValue.getRequiredValue(name, method.getReturnType());
}
return method.getDefaultValue();
}
}
}