package io.micronaut.context;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.naming.NameResolver;
import io.micronaut.core.naming.Named;
import io.micronaut.core.type.Argument;
import io.micronaut.core.value.ValueResolver;
import io.micronaut.inject.*;
import io.micronaut.inject.qualifiers.Qualifiers;
import javax.inject.Provider;
import java.util.*;
@Internal
class BeanDefinitionDelegate<T> extends AbstractBeanContextConditional implements DelegatingBeanDefinition<T>, BeanFactory<T>, NameResolver, ValueResolver<String> {
static final String PRIMARY_ATTRIBUTE = Primary.class.getName();
protected final BeanDefinition<T> definition;
protected final Map<String, Object> attributes = new HashMap<>(2);
private BeanDefinitionDelegate(BeanDefinition<T> definition) {
this.definition = definition;
}
@Nullable
@Override
public Qualifier<T> resolveDynamicQualifier() {
Qualifier<T> qualifier = null;
Object o = attributes.get(NAMED_ATTRIBUTE);
if (o instanceof CharSequence) {
qualifier = Qualifiers.byName(o.toString());
}
return qualifier;
}
BeanDefinition<T> getDelegate() {
return definition;
}
@Override
public boolean isProxy() {
return definition.isProxy();
}
@Override
public boolean isIterable() {
return definition.isIterable();
}
@Override
public boolean isPrimary() {
return definition.isPrimary() || isPrimaryThroughAttribute();
}
private boolean isPrimaryThroughAttribute() {
Object o = attributes.get(PRIMARY_ATTRIBUTE);
if (o instanceof Boolean) {
return (Boolean) o;
}
return false;
}
@Override
public T build(BeanResolutionContext resolutionContext, BeanContext context, BeanDefinition<T> definition) throws BeanInstantiationException {
LinkedHashMap<String, Object> oldAttributes = null;
if (!attributes.isEmpty()) {
LinkedHashMap<String, Object> oldAttrs = new LinkedHashMap<>(attributes.size());
attributes.forEach((key, value) -> {
Object previous = resolutionContext.setAttribute(key, value);
if (previous != null) {
oldAttrs.put(key, previous);
}
});
oldAttributes = oldAttrs;
}
try {
if (this.definition instanceof ParametrizedBeanFactory) {
ParametrizedBeanFactory<T> parametrizedBeanFactory = (ParametrizedBeanFactory<T>) this.definition;
Argument[] requiredArguments = parametrizedBeanFactory.getRequiredArguments();
Object named = attributes.get(Named.class.getName());
if (named != null) {
Map<String, Object> fulfilled = new LinkedHashMap<>(requiredArguments.length);
for (Argument argument : requiredArguments) {
Class argumentType = argument.getType();
Optional result = ConversionService.SHARED.convert(named, argument);
String argumentName = argument.getName();
if (result.isPresent()) {
fulfilled.put(argumentName, result.get());
} else {
Qualifier qualifier = Qualifiers.byName(named.toString());
if (Provider.class.isAssignableFrom(argumentType)) {
Optional<Argument<?>> genericType = argument.getFirstTypeVariable();
if (genericType.isPresent()) {
Class beanType = genericType.get().getType();
try {
fulfilled.put(argumentName, ((DefaultBeanContext) context).getBeanProvider(resolutionContext, beanType, qualifier));
} catch (NoSuchBeanException e) {
}
}
} else {
Optional bean = context.findBean(argumentType, qualifier);
if (bean.isPresent()) {
fulfilled.put(argumentName, bean.get());
}
}
}
}
return parametrizedBeanFactory.build(resolutionContext, context, definition, fulfilled);
}
}
if (this.definition instanceof BeanFactory) {
return ((BeanFactory<T>) this.definition).build(resolutionContext, context, definition);
} else {
throw new IllegalStateException("Cannot construct a dynamically registered singleton");
}
} finally {
for (String key : attributes.keySet()) {
resolutionContext.removeAttribute(key);
}
if (oldAttributes != null) {
oldAttributes.forEach(resolutionContext::setAttribute);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BeanDefinitionDelegate<?> that = (BeanDefinitionDelegate<?>) o;
return Objects.equals(definition, that.definition) &&
Objects.equals(resolveName().orElse(null), that.resolveName().orElse(null));
}
@Override
public int hashCode() {
return Objects.hash(definition, resolveName().orElse(null));
}
@Override
public BeanDefinition<T> getTarget() {
return definition;
}
@Override
public Optional<String> resolveName() {
return get(Named.class.getName(), String.class);
}
public void put(String name, Object value) {
this.attributes.put(name, value);
}
@Override
public <T> Optional<T> get(String name, ArgumentConversionContext<T> conversionContext) {
Object value = attributes.get(name);
if (value != null && conversionContext.getArgument().getType().isInstance(value)) {
return Optional.of((T) value);
}
return Optional.empty();
}
@Override
public String toString() {
return definition.toString();
}
static <T> BeanDefinitionDelegate<T> create(BeanDefinition<T> definition) {
if (definition instanceof InitializingBeanDefinition || definition instanceof DisposableBeanDefinition) {
if (definition instanceof ValidatedBeanDefinition) {
return new LifeCycleValidatingDelegate<>(definition);
} else {
return new LifeCycleDelegate<>(definition);
}
} else if (definition instanceof ValidatedBeanDefinition) {
return new ValidatingDelegate<>(definition);
}
return new BeanDefinitionDelegate<>(definition);
}
@Override
public String getName() {
return definition.getName();
}
interface ProxyInitializingBeanDefinition<T> extends DelegatingBeanDefinition<T>, InitializingBeanDefinition<T> {
@Override
default T initialize(BeanResolutionContext resolutionContext, BeanContext context, T bean) {
BeanDefinition<T> definition = getTarget();
if (definition instanceof InitializingBeanDefinition) {
return ((InitializingBeanDefinition<T>) definition).initialize(resolutionContext, context, bean);
}
return bean;
}
}
interface ProxyDisposableBeanDefinition<T> extends DelegatingBeanDefinition<T>, DisposableBeanDefinition<T> {
@Override
default T dispose(BeanResolutionContext resolutionContext, BeanContext context, T bean) {
BeanDefinition<T> definition = getTarget();
if (definition instanceof DisposableBeanDefinition) {
return ((DisposableBeanDefinition<T>) definition).dispose(resolutionContext, context, bean);
}
return bean;
}
}
interface ProxyValidatingBeanDefinition<T> extends DelegatingBeanDefinition<T>, ValidatedBeanDefinition<T> {
@Override
default T validate(BeanResolutionContext resolutionContext, T instance) {
BeanDefinition<T> definition = getTarget();
if (definition instanceof ValidatedBeanDefinition) {
return ((ValidatedBeanDefinition<T>) definition).validate(resolutionContext, instance);
}
return instance;
}
@Override
default <V> void validateBeanArgument(@NonNull BeanResolutionContext resolutionContext, @NonNull InjectionPoint injectionPoint, @NonNull Argument<V> argument, int index, @Nullable V value) {
BeanDefinition<T> definition = getTarget();
if (definition instanceof ValidatedBeanDefinition) {
((ValidatedBeanDefinition<T>) definition).validateBeanArgument(
resolutionContext,
injectionPoint,
argument,
index,
value
);
}
}
}
private static final class LifeCycleDelegate<T> extends BeanDefinitionDelegate<T> implements ProxyInitializingBeanDefinition<T>, ProxyDisposableBeanDefinition<T> {
private LifeCycleDelegate(BeanDefinition<T> definition) {
super(definition);
}
}
private static final class ValidatingDelegate<T> extends BeanDefinitionDelegate<T> implements ProxyValidatingBeanDefinition<T> {
private ValidatingDelegate(BeanDefinition<T> definition) {
super(definition);
}
}
private static final class LifeCycleValidatingDelegate<T> extends BeanDefinitionDelegate<T> implements ProxyValidatingBeanDefinition<T>, ProxyInitializingBeanDefinition<T>, ProxyDisposableBeanDefinition<T> {
private LifeCycleValidatingDelegate(BeanDefinition<T> definition) {
super(definition);
}
}
}