All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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 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 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); }); } } }