package io.micronaut.inject.qualifiers;
import io.micronaut.context.Qualifier;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.GenericTypeUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanType;
import org.slf4j.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Internal
public class TypeArgumentQualifier<T> implements Qualifier<T> {
private static final Logger LOG = ClassUtils.getLogger(TypeArgumentQualifier.class);
private final Class[] typeArguments;
TypeArgumentQualifier(Class... typeArguments) {
this.typeArguments = typeArguments;
}
@Override
public <BT extends BeanType<T>> Stream<BT> reduce(Class<T> beanType, Stream<BT> candidates) {
return candidates.filter(candidate -> beanType.isAssignableFrom(candidate.getBeanType()))
.filter(candidate -> {
List<Class> typeArguments = getTypeArguments(beanType, candidate);
boolean result = areTypesCompatible(typeArguments);
if (LOG.isTraceEnabled() && !result) {
LOG.trace("Bean type {} is not compatible with candidate generic types [{}] of candidate {}", beanType, CollectionUtils.toString(typeArguments), candidate);
}
return result;
});
}
public Class[] getTypeArguments() {
return typeArguments;
}
protected boolean areTypesCompatible(List<Class> classes) {
final Class[] typeArguments = this.typeArguments;
return areTypesCompatible(typeArguments, classes);
}
protected <BT extends BeanType<T>> List<Class> getTypeArguments(Class<T> beanType, BT candidate) {
if (candidate instanceof BeanDefinition) {
BeanDefinition<BT> definition = (BeanDefinition<BT>) candidate;
return definition.getTypeArguments(beanType).stream().map(Argument::getType).collect(Collectors.toList());
} else {
if (beanType.isInterface()) {
return Arrays.asList(GenericTypeUtils.resolveInterfaceTypeArguments(candidate.getBeanType(), beanType));
} else {
return Arrays.asList(GenericTypeUtils.resolveSuperTypeGenericArguments(candidate.getBeanType(), beanType));
}
}
}
public static boolean areTypesCompatible(Class[] typeArguments, List<Class> classes) {
if (classes.isEmpty()) {
return true;
} else {
if (classes.size() != typeArguments.length) {
return false;
} else {
for (int i = 0; i < classes.size(); i++) {
Class left = classes.get(i);
Class right = typeArguments[i];
if (right == Object.class) {
continue;
}
if (left != right && !left.isAssignableFrom(right)) {
return false;
}
}
}
}
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TypeArgumentQualifier<?> that = (TypeArgumentQualifier<?>) o;
return Arrays.equals(typeArguments, that.typeArguments);
}
@Override
public int hashCode() {
return Arrays.hashCode(typeArguments);
}
@Override
public String toString() {
return "<" + Arrays.stream(typeArguments).map(Class::getSimpleName).collect(Collectors.joining(",")) + ">";
}
}