package io.micronaut.core.beans;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.reflect.exception.InstantiationException;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.StringUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.util.*;
@UsedByGeneratedCode
@Internal
public abstract class AbstractBeanIntrospection<T> implements BeanIntrospection<T> {
protected final AnnotationMetadata annotationMetadata;
protected final Class<T> beanType;
@SuppressWarnings("WeakerAccess")
protected final Map<String, BeanProperty<T, Object>> beanProperties;
private Map<Class<? extends Annotation>, List<BeanProperty<T, Object>>> indexed;
private Map<AnnotationValueKey, BeanProperty<T, Object>> indexedValues;
@UsedByGeneratedCode
protected AbstractBeanIntrospection(
@NonNull Class<T> beanType,
@Nullable AnnotationMetadata annotationMetadata,
int propertyCount) {
ArgumentUtils.requireNonNull("beanType", beanType);
this.beanType = beanType;
this.annotationMetadata = annotationMetadata == null ? AnnotationMetadata.EMPTY_METADATA : annotationMetadata;
this.beanProperties = new LinkedHashMap<>(propertyCount);
}
@NonNull
@Override
public Optional<BeanProperty<T, Object>> getIndexedProperty(@NonNull Class<? extends Annotation> annotationType, @NonNull String annotationValue) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
if (indexedValues != null && StringUtils.isNotEmpty(annotationValue)) {
return Optional.ofNullable(
indexedValues.get(new AnnotationValueKey(annotationType, annotationValue))
);
}
return Optional.empty();
}
@NonNull
@Override
public T instantiate(boolean strictNullable, Object... arguments) throws InstantiationException {
ArgumentUtils.requireNonNull("arguments", arguments);
if (arguments.length == 0) {
return instantiate();
}
final Argument<?>[] constructorArguments = getConstructorArguments();
if (constructorArguments.length != arguments.length) {
throw new InstantiationException("Argument count [" + arguments.length + "] doesn't match required argument count: " + constructorArguments.length);
}
for (int i = 0; i < constructorArguments.length; i++) {
Argument<?> constructorArgument = constructorArguments[i];
final Object specified = arguments[i];
if (specified == null) {
if (constructorArgument.isDeclaredNullable() || !strictNullable) {
continue;
} else {
throw new InstantiationException("Null argument specified for [" + constructorArgument.getName() + "]. If this argument is allowed to be null annotate it with @Nullable");
}
}
if (!ReflectionUtils.getWrapperType(constructorArgument.getType()).isInstance(specified)) {
throw new InstantiationException("Invalid argument [" + specified + "] specified for argument: " + constructorArgument);
}
}
return instantiateInternal(arguments);
}
@NonNull
@Override
public Optional<BeanProperty<T, Object>> getProperty(@NonNull String name) {
ArgumentUtils.requireNonNull("name", name);
return Optional.ofNullable(beanProperties.get(name));
}
@NonNull
@Override
public Collection<BeanProperty<T, Object>> getIndexedProperties(@NonNull Class<? extends Annotation> annotationType) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
if (indexed != null) {
final List<BeanProperty<T, Object>> indexed = this.indexed.get(annotationType);
if (indexed != null) {
return Collections.unmodifiableCollection(indexed);
}
}
return Collections.emptyList();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@NonNull
@Override
public Collection<BeanProperty<T, Object>> getBeanProperties() {
return Collections.unmodifiableCollection(beanProperties.values());
}
@NonNull
@Override
public Class<T> getBeanType() {
return beanType;
}
@Internal
@UsedByGeneratedCode
protected abstract T instantiateInternal(Object[] arguments);
@SuppressWarnings("unused")
@Internal
@UsedByGeneratedCode
protected final void addProperty(@NonNull BeanProperty<T, Object> property) {
ArgumentUtils.requireNonNull("property", property);
beanProperties.put(property.getName(), property);
}
@SuppressWarnings("unused")
@Internal
@UsedByGeneratedCode
protected final void indexProperty(@NonNull Class<? extends Annotation> annotationType, @NonNull String propertyName) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
if (StringUtils.isNotEmpty(propertyName)) {
final BeanProperty<T, Object> property = beanProperties.get(propertyName);
if (property == null) {
throw new IllegalStateException("Invalid byte code generated during bean introspection. Call addProperty first!");
}
if (indexed == null) {
indexed = new HashMap<>(2);
}
final List<BeanProperty<T, Object>> indexed = this.indexed.computeIfAbsent(annotationType, aClass -> new ArrayList<>(2));
indexed.add(property);
}
}
@SuppressWarnings("unused")
@Internal
@UsedByGeneratedCode
protected final void indexProperty(
@NonNull Class<? extends Annotation> annotationType,
@NonNull String propertyName,
@NonNull String annotationValue) {
indexProperty(annotationType, propertyName);
if (StringUtils.isNotEmpty(annotationValue) && StringUtils.isNotEmpty(propertyName)) {
if (indexedValues == null) {
indexedValues = new HashMap<>(10);
}
final BeanProperty<T, Object> property = beanProperties.get(propertyName);
indexedValues.put(new AnnotationValueKey(annotationType, annotationValue), property);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AbstractBeanIntrospection<?> that = (AbstractBeanIntrospection<?>) o;
return Objects.equals(beanType, that.beanType);
}
@Override
public int hashCode() {
return Objects.hash(beanType);
}
@Override
public String toString() {
return "BeanIntrospection{" +
"type=" + beanType +
'}';
}
private final class AnnotationValueKey {
final @NonNull Class<? extends Annotation> type;
final @NonNull String value;
AnnotationValueKey(@NonNull Class<? extends Annotation> type, @NonNull String value) {
this.type = type;
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AnnotationValueKey that = (AnnotationValueKey) o;
return type.equals(that.type) &&
value.equals(that.value);
}
@Override
public int hashCode() {
return Objects.hash(type, value);
}
}
}