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

io.micronaut.inject.qualifiers.MatchArgumentQualifier Maven / Gradle / Ivy

There is a newer version: 4.7.12
Show newest version
/*
 * Copyright 2017-2024 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.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanType;
import org.slf4j.Logger;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * A {@link Qualifier} that filters beans according to the type arguments.
 *
 * @param  The type
 * @author Denis Stepanov
 * @since 4.6
 */
@Internal
public final class MatchArgumentQualifier implements Qualifier {

    private static final Logger LOG = ClassUtils.getLogger(MatchArgumentQualifier.class);
    private final Argument argument;
    private final Argument covariantArgument;

    private MatchArgumentQualifier(Argument argument, Argument covariantArgument) {
        this.argument = argument;
        this.covariantArgument = covariantArgument;
    }

    public static  MatchArgumentQualifier ofArgument(Argument argument) {
        return new MatchArgumentQualifier<>(
            argument,
            null
        );
    }

    /**
     * Finds matches of a type with a covariant generic type (types that extend the type or are equal to it).
     * The generic argument is assignable from the candidate generic type.
     * Use-cases are generic deserializers, readers.
     *
     * 
     * Java example:
     * {@code MyReader> candidate = ...; }
     * {@code MyReader> aMatch = candidate; }
     * 
* * @param beanType The type of the beans * @param genericArgument The generic argument of the bean type * @param The bean type * @return The qualifier */ @NonNull public static MatchArgumentQualifier covariant(@NonNull Class beanType, @NonNull Argument genericArgument) { Argument covariantArgument = Argument.ofTypeVariable(genericArgument.getType(), null, genericArgument.getAnnotationMetadata(), genericArgument.getTypeParameters()); return new MatchArgumentQualifier<>( Argument.of(beanType, covariantArgument), covariantArgument ); } /** * Finds matches of a type with a contravariant generic type (types that is a super type or are equal to it). * The candidate generic type is assignable from the generic argument. * Use-cases are generic serializers, writers. * *
     * Java example:
     * {@code MyWriter candidate = ...; }
     * {@code MyWriter aMatch = candidate; }
     * 
* * @param beanType The type of the beans * @param genericArgument The generic argument of the bean type * @param The bean type * @return The qualifier */ @NonNull public static MatchArgumentQualifier contravariant(@NonNull Class beanType, @NonNull Argument genericArgument) { return new MatchArgumentQualifier<>( Argument.of(beanType, Argument.ofTypeVariable(genericArgument.getType(), null, genericArgument.getAnnotationMetadata(), genericArgument.getTypeParameters())), null ); } @Override public > Stream reduce(Class beanType, Stream candidates) { return filter(beanType, candidates.toList()).stream(); } @Override public boolean doesQualify(Class beanType, Collection> candidates) { return !filter(beanType, candidates).isEmpty(); } @Override public boolean doesQualify(Class beanType, BeanType candidate) { throw new IllegalStateException("Not supported!"); } @Override public > Collection filter(Class beanType, Collection candidates) { return filterArgumentTypeParameters(argument, candidates, null); } private boolean matchesArgumentTypeParameters(Argument argument, Argument candidateArgument) { Argument[] argumentTypeParameters = argument.getTypeParameters(); Argument[] candidateTypeParameters = candidateArgument.getTypeParameters(); if (argumentTypeParameters.length != candidateTypeParameters.length) { if (argumentTypeParameters.length == 0) { for (Argument candidateTypeParameter : candidateTypeParameters) { if (!candidateTypeParameter.getType().equals(Object.class)) { return false; } } return true; } return false; } for (int i = 0; i < candidateTypeParameters.length; i++) { Argument typeParameter = argumentTypeParameters[i]; Argument candidateTypeParameter = candidateTypeParameters[i]; if (!doesMatch(candidateTypeParameter, candidateTypeParameter.getType(), typeParameter, typeParameter.getType())) { return false; } } return true; } private > Collection filterArgumentTypeParameters(Argument argument, Collection result, Function> typeArgumentExtractor) { Argument[] typeParameters = argument.getTypeParameters(); for (int i = 0; i < typeParameters.length; i++) { int finalI = i; Argument typeParameter = typeParameters[i]; Function> getCandidateArgumentFunction = bd -> { if (typeArgumentExtractor == null) { if (bd instanceof BeanDefinition beanDefinition) { List> typeArguments = beanDefinition.getTypeArguments(argument.getType()); if (finalI < typeArguments.size()) { return typeArguments.get(finalI); } } return null; } Argument apply = typeArgumentExtractor.apply(bd); Argument[] candidateTypeParameters = apply.getTypeParameters(); if (finalI < candidateTypeParameters.length) { return candidateTypeParameters[finalI]; } return Argument.OBJECT_ARGUMENT; }; result = filterMatching(typeParameter, result, getCandidateArgumentFunction); result = filterArgumentTypeParameters(typeParameter, result, getCandidateArgumentFunction); } return result; } private > List filterMatching(Argument argument, Collection candidates, Function> typeArgumentExtractor) { List selectedDirect = null; boolean directMatch = false; List, List>> closestMatches = null; candidatesForLoop: for (BT candidate : candidates) { Class argumentTypeClass = argument.getType(); if (argumentTypeClass.isPrimitive()) { argumentTypeClass = ReflectionUtils.getWrapperType(argumentTypeClass); } Argument candidateArgument = typeArgumentExtractor.apply(candidate); if (candidateArgument == null) { continue; } Class candidateType = candidateArgument.getType(); if (argumentTypeClass.equals(candidateType)) { if (!matchesArgumentTypeParameters(argument, candidateArgument)) { // Eliminate a candidate that doesn't fully match reject(argument, candidate); continue; } if (!directMatch) { selectedDirect = new ArrayList<>(3); closestMatches = null; directMatch = true; } selectedDirect.add(candidate); continue; } if (directMatch) { // After direct match found ignore all non-direct reject(argument, candidate); continue; } if (!doesMatch(candidateArgument, candidateType, argument, argumentTypeClass)) { reject(argument, candidate); continue; } if (closestMatches != null) { // Compare the candidate with previous matches, possibly eliminating some of them for (Iterator, List>> iterator = closestMatches.iterator(); iterator.hasNext(); ) { Map.Entry, List> e = iterator.next(); Class closestMatch = e.getKey(); List selected = e.getValue(); if (closestMatch.equals(candidateType)) { // Same type as found before - also select this type selected.add(candidate); continue candidatesForLoop; } else if (closestMatch.isAssignableFrom(candidateType)) { // We found more close type - disregard previous selection iterator.remove(); if (LOG.isTraceEnabled()) { for (BT bt : selected) { reject(argument, bt); } } ArrayList newSelected = new ArrayList<>(); newSelected.add(candidate); closestMatches.add(new AbstractMap.SimpleEntry<>(candidateType, newSelected)); continue candidatesForLoop; } else if (candidateType.isAssignableFrom(closestMatch)) { // Previous match is much closer reject(argument, candidate); continue candidatesForLoop; } } } else { closestMatches = new ArrayList<>(); } ArrayList newSelected = new ArrayList<>(); newSelected.add(candidate); closestMatches.add(new AbstractMap.SimpleEntry<>(candidateType, newSelected)); } if (directMatch) { return selectedDirect; } if (closestMatches != null) { if (closestMatches.size() == 1) { return closestMatches.iterator().next().getValue(); } List result = new ArrayList<>(); for (Map.Entry, List> match : closestMatches) { result.addAll(match.getValue()); } return result; } return List.of(); } private boolean doesMatch(Argument candidateArgument, Class candidateType, Argument argument, Class argumentType) { if (candidateType.equals(argumentType)) { return true; } if (candidateType.equals(Enum.class)) { // Avoid checking generic types for enums return candidateType.isAssignableFrom(argumentType); } if (argument.isTypeVariable()) { // Is compatible? if (argument == covariantArgument) { if (candidateArgument.isTypeVariable() && candidateType.isAssignableFrom(argumentType)) { // Defined a type variable return true; } return argumentType.isAssignableFrom(candidateType); } else { return candidateType.isAssignableFrom(argumentType); } } if (candidateType.equals(Object.class)) { return true; } if (!candidateArgument.isTypeVariable()) { // Defined as a direct type return false; } // Is compatible? return candidateType.isAssignableFrom(argumentType); } private static > void reject(Argument argument, BT candidate) { if (LOG.isTraceEnabled()) { LOG.trace("Bean candidate {} is not compatible with an argument {}", candidate, argument); } } @Override public String toString() { return "Matches [" + argument.toString() + "]"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy