io.micronaut.inject.qualifiers.Qualifiers Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* 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
*
* https://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 io.micronaut.inject.qualifiers;
import io.micronaut.context.Qualifier;
import io.micronaut.context.annotation.Any;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Type;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.BeanType;
import jakarta.inject.Named;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Factory for {@link io.micronaut.context.annotation.Bean} qualifiers.
*
* @author Graeme Rocher
* @since 1.0
*/
public class Qualifiers {
/**
* Allows looking up the first matching instance.
*
* This qualifier results on {@link io.micronaut.context.exceptions.NonUniqueBeanException} never being thrown as
* the first matching instance will be returned.
*
* @param The generic type
* @return The any qualifier.
* @see Any
* @since 3.0.0
*/
@SuppressWarnings("unchecked")
public static Qualifier any() {
return AnyQualifier.INSTANCE;
}
/**
* Allows looking up for beans without any qualifier.
*
* @param The generic type
* @return The none qualifier.
* @since 3.8.0
*/
@SuppressWarnings("unchecked")
public static Qualifier none() {
return NoneQualifier.INSTANCE;
}
/**
* Build a qualifier for the given argument.
*
* @param argument The argument
* @param The type
* @return The resolved qualifier
*/
@SuppressWarnings("unchecked")
public static @Nullable
Qualifier forArgument(@NonNull Argument> argument) {
AnnotationMetadata annotationMetadata = Objects.requireNonNull(argument, "Argument cannot be null").getAnnotationMetadata();
boolean hasMetadata = annotationMetadata != AnnotationMetadata.EMPTY_METADATA;
List qualifierTypes = hasMetadata ? AnnotationUtil.findQualifierAnnotationsNames(annotationMetadata) : null;
if (CollectionUtils.isNotEmpty(qualifierTypes)) {
if (qualifierTypes.size() == 1) {
return Qualifiers.byAnnotation(
annotationMetadata,
qualifierTypes.iterator().next()
);
} else {
Qualifier[] qualifiers = new Qualifier[qualifierTypes.size()];
int i = 0;
for (String type : qualifierTypes) {
qualifiers[i++] = Qualifiers.byAnnotation(annotationMetadata, type);
}
return Qualifiers.byQualifiers(qualifiers);
}
}
return null;
}
/**
* Build a qualifier from other qualifiers.
*
* @param qualifiers The qualifiers
* @param The component type
* @return The qualifier
*/
public static Qualifier byQualifiers(Qualifier... qualifiers) {
return new CompositeQualifier<>(qualifiers);
}
/**
* Build a qualifier for the given name.
*
* @param name The name
* @param The component type
* @return The qualifier
*/
@UsedByGeneratedCode
public static Qualifier byName(String name) {
return new NameQualifier<>(null, name);
}
/**
* Finds a name in the provided qualifier.
*
* @return The qualifier
* @since 4.0.0
*/
@Nullable
public static String findName(@NonNull Qualifier> qualifier) {
if (qualifier instanceof NameQualifier> nameQualifier) {
return nameQualifier.getName();
}
if (qualifier instanceof CompositeQualifier> compositeQualifier) {
for (Qualifier> composite : compositeQualifier.getQualifiers()) {
String name = findName(composite);
if (name != null) {
return name;
}
}
}
return null;
}
/**
* Qualify by a prefix. Applies starting with logic to the name of the bean..
*
* @param prefix The name
* @param The component type
* @return The qualifier
* @since 4.0.0
*/
public static Qualifier byNamePrefix(String prefix) {
return new PrefixQualifier<>(prefix);
}
/**
* Build a qualifier for the given annotation.
*
* @param annotation The annotation
* @param The component type
* @return The qualifier
*/
public static Qualifier byAnnotation(Annotation annotation) {
Qualifier qualifier = findCustomByType(AnnotationMetadata.EMPTY_METADATA, annotation.annotationType());
if (qualifier != null) {
return qualifier;
}
return new AnnotationQualifier<>(annotation);
}
/**
* Build a qualifier for the given annotation.
*
* @param metadata The metadata
* @param type The annotation type
* @param The component type
* @return The qualifier
*/
public static Qualifier byAnnotation(AnnotationMetadata metadata, Class extends Annotation> type) {
Qualifier instance = findCustomByType(metadata, type);
if (instance != null) {
return instance;
}
return AnnotationMetadataQualifier.fromType(metadata, type);
}
/**
* Build a qualifier for the given annotation. This qualifier will match a candidate under the following
* circumstances:
*
*
* - If the {@code type} parameter is {@link Named} then the value of the {@link Named} annotation within the metadata is used to match the candidate by name
* - If the {@code type} parameter is {@link Type} then the value of the {@link Type} annotation is used to match the candidate by type
*
*
* @param metadata The metadata
* @param type The annotation type
* @param The component type
* @return The qualifier
*/
public static Qualifier byAnnotation(AnnotationMetadata metadata, String type) {
Qualifier qualifier = findCustomByName(metadata, type);
if (qualifier != null) {
return qualifier;
}
return AnnotationMetadataQualifier.fromTypeName(metadata, type);
}
/**
* Build a qualifier for the given annotation value.
*
* @param metadata The metadata
* @param annotationValue The annotation value
* @param The component type
* @return The qualifier
*/
public static Qualifier byAnnotation(AnnotationMetadata metadata, AnnotationValue annotationValue) {
Qualifier qualifier = findCustomByName(metadata, annotationValue.getAnnotationName());
if (qualifier != null) {
return qualifier;
}
return AnnotationMetadataQualifier.fromValue(metadata, annotationValue);
}
/**
* Builds a qualifier that uses the given repeatable annotation.
*
* @param metadata The metadata
* @param repeatableType The annotation repeatable type. That is the annotation specified to {@link java.lang.annotation.Repeatable#value()}
* @param The component type
* @return The qualifier
*/
@UsedByGeneratedCode
public static Qualifier byRepeatableAnnotation(AnnotationMetadata metadata, String repeatableType) {
return new RepeatableAnnotationQualifier<>(metadata, repeatableType);
}
/**
* Build a qualifier for the given annotation.
*
* Unlike {@link #byAnnotation(io.micronaut.core.annotation.AnnotationMetadata, String)} this method will not attempt to pick the qualifier strategy to use at runtime based on the passed annotation name.
*
* @param metadata The metadata
* @param type The annotation type
* @param The component type
* @return The qualifier
* @since 3.1.0
*/
@UsedByGeneratedCode
@Internal
public static Qualifier byAnnotationSimple(AnnotationMetadata metadata, String type) {
Qualifier qualifier = findCustomByName(metadata, type);
if (qualifier != null) {
return qualifier;
}
return AnnotationMetadataQualifier.fromTypeName(metadata, type);
}
/**
* Build a qualifier for the given annotation.
*
* @param stereotype The stereotype
* @param The component type
* @return The qualifier
*/
public static Qualifier byStereotype(Class extends Annotation> stereotype) {
Qualifier instance = findCustomByType(AnnotationMetadata.EMPTY_METADATA, stereotype);
if (instance != null) {
return instance;
}
return new AnnotationStereotypeQualifier<>(stereotype);
}
/**
* Build a qualifier for the given annotation.
*
* @param stereotype The stereotype
* @param The component type
* @return The qualifier
* @since 3.0.0
*/
public static Qualifier byStereotype(String stereotype) {
Qualifier qualifier = findCustomByName(AnnotationMetadata.EMPTY_METADATA, stereotype);
if (qualifier != null) {
return qualifier;
}
return new NamedAnnotationStereotypeQualifier<>(stereotype);
}
/**
* Build a qualifier for the given generic type arguments.
*
* @param typeArguments The generic type arguments
* @param The component type
* @return The qualifier
*/
public static Qualifier byTypeArguments(Class>... typeArguments) {
return new TypeArgumentQualifier<>(typeArguments);
}
/**
* Build a qualifier for the given generic type argument name.
*
* @param typeName The name of the generic type argument
* @param The component type
* @return The qualifier
* @since 3.0.0
*/
public static @NonNull Qualifier byExactTypeArgumentName(@NonNull String typeName) {
return new ExactTypeArgumentNameQualifier<>(typeName);
}
/**
* Build a qualifier for the given generic type arguments. Only the closest
* matches will be returned.
*
* @param typeArguments The generic type arguments
* @param The component type
* @return The qualifier
*/
public static Qualifier byTypeArgumentsClosest(Class>... typeArguments) {
return new ClosestTypeArgumentQualifier<>(typeArguments);
}
/**
* Build a qualifier for the given generic type arguments.
*
* @param typeArguments The generic type arguments
* @param The component type
* @return The qualifier
*/
public static Qualifier byType(Class>... typeArguments) {
return new TypeAnnotationQualifier<>(typeArguments);
}
/**
* Reduces bean definitions by the given interceptor binding.
*
* @param annotationMetadata The annotation metadata
* @param The bean type
* @return The qualifier
*/
public static @NonNull
Qualifier byInterceptorBinding(@NonNull AnnotationMetadata annotationMetadata) {
return new InterceptorBindingQualifier<>(annotationMetadata);
}
/**
* Reduces bean definitions by the given interceptor binding.
*
* @param binding The binding values to use
* @param The bean type
* @return The qualifier
* @since 3.3.0
*/
public static @NonNull
Qualifier byInterceptorBindingValues(@NonNull Collection> binding) {
return new InterceptorBindingQualifier<>(binding);
}
@Nullable
private static Qualifier findCustomByType(@NonNull AnnotationMetadata metadata, @NonNull Class extends Annotation> type) {
if (Any.class == type) {
//noinspection unchecked
return AnyQualifier.INSTANCE;
} else if (Primary.class == type) {
//noinspection unchecked
return PrimaryQualifier.INSTANCE;
} else if (Type.class == type) {
Optional aClass = metadata.classValue(type);
if (aClass.isPresent()) {
return byType(aClass.get());
}
} else if (Named.class == type) {
Optional value = metadata.stringValue(type);
if (value.isPresent()) {
return byName(value.get());
}
}
return null;
}
@Nullable
private static Qualifier findCustomByName(@NonNull AnnotationMetadata metadata, @NonNull String type) {
if (Type.NAME.equals(type)) {
Optional aClass = metadata.classValue(type);
if (aClass.isPresent()) {
return byType(aClass.get());
}
} else if (Any.NAME.equals(type)) {
//noinspection unchecked
return AnyQualifier.INSTANCE;
} else if (Qualifier.PRIMARY.equals(type)) {
//noinspection unchecked
return PrimaryQualifier.INSTANCE;
} else if (Named.class.getName().equals(type)) {
String n = metadata.stringValue(type).orElse(null);
if (n != null) {
return byName(n);
}
}
return null;
}
private record PrefixQualifier(String prefix) implements Qualifier {
@Override
public > Stream reduce(Class beanType, Stream candidates) {
return candidates.filter(candidate -> {
if (!QualifierUtils.matchType(beanType, candidate)) {
return false;
}
if (QualifierUtils.matchAny(beanType, candidate)) {
return true;
}
String name = candidate.getBeanName().orElse(null);
return name != null && name.startsWith(prefix);
});
}
}
}