BeanType.java

/*
 * 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;

import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Bean;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationMetadataProvider;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanInfo;
import io.micronaut.core.naming.NameResolver;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.DefaultArgument;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;

/**
 * A reference to a bean. Implemented by bother {@link BeanDefinitionReference} and {@link BeanDefinition}.
 *
 * @param <T> The bean type
 * @author Graeme Rocher
 * @since 1.0
 */
public interface BeanType<T> extends AnnotationMetadataProvider, BeanContextConditional, BeanInfo<T> {

    /**
     * @return Whether the bean definition is the {@link io.micronaut.context.annotation.Primary}
     */
    default boolean isPrimary() {
        return getAnnotationMetadata().hasDeclaredStereotype(Primary.class);
    }

    /**
     * Returns the bean type.
     *
     * @return The underlying bean type
     */
    @Override
    @NonNull Class<T> getBeanType();

    /**
     * Returns the name of the bean usually resolved via the {@link jakarta.inject.Named} annotation.
     * @return The name of the bean if any
     * @since 4.0.0
     */
    default Optional<String> getBeanName() {
        if (this instanceof NameResolver nameResolver) {
            return nameResolver.resolveName();
        }
        AnnotationMetadata annotationMetadata = getAnnotationMetadata();
        // here we resolved the declared Qualifier of the bean
        return annotationMetadata
            .findDeclaredAnnotation(AnnotationUtil.NAMED)
            .flatMap(AnnotationValue::stringValue);
    }

    /**
     * Checks whether the bean type is a container type.
     * @return Whether the type is a container type like {@link Iterable}.
     * @since 3.0.0
     */
    default boolean isContainerType() {
        return DefaultArgument.CONTAINER_TYPES.contains(getBeanType().getName());
    }

    /**
     * Returns a potentially limited subset of bean types exposed by this bean.
     * The types to be exposed can be defined by the {@link io.micronaut.context.annotation.Type} annotation.
     *
     * @return The exposed types
     * @since 3.0.0
     */
    default @NonNull Set<Class<?>> getExposedTypes() {
        final AnnotationMetadata annotationMetadata = getAnnotationMetadata();
        final String beanAnn = Bean.class.getName();
        if (annotationMetadata.hasDeclaredAnnotation(beanAnn)) {
            final Class<?>[] exposedTypes = annotationMetadata.classValues(beanAnn, "typed");
            if (ArrayUtils.isNotEmpty(exposedTypes)) {
                return Collections.unmodifiableSet(CollectionUtils.setOf(exposedTypes));
            }
        }
        return Collections.emptySet();
    }

    /**
     * Return whether this bean type is a candidate for dependency injection for the passed type.
     * @param beanType The bean type
     * @return True if it is
     * @since 3.0.0
     */
    default boolean isCandidateBean(@Nullable Argument<?> beanType) {
        if (beanType == null) {
            return false;
        }
        final Set<Class<?>> exposedTypes = getExposedTypes();
        if (CollectionUtils.isNotEmpty(exposedTypes)) {
            return exposedTypes.contains(beanType.getType());
        } else {
            final Class<T> exposedType = getBeanType();
            return beanType.isAssignableFrom(exposedType) || beanType.getType() == exposedType || isContainerType();
        }
    }

    /**
     * @return The class name
     */
    default String getName() {
        return getBeanType().getName();
    }

    /**
     * By default, when the {@link io.micronaut.context.BeanContext} is started, the
     * {@link BeanDefinition#getExecutableMethods()} are not processed by registered
     * {@link io.micronaut.context.processor.ExecutableMethodProcessor} instances unless this method returns true.
     *
     * @return Whether the bean definition requires method processing
     * @see io.micronaut.context.annotation.Executable#processOnStartup()
     */
    default boolean requiresMethodProcessing() {
        return false;
    }
}