AbstractAnnotationMetadataBuilder.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.annotation;

import io.micronaut.context.annotation.AliasFor;
import io.micronaut.context.annotation.Aliases;
import io.micronaut.context.annotation.DefaultScope;
import io.micronaut.context.annotation.NonBinding;
import io.micronaut.core.annotation.AnnotatedElement;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationMetadataDelegate;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.AnnotationValueBuilder;
import io.micronaut.core.annotation.InstantiatedMember;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.expressions.EvaluatedExpressionReference;
import io.micronaut.core.io.service.SoftServiceLoader;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.visitor.VisitorContext;

import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static io.micronaut.expressions.EvaluatedExpressionConstants.EXPRESSION_PATTERN;

/**
 * An abstract implementation that builds {@link AnnotationMetadata}.
 *
 * @param <T> The element type
 * @param <A> The annotation type
 * @author Graeme Rocher
 * @since 1.0
 */
@Internal
public abstract class AbstractAnnotationMetadataBuilder<T, A> {

    /**
     * Names of annotations that should produce deprecation warnings.
     * The key in the map is the deprecated annotation the value the replacement.
     */
    protected static final AnnotatedElementValidator ELEMENT_VALIDATOR;
    private static final Map<String, String> DEPRECATED_ANNOTATION_NAMES = Collections.emptyMap();
    private static final Map<String, List<AnnotationMapper<?>>> ANNOTATION_MAPPERS = new HashMap<>(10);
    private static final Map<String, List<AnnotationTransformer<?>>> ANNOTATION_TRANSFORMERS = new HashMap<>(5);
    private static final Map<String, List<AnnotationRemapper>> ANNOTATION_REMAPPERS = new HashMap<>(5);
    private static final List<AnnotationRemapper> ALL_ANNOTATION_REMAPPERS = new ArrayList<>(5);
    private static final Map<Object, CachedAnnotationMetadata> MUTATED_ANNOTATION_METADATA = new HashMap<>(100);
    private static final Map<String, Map<CharSequence, Object>> ANNOTATION_DEFAULTS = new HashMap<>(20);

    static {
        for (AnnotationMapper<?> mapper : SoftServiceLoader.load(AnnotationMapper.class, AbstractAnnotationMetadataBuilder.class.getClassLoader())
                .disableFork().collectAll()) {
            try {
                String name = null;
                if (mapper instanceof TypedAnnotationMapper<?> typedAnnotationMapper) {
                    name = typedAnnotationMapper.annotationType().getName();
                } else if (mapper instanceof NamedAnnotationMapper namedAnnotationMapper) {
                    name = namedAnnotationMapper.getName();
                }
                if (StringUtils.isNotEmpty(name)) {
                    ANNOTATION_MAPPERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(mapper);
                }
            } catch (Throwable e) {
                // mapper, missing dependencies, continue
            }
        }

        for (AnnotationTransformer<?> transformer : SoftServiceLoader.load(AnnotationTransformer.class, AbstractAnnotationMetadataBuilder.class.getClassLoader())
                .disableFork().collectAll()) {
            try {
                String name = null;
                if (transformer instanceof TypedAnnotationTransformer<?> typedAnnotationTransformer) {
                    name = typedAnnotationTransformer.annotationType().getName();
                } else if (transformer instanceof NamedAnnotationTransformer namedAnnotationTransformer) {
                    name = namedAnnotationTransformer.getName();
                }
                if (StringUtils.isNotEmpty(name)) {
                    ANNOTATION_TRANSFORMERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(transformer);
                }
            } catch (Throwable e) {
                // mapper, missing dependencies, continue
            }
        }

        for (AnnotationRemapper mapper : SoftServiceLoader.load(AnnotationRemapper.class, AbstractAnnotationMetadataBuilder.class.getClassLoader())
                .disableFork().collectAll()) {
            try {
                String name = mapper.getPackageName();
                if (name.equals(AnnotationRemapper.ALL_PACKAGES)) {
                    ALL_ANNOTATION_REMAPPERS.add(mapper);
                } else if (StringUtils.isNotEmpty(name)) {
                    ANNOTATION_REMAPPERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(mapper);
                }
            } catch (Throwable e) {
                // mapper, missing dependencies, continue
            }
        }
        ELEMENT_VALIDATOR = SoftServiceLoader.load(AnnotatedElementValidator.class).firstAvailable().orElse(null);
    }

    private boolean validating = true;
    private final Set<T> erroneousElements = new HashSet<>();

    /**
     * Default constructor.
     */
    protected AbstractAnnotationMetadataBuilder() {

    }

    @SuppressWarnings("java:S1872")
    private AnnotationMetadata metadataForError(RuntimeException e) {
        if ("org.eclipse.jdt.internal.compiler.problem.AbortCompilation".equals(e.getClass().getName())) {
            // workaround for a bug in the Eclipse APT implementation. See bug 541466 on their Bugzilla.
            return AnnotationMetadata.EMPTY_METADATA;
        } else {
            throw e;
        }
    }

    /**
     * Build only metadata for declared annotations.
     *
     * @param element The element
     * @return The {@link AnnotationMetadata}
     */
    public AnnotationMetadata buildDeclared(T element) {
        MutableAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
        try {
            AnnotationMetadata metadata = buildInternalMulti(
                    Collections.emptyList(),
                    element,
                    annotationMetadata, true, true
            );
            if (metadata.isEmpty()) {
                return AnnotationMetadata.EMPTY_METADATA;
            }
            return metadata;
        } catch (RuntimeException e) {
            return metadataForError(e);
        }
    }

    /**
     * Build the metadata for the given element. If the element is a method the class metadata will be included.
     *
     * @param owningType       The owning type
     * @param methodElement    The method element
     * @param parameterElement The parameter element
     * @return The {@link AnnotationMetadata}
     */
    public CachedAnnotationMetadata lookupOrBuildForParameter(T owningType, T methodElement, T parameterElement) {
        return lookupOrBuild(new Key3<>(owningType, methodElement, parameterElement), parameterElement);
    }

    /**
     * Build the metadata for the given element.
     *
     * @param typeElement The element
     * @return The {@link AnnotationMetadata}
     */
    public CachedAnnotationMetadata lookupOrBuildForType(T typeElement) {
        return lookupOrBuild(typeElement, typeElement);
    }

    /**
     * Build the metadata for the given method element excluding any class metadata.
     *
     * @param owningType The owningType
     * @param element    The element
     * @return The {@link CachedAnnotationMetadata}
     */
    public CachedAnnotationMetadata lookupOrBuildForMethod(T owningType, T element) {
        return lookupOrBuild(new Key2<>(owningType, element), element);
    }

    /**
     * Build the metadata for the given field element excluding any class metadata.
     *
     * @param owningType The owningType
     * @param element    The element
     * @return The {@link CachedAnnotationMetadata}
     */
    public CachedAnnotationMetadata lookupOrBuildForField(T owningType, T element) {
        return lookupOrBuild(new Key2<>(owningType, element), element);
    }

    /**
     * Lookup or build new annotation metadata.
     *
     * @param key     The cache key
     * @param element The type element
     * @return The annotation metadata
     * @since 4.0.0
     */
    public CachedAnnotationMetadata lookupOrBuild(Object key, T element) {
        CachedAnnotationMetadata cachedAnnotationMetadata = MUTATED_ANNOTATION_METADATA.get(key);
        if (cachedAnnotationMetadata == null) {
            AnnotationMetadata annotationMetadata = buildInternal(element);
            cachedAnnotationMetadata = new DefaultCachedAnnotationMetadata(annotationMetadata);
            // Don't use `computeIfAbsent` as it can lead to a concurrent exception because the cache is accessed during in `buildInternal`
            MUTATED_ANNOTATION_METADATA.put(key, cachedAnnotationMetadata);
        }
        return cachedAnnotationMetadata;
    }

    private AnnotationMetadata buildInternal(T element) {
        MutableAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
        try {
            return buildInternalMulti(
                    Collections.emptyList(),
                    element,
                    annotationMetadata, false, false
            );
        } catch (RuntimeException e) {
            return metadataForError(e);
        }
    }

    /**
     * Get the type of the given annotation.
     *
     * @param annotationMirror The annotation
     * @return The type
     */
    protected abstract T getTypeForAnnotation(A annotationMirror);

    /**
     * Checks whether an annotation is present.
     *
     * @param element    The element
     * @param annotation The annotation type
     * @return True if the annotation is present
     */
    protected abstract boolean hasAnnotation(T element, Class<? extends Annotation> annotation);

    /**
     * Checks whether an annotation is present.
     *
     * @param element    The element
     * @param annotation The annotation type name
     * @return True if the annotation is present
     */
    protected abstract boolean hasAnnotation(T element, String annotation);

    /**
     * Checks whether any annotations are present on the given element.
     *
     * @param element The element
     * @return True if the annotation is present
     */
    protected abstract boolean hasAnnotations(T element);

    /**
     * Get the given type of the annotation.
     *
     * @param annotationMirror The annotation
     * @return The type
     */
    protected abstract String getAnnotationTypeName(A annotationMirror);

    /**
     * Get the name for the given element.
     *
     * @param element The element
     * @return The name
     */
    protected abstract String getElementName(T element);

    /**
     * Obtain the annotations for the given type. This method
     * is also responsible for unwrapping repeatable annotations.
     * <p>
     * For example, {@code @Parent(value = {@Child, @Child})} should result in the two
     * child annotations being returned from this method <b>instead</b> of the
     * parent annotation.
     *
     * @param element The type element
     * @return The annotations
     */
    protected abstract List<? extends A> getAnnotationsForType(T element);

    /**
     * Build the type hierarchy for the given element.
     *
     * @param element                The element
     * @param inheritTypeAnnotations Whether to inherit type annotations
     * @param declaredOnly           Whether to only include declared annotations
     * @return The type hierarchy
     */
    protected abstract List<T> buildHierarchy(T element, boolean inheritTypeAnnotations, boolean declaredOnly);

    /**
     * Read the given member and value, applying conversions if necessary, and place the data in the given map.
     *
     * @param originatingElement The originating element
     * @param annotationName     The annotation name
     * @param member             The member being read from
     * @param memberName         The member
     * @param annotationValue    The value
     * @param annotationValues   The values to populate
     */
    protected abstract void readAnnotationRawValues(
            T originatingElement,
            String annotationName,
            T member,
            String memberName,
            Object annotationValue,
            Map<CharSequence, Object> annotationValues);

    /**
     * Read the given member and value, applying conversions if necessary, and place the data in the given map.
     *
     * @param originatingElement The originating element
     * @param annotationName     The annotation name
     * @param member             The member being read from
     * @param memberName         The member
     * @param annotationValue    The value
     * @param annotationValues   The values to populate
     * @param resolvedDefaults   The resolved defaults
     * @since 4.3.0
     */
    protected void readAnnotationRawValues(
            T originatingElement,
            String annotationName,
            T member,
            String memberName,
            Object annotationValue,
            Map<CharSequence, Object> annotationValues,
            Map<String, Map<CharSequence, Object>> resolvedDefaults) {
        readAnnotationRawValues(originatingElement, annotationName, member, memberName, annotationValue, annotationValues);
    }

    /**
     * Validates an annotation value.
     *
     * @param originatingElement The originating element
     * @param annotationName     The annotation name
     * @param member             The member
     * @param memberName         The member name
     * @param resolvedValue      The resolved value
     */
    protected void validateAnnotationValue(T originatingElement,
                                           String annotationName,
                                           T member,
                                           String memberName,
                                           Object resolvedValue) {
        if (!validating) {
            return;
        }

        final AnnotatedElementValidator elementValidator = getElementValidator();
        if (elementValidator != null && !erroneousElements.contains(member)) {
            boolean shouldValidate = !(annotationName.equals(AliasFor.class.getName())) &&
                    (!(resolvedValue instanceof String) || !resolvedValue.toString().contains("${"));
            if (shouldValidate) {
                shouldValidate = isValidationRequired(member);
            }
            if (shouldValidate) {
                AnnotationMetadata metadata;
                try {
                    validating = false;
                    metadata = buildDeclared(member);
                } finally {
                    validating = true;
                }

                final Set<String> errors = elementValidator.validatedAnnotatedElement(new AnnotatedElement() {
                    @NonNull
                    @Override
                    public String getName() {
                        return memberName;
                    }

                    @Override
                    public AnnotationMetadata getAnnotationMetadata() {
                        return metadata;
                    }
                }, resolvedValue);

                if (CollectionUtils.isNotEmpty(errors)) {
                    erroneousElements.add(member);
                    for (String error : errors) {
                        error = "@" + NameUtils.getSimpleName(annotationName) + "." + memberName + ": " + error;
                        addError(originatingElement, error);
                    }
                }
            }
        }
    }

    /**
     * Return whether the given member requires validation.
     *
     * @param member The member
     * @return True if it is
     */
    protected abstract boolean isValidationRequired(T member);

    /**
     * Obtains the element validator.
     *
     * @return The validator.
     */
    @Nullable
    protected AnnotatedElementValidator getElementValidator() {
        return ELEMENT_VALIDATOR;
    }

    /**
     * Adds an error.
     *
     * @param originatingElement The originating element
     * @param error              The error
     */
    protected abstract void addError(@NonNull T originatingElement, @NonNull String error);

    /**
     * Adds a warning.
     *
     * @param originatingElement The originating element
     * @param warning            The warning
     */
    protected abstract void addWarning(@NonNull T originatingElement, @NonNull String warning);

    /**
     * Read the given member and value, applying conversions if necessary, and place the data in the given map.
     *
     * @param originatingElement The originating element
     * @param member             The member
     * @param annotationName     The annotation name
     * @param memberName         The member name
     * @param annotationValue    The value
     * @return The object
     */
    protected abstract Object readAnnotationValue(T originatingElement, T member, String annotationName, String memberName, Object annotationValue);

    /**
     * Read the raw default annotation values from the given annotation.
     *
     * @param annotationName annotation name
     * @param annotationType the type
     * @return The values
     */
    protected abstract Map<? extends T, ?> readAnnotationDefaultValues(String annotationName, T annotationType);

    /**
     * Read the raw annotation values from the given annotation.
     *
     * @param annotationMirror The annotation
     * @return The values
     */
    protected abstract Map<? extends T, ?> readAnnotationRawValues(A annotationMirror);

    /**
     * Resolve the annotations values from the given member for the given type.
     *
     * @param originatingElement The originating element
     * @param member             The member
     * @param annotationType     The type
     * @param <K>                The annotation type
     * @return The values
     */
    protected abstract <K extends Annotation> Optional<AnnotationValue<K>> getAnnotationValues(T originatingElement, T member, Class<K> annotationType);

    /**
     * Read the name of an annotation member.
     *
     * @param member The member
     * @return The name
     */
    protected abstract String getAnnotationMemberName(T member);

    /**
     * Obtain the name of the repeatable annotation if the annotation is one.
     *
     * @param annotationMirror The annotation mirror
     * @return Return the name or null
     */
    @Nullable
    protected abstract String getRepeatableName(A annotationMirror);

    /**
     * Obtain the name of the repeatable annotation if the annotation is one.
     *
     * @param annotationType The annotation mirror
     * @return Return the name or null
     */
    @Nullable
    protected abstract String getRepeatableContainerNameForType(T annotationType);

    /**
     * @param annotationElement The annotation element
     * @param annotationType    The annotation type
     * @return The annotation value
     */
    protected AnnotationValue<?> readNestedAnnotationValue(T annotationElement, A annotationType) {
        return readNestedAnnotationValue(annotationElement, annotationType, new HashMap<>());
    }

    /**
     * @param annotationElement The annotation element
     * @param annotationType    The annotation type
     * @param resolvedDefaults resoldved defaults
     * @return The annotation value
     */
    protected AnnotationValue<?> readNestedAnnotationValue(T annotationElement, A annotationType, Map<String, Map<CharSequence, Object>> resolvedDefaults) {
        final String annotationTypeName = getAnnotationTypeName(annotationType);
        Map<? extends T, ?> annotationValues = readAnnotationRawValues(annotationType);
        T theType = getTypeForAnnotation(annotationType);
        if (annotationValues.isEmpty()) {
            Map<CharSequence, Object> annotationDefaults = resolveAnnotationDefaults(theType, annotationTypeName, resolvedDefaults);
            return new AnnotationValue<>(annotationTypeName, Collections.emptyMap(), annotationDefaults);
        }

        Map<CharSequence, Object> resolvedValues = CollectionUtils.newLinkedHashMap(annotationValues.size());
        for (Map.Entry<? extends T, ?> entry : annotationValues.entrySet()) {
            T member = entry.getKey();
            Optional<AnnotationValue<AliasFor>> aliasForValues = getAnnotationValues(annotationElement, member, AliasFor.class);
            Object annotationValue = entry.getValue();
            if (aliasForValues.isPresent()) {
                AnnotationValue<AliasFor> aliasFor = aliasForValues.get();
                Optional<String> aliasMember = aliasFor.stringValue("member");
                Optional<String> aliasAnnotation = aliasFor.stringValue("annotation");
                Optional<String> aliasAnnotationName = aliasFor.stringValue("annotationName");
                if (aliasMember.isPresent() && !(aliasAnnotation.isPresent() || aliasAnnotationName.isPresent())) {
                    String aliasedNamed = aliasMember.get();
                    readAnnotationRawValues(annotationElement,
                            annotationTypeName,
                            member,
                            aliasedNamed,
                            annotationValue,
                            resolvedValues);
                }
            }
            String memberName = getAnnotationMemberName(member);
            readAnnotationRawValues(annotationElement,
                    annotationTypeName,
                    member,
                    memberName,
                    annotationValue,
                    resolvedValues);

        }
        Map<CharSequence, Object> annotationDefaults = resolveAnnotationDefaults(theType, annotationTypeName, resolvedDefaults);
        return new AnnotationValue<>(annotationTypeName, resolvedValues, annotationDefaults);
    }

    private Map<CharSequence, Object> resolveAnnotationDefaults(T annotationElement, String annotationTypeName, Map<String, Map<CharSequence, Object>> resolvedDefaults) {
        Map<CharSequence, Object> defaults = resolvedDefaults.get(annotationTypeName);
        if (defaults != null) {
            return defaults;
        }
        defaults = new LinkedHashMap<>();
        // To prevent recursion we set the map and modify it later
        resolvedDefaults.put(annotationTypeName, defaults);
        Map<? extends T, ?> nativeDefaultValues = readAnnotationDefaultValues(annotationTypeName, annotationElement);
        Map<CharSequence, Object> annotationDefaults = getAnnotationDefaults(annotationElement, annotationTypeName, nativeDefaultValues, resolvedDefaults);
        defaults.putAll(annotationDefaults);
        return annotationDefaults;
    }

    /**
     * Return a mirror for the given annotation.
     *
     * @param annotationName The annotation name
     * @return An optional mirror
     */
    protected abstract Optional<T> getAnnotationMirror(String annotationName);

    /**
     * Detect evaluated expression in annotation value.
     *
     * @param value - Annotation value
     * @return if value contains evaluated expression
     */
    protected boolean isEvaluatedExpression(@Nullable Object value) {
        return (value instanceof String str && str.matches(EXPRESSION_PATTERN))
                   || (value instanceof String[] strArray &&
                           Arrays.stream(strArray).anyMatch(this::isEvaluatedExpression));
    }

    /**
     * Wraps original annotation value to make it processable at later stages.
     *
     * @param originatingElement originating annotated element
     * @param annotationName annotation name
     * @param memberName annotation member name
     * @param initialAnnotationValue original annotation value
     * @return expression reference
     */
    @NonNull
    protected Object buildEvaluatedExpressionReference(@NonNull T originatingElement,
                                                       @NonNull String annotationName,
                                                       @NonNull String memberName,
                                                       @NonNull Object initialAnnotationValue) {
        String originatingClassName = getOriginatingClassName(originatingElement);
        if (originatingClassName != null) {
            String packageName = NameUtils.getPackageName(originatingClassName);
            String simpleClassName = NameUtils.getSimpleName(originatingClassName);
            String exprClassName = "%s.$%s%s".formatted(packageName, simpleClassName, EvaluatedExpressionReferenceCounter.EXPR_SUFFIX);

            Integer expressionIndex = EvaluatedExpressionReferenceCounter.nextIndex(exprClassName);

            return new EvaluatedExpressionReference(initialAnnotationValue, annotationName, memberName, exprClassName + expressionIndex);
        } else {
            return initialAnnotationValue;
        }

    }

    @Nullable
    protected abstract String getOriginatingClassName(@NonNull T orginatingElement);

    /**
     * Get the annotation member.
     *
     * @param annotationElement The annotation element
     * @param member            The member
     * @return The annotation member element
     */
    @Nullable
    protected abstract T getAnnotationMember(T annotationElement, CharSequence member);

    /**
     * Obtain the annotation mappers for the given annotation name.
     *
     * @param annotationName The annotation name
     * @param <K>            The annotation type
     * @return The mappers
     */
    @NonNull
    protected <K extends Annotation> List<AnnotationMapper<K>> getAnnotationMappers(@NonNull String annotationName) {
        return (List) ANNOTATION_MAPPERS.get(annotationName);
    }

    /**
     * Obtain the transformers mappers for the given annotation name.
     *
     * @param annotationName The annotation name
     * @param <K>            The annotation type
     * @return The transformers
     */
    @NonNull
    protected <K extends Annotation> List<AnnotationTransformer<K>> getAnnotationTransformers(@NonNull String annotationName) {
        return (List) ANNOTATION_TRANSFORMERS.get(annotationName);
    }

    /**
     * Returns the visitor context for this implementation.
     *
     * @return The visitor context
     */
    protected abstract VisitorContext getVisitorContext();

    private Map<CharSequence, Object> getAnnotationDefaults(T originatingElement,
                                                            String annotationName,
                                                            Map<? extends T, ?> elementDefaultValues,
                                                            Map<String, Map<CharSequence, Object>> resolvedDefaults) {
        if (elementDefaultValues == null) {
            return null;
        }
        Map<CharSequence, Object> defaultValues = CollectionUtils.newLinkedHashMap(elementDefaultValues.size());
        for (Map.Entry<? extends T, ?> entry : elementDefaultValues.entrySet()) {
            T member = entry.getKey();
            String memberName = getAnnotationMemberName(member);
            if (!defaultValues.containsKey(memberName)) {
                Object annotationValue = entry.getValue();
                readAnnotationRawValues(originatingElement,
                        annotationName,
                        member,
                        memberName,
                        annotationValue,
                        defaultValues,
                        resolvedDefaults);
            }
        }
        return defaultValues;
    }

    @Nullable
    private void processAnnotationAlias(Map<CharSequence, Object> annotationValues,
                                        Object annotationValue,
                                        AnnotationValue<AliasFor> aliasForAnnotation,
                                        List<ProcessedAnnotation> introducedAnnotations) {
        Optional<String> aliasAnnotation = aliasForAnnotation.stringValue("annotation");
        Optional<String> aliasAnnotationName = aliasForAnnotation.stringValue("annotationName");
        Optional<String> aliasMember = aliasForAnnotation.stringValue("member");

        if (aliasAnnotation.isPresent() || aliasAnnotationName.isPresent()) {
            if (aliasMember.isPresent()) {
                String aliasedAnnotation;
                aliasedAnnotation = aliasAnnotation.orElseGet(aliasAnnotationName::get);
                String aliasedMemberName = aliasMember.get();
                if (annotationValue != null) {
                    ProcessedAnnotation newAnnotation = toProcessedAnnotation(
                            AnnotationValue.builder(aliasedAnnotation, getRetentionPolicy(aliasedAnnotation))
                                    .members(Collections.singletonMap(aliasedMemberName, annotationValue))
                                    .build()
                    );
                    introducedAnnotations.add(newAnnotation);
                    ProcessedAnnotation newNewAnnotation = processAliases(newAnnotation, introducedAnnotations);
                    if (newNewAnnotation != newAnnotation) {
                        introducedAnnotations.set(introducedAnnotations.indexOf(newAnnotation), newNewAnnotation);
                    }
                }
            }
        } else if (aliasMember.isPresent()) {
            String aliasedNamed = aliasMember.get();
            if (annotationValue != null) {
                annotationValues.put(aliasedNamed, annotationValue);
            }
        }
    }

    /**
     * Gets the retention policy for the given annotation.
     *
     * @param annotation The annotation
     * @return The retention policy
     */
    @NonNull
    protected abstract RetentionPolicy getRetentionPolicy(@NonNull T annotation);

    /**
     * Gets the retention policy for the given annotation.
     *
     * @param annotation The annotation
     * @return The retention policy
     */
    @NonNull
    public RetentionPolicy getRetentionPolicy(@NonNull String annotation) {
        return getAnnotationMirror(annotation).map(this::getRetentionPolicy).orElse(RetentionPolicy.RUNTIME);
    }

    private AnnotationMetadata buildInternalMulti(
            List<T> parents,
            T element,
            MutableAnnotationMetadata annotationMetadata,
            boolean inheritTypeAnnotations,
            boolean declaredOnly) {
        List<T> hierarchy = buildHierarchy(element, inheritTypeAnnotations, declaredOnly);
        for (T parent : parents) {
            final List<T> parentHierarchy = buildHierarchy(parent, inheritTypeAnnotations, declaredOnly);
            if (hierarchy.isEmpty() && !parentHierarchy.isEmpty()) {
                hierarchy = parentHierarchy;
            } else {
                hierarchy.addAll(0, parentHierarchy);
            }
        }
        Collections.reverse(hierarchy);
        for (T currentElement : hierarchy) {
            if (currentElement == null) {
                continue;
            }
            List<? extends A> annotationHierarchy = getAnnotationsForType(currentElement);

            if (annotationHierarchy.isEmpty()) {
                continue;
            }

            boolean originatingElementIsSameParent = parents.contains(currentElement);
            boolean isDeclared = currentElement == element;
            addAnnotations(
                    annotationMetadata,
                    currentElement,
                    originatingElementIsSameParent,
                    isDeclared,
                    annotationHierarchy
            );

        }
        if (!annotationMetadata.hasDeclaredStereotype(AnnotationUtil.SCOPE) && annotationMetadata.hasDeclaredStereotype(
                DefaultScope.class)) {
            Optional<String> value = annotationMetadata.stringValue(DefaultScope.class);
            value.ifPresent(name -> annotationMetadata.addDeclaredAnnotation(name, Collections.emptyMap()));
        }
        postProcess(annotationMetadata, element);
        return annotationMetadata;
    }

    protected void postProcess(MutableAnnotationMetadata mutableAnnotationMetadata,
                               T element) {
        //no-op
    }

    private void addAnnotations(MutableAnnotationMetadata annotationMetadata,
                                T element,
                                boolean alwaysIncludeAnnotation,
                                boolean isDeclared,
                                List<? extends A> annotationHierarchy) {
        Stream<? extends A> stream = annotationHierarchy.stream();
        Stream<ProcessedAnnotation> annotationValues = annotationMirrorToAnnotationValue(stream, element);
        addAnnotations(annotationMetadata, annotationValues, isDeclared, alwaysIncludeAnnotation);
    }

    @NonNull
    private Stream<ProcessedAnnotation> annotationMirrorToAnnotationValue(Stream<? extends A> stream,
                                                                          T element) {
        return stream
                .filter(annotationMirror -> {
                    String annotationName = getAnnotationTypeName(annotationMirror);
                    if (!annotationName.equals(AnnotationUtil.ANN_INHERITED)
                            && (AnnotationUtil.INTERNAL_ANNOTATION_NAMES.contains(annotationName) || isExcludedAnnotation(element, annotationName))) {
                        return false;
                    }
                    if (DEPRECATED_ANNOTATION_NAMES.containsKey(annotationName)) {
                        addWarning(element,
                                "Usages of deprecated annotation " + annotationName + " found. You should use " + DEPRECATED_ANNOTATION_NAMES.get(
                                        annotationName) + " instead.");
                    }
                    return true;
                }).map(annotationMirror -> createAnnotationValue(element, annotationMirror));
    }

    // The value of this method can be cached
    @NonNull
    private ProcessedAnnotation createAnnotationValue(@NonNull T originatingElement,
                                                      @NonNull A annotationMirror) {
        String annotationName = getAnnotationTypeName(annotationMirror);
        final T annotationType = getTypeForAnnotation(annotationMirror);
        RetentionPolicy retentionPolicy = getRetentionPolicy(annotationType);

        Map<? extends T, ?> elementValues = readAnnotationRawValues(annotationMirror);
        Map<CharSequence, Object> annotationValues;
        if (CollectionUtils.isEmpty(elementValues)) {
            annotationValues = new LinkedHashMap<>(3);
        } else {
            annotationValues = new LinkedHashMap<>(5);
            Set<String> nonBindingMembers = new LinkedHashSet<>(2);
            for (Map.Entry<? extends T, ?> entry : elementValues.entrySet()) {
                T member = entry.getKey();

                if (member == null) {
                    continue;
                }

                Object annotationValue = entry.getValue();
                if (hasAnnotations(member)) {
                    final MutableAnnotationMetadata memberMetadata = new MutableAnnotationMetadata();
                    final List<? extends A> annotationsForMember = getAnnotationsForType(member)
                            .stream().filter((a) -> !getAnnotationTypeName(a).equals(annotationName))
                            .toList();

                    addAnnotations(memberMetadata, member, false,
                            true, annotationsForMember);

                    boolean isInstantiatedMember = memberMetadata.hasAnnotation(InstantiatedMember.class);

                    if (memberMetadata.hasAnnotation(NonBinding.class)) {
                        final String memberName = getElementName(member);
                        nonBindingMembers.add(memberName);
                    }
                    if (isInstantiatedMember) {
                        final String memberName = getAnnotationMemberName(member);
                        final Object rawValue = readAnnotationValue(originatingElement, member, annotationName, memberName, annotationValue);
                        if (rawValue instanceof AnnotationClassValue<?> annotationClassValue) {
                            annotationValues.put(memberName, new AnnotationClassValue<>(annotationClassValue.getName(), true));
                        }
                    }
                }

                readAnnotationRawValues(originatingElement,
                        annotationName,
                        member,
                        getAnnotationMemberName(member),
                        annotationValue,
                        annotationValues);

            }

            if (!nonBindingMembers.isEmpty()) {
                nonBindingMembers.add(AnnotationUtil.NON_BINDING_ATTRIBUTE);
                annotationValues.put(AnnotationUtil.NON_BINDING_ATTRIBUTE, nonBindingMembers.toArray(String[]::new));
            }
        }

        Map<CharSequence, Object> defaultValues = getCachedAnnotationDefaults(annotationName, annotationType);

        return new ProcessedAnnotation(
                annotationType,
                new AnnotationValue<>(annotationName, annotationValues, defaultValues, retentionPolicy)
        );
    }

    /**
     * Get the cached annotation defaults.
     *
     * @param annotationName The annotation name
     * @param annotationType The annotation type
     * @return The defaults
     */
    @NonNull
    protected Map<CharSequence, Object> getCachedAnnotationDefaults(String annotationName, T annotationType) {
        Map<CharSequence, Object> defaultValues;
        final Map<CharSequence, Object> defaults = ANNOTATION_DEFAULTS.get(annotationName);
        if (defaults != null) {
            defaultValues = new LinkedHashMap<>(defaults);
        } else {
            Map<? extends T, ?> annotationDefaultValues = readAnnotationDefaultValues(annotationName, annotationType);
            defaultValues = getAnnotationDefaults(annotationType, annotationName, annotationDefaultValues, new HashMap<>());
            if (defaultValues != null && !defaultValues.isEmpty()) {
                // Don't cache empty values is it can be invalid annotation type provided by KSP or Groovy
                // Add the default for any retention type annotation
                ANNOTATION_DEFAULTS.put(annotationName, new LinkedHashMap<>(defaultValues));
            } else {
                defaultValues = Collections.emptyMap();
            }
        }
        return defaultValues;
    }

    private void handleAnnotationAlias(T originatingElement,
                                       Map<CharSequence, Object> annotationValues,
                                       T annotationMember,
                                       Object annotationValue,
                                       List<ProcessedAnnotation> introducedAnnotations) {
        Optional<AnnotationValue<Aliases>> aliases = getAnnotationValues(originatingElement, annotationMember, Aliases.class);
        if (aliases.isPresent()) {
            for (AnnotationValue<AliasFor> av : aliases.get().<AliasFor>getAnnotations(AnnotationMetadata.VALUE_MEMBER)) {
                processAnnotationAlias(
                        annotationValues,
                        annotationValue,
                        av,
                        introducedAnnotations
                );
            }
        } else {
            Optional<AnnotationValue<AliasFor>> aliasForValues = getAnnotationValues(originatingElement, annotationMember, AliasFor.class);
            if (aliasForValues.isPresent()) {
                processAnnotationAlias(
                        annotationValues,
                        annotationValue,
                        aliasForValues.get(),
                        introducedAnnotations
                );
            }
        }
    }

    private void addAnnotations(@NonNull MutableAnnotationMetadata annotationMetadata,
                                @NonNull Stream<ProcessedAnnotation> stream,
                                boolean isDeclared,
                                boolean alwaysIncludeAnnotation) {

        ProcessingContext processingContext = new ProcessingContext(getVisitorContext());

        List<AnnotationValue<?>> annotationValues = stream
                .flatMap(processedAnnotation -> processAnnotation(processingContext, processedAnnotation))
                .<AnnotationValue<?>>map(ProcessedAnnotation::getAnnotationValue)
                .toList();

        addAnnotations(annotationMetadata, isDeclared, false, alwaysIncludeAnnotation, List.of(
                Map.entry(
                        List.of(), annotationValues
                )
        ), processingContext.repeatableToContainer);
    }

    private void addAnnotations(@NonNull MutableAnnotationMetadata annotationMetadata,
                                boolean isDeclared,
                                boolean isStereotype,
                                boolean alwaysIncludeAnnotation,
                                @NonNull List<Map.Entry<List<String>, List<AnnotationValue<?>>>> annotations,
                                @NonNull Map<String, String> repeatableToContainer) {

        // We need to add annotations by their levels:
        // 1. The annotation
        // 2. Stereotypes defined on the annotation
        // 3. The stereotypes of the stereotypes added in #2
        // 3. The stereotypes of the stereotypes added in #3 etc

        List<Map.Entry<List<String>, List<AnnotationValue<?>>>> stereotypes = new ArrayList<>(annotations.size());

        for (Map.Entry<List<String>, List<AnnotationValue<?>>> e : annotations) {
            List<String> parentAnnotations = e.getKey();
            for (AnnotationValue<?> annotationValue : e.getValue()) {
                if (annotationValue.getAnnotationName().equals(AnnotationUtil.ANN_INHERITED)) {
                    continue;
                }
                if (isDeclared || isStereotype || alwaysIncludeAnnotation || isInherited(annotationValue.getStereotypes())) {

                    addAnnotation(
                            annotationMetadata,
                            parentAnnotations,
                            isDeclared,
                            isStereotype,
                            annotationValue,
                            repeatableToContainer);

                    List<String> newParentAnnotations = CollectionUtils.concat(parentAnnotations, annotationValue.getAnnotationName());

                    if (annotationValue.getStereotypes() != null) {
                        stereotypes.add(Map.entry(
                                newParentAnnotations,
                                annotationValue.getStereotypes()
                        ));
                    }

                }
            }

        }

        if (!stereotypes.isEmpty()) {
            addAnnotations(annotationMetadata, isDeclared, true, alwaysIncludeAnnotation, stereotypes, repeatableToContainer);
        }
    }

    private boolean isInherited(@Nullable List<AnnotationValue<?>> stereotypes) {
        if (stereotypes == null) {
            return false;
        }
        return stereotypes.stream().anyMatch(av -> av.getAnnotationName().equals(AnnotationUtil.ANN_INHERITED));
    }

    @NonNull
    private Stream<ProcessedAnnotation> processAnnotation(@NonNull ProcessingContext context,
                                                          @NonNull ProcessedAnnotation processedAnnotation) {

        AnnotationValue<?> annotationValue = processedAnnotation.getAnnotationValue();
        if (AnnotationUtil.INTERNAL_ANNOTATION_NAMES.contains(annotationValue.getAnnotationName()) || context.isProcessed(annotationValue)) {
            return Stream.empty();
        }
        if (processedAnnotation.getAnnotationType() != null) {
            String repeatableContainer = getRepeatableContainerNameForType(processedAnnotation.getAnnotationType());
            if (repeatableContainer != null) {
                // Collect the repeatable container from the annotation mirror for later
                context.repeatableToContainer.put(annotationValue.getAnnotationName(), repeatableContainer);
            }
        }

        // Add annotation default values
        processedAnnotation = addDefaults(processedAnnotation);
        // Check if the annotation has the stereotypes set manually, before adding alias stereotypes
        boolean stereotypesProvided = annotationValue.getStereotypes() != null;
        // First we need to process aliases, those contribute stereotypes with higher priority
        processedAnnotation = processAliases(context, processedAnnotation);

        // The next invocation will invoke current method recursively till the stereotypes are processed.
        // That will build an annotation value tree with annotations and it's stereotypes.
        processedAnnotation = addStereotypes(context, processedAnnotation, stereotypesProvided);
        // Next step is transforming, starting from the stereotypes moving up in the hierarchy.
        return transform(context, processedAnnotation)
                .flatMap(this::flattenRepeatable)
                .map(this::addDefaults);
    }

    @NonNull
    private ProcessedAnnotation processAliases(@NonNull ProcessingContext context,
                                               @NonNull ProcessedAnnotation processedAnnotation) {
        // Aliases produces by the annotations are added to the stereotypes collection
        List<ProcessedAnnotation> introducedAliasForAnnotations = new ArrayList<>();
        ProcessedAnnotation newAnn = processAliases(processedAnnotation, introducedAliasForAnnotations);
        if (!introducedAliasForAnnotations.isEmpty()) {
            newAnn = newAnn.mutateAnnotationValue(builder ->
                    builder.stereotypes(
                                    introducedAliasForAnnotations.stream()
                                            .flatMap(a -> processAnnotation(context, a))
                                            .<AnnotationValue<?>>map(ProcessedAnnotation::getAnnotationValue)
                                            .toList()
                            )
            );
        }
        return newAnn;
    }

    @NonNull
    private ProcessedAnnotation addStereotypes(@NonNull ProcessingContext context,
                                               @NonNull ProcessedAnnotation processedAnnotation,
                                               boolean stereotypesProvided) {
        List<ProcessedAnnotation> stereotypes = Collections.emptyList();
        if (processedAnnotation.getAnnotationValue().getStereotypes() != null) {
            stereotypes = processedAnnotation.getAnnotationValue().getStereotypes().stream()
                .map(this::toProcessedAnnotation)
                .flatMap(this::flattenRepeatable)
                .map(this::addDefaults)
                .toList();
        }
        if (!stereotypesProvided) {
            // The annotation doesn't have the stereotypes set manually
            // In this case `stereotypes` are aliases
            List<ProcessedAnnotation> extractedStereotypes = extractStereotypes(context, processedAnnotation);
            stereotypes = Stream.concat(
                // Some implementation like KSP and Groovy cannot extract proper compiler annotation type from an annotation name (@AliasFor(annotationName))
                // That prevents us to properly extract the annotation defaults
                // To fix it for the aliases we will try to set the annotation type and the defaults from the stereotype of the same annotation if present
                stereotypes.stream().map(annotation -> {
                    for (ProcessedAnnotation extractedStereotype : extractedStereotypes) {
                        if (extractedStereotype.getAnnotationValue().getAnnotationName().equals(annotation.getAnnotationValue().getAnnotationName())) {
                            return annotation
                                .withAnnotationType(extractedStereotype.annotationType)
                                .mutateAnnotationValue(builder -> builder.defaultValues(extractedStereotype.annotationValue.getDefaultValues()));
                        }
                    }
                    return annotation;
                }),
                extractedStereotypes.stream()
            ).toList();
        }
        List<ProcessedAnnotation> addedStereotypes = getAddedStereotypes(context, processedAnnotation.annotationType);
        if (!addedStereotypes.isEmpty()) {
            stereotypes = CollectionUtils.concat(stereotypes, addedStereotypes);
        }
        List<ProcessedAnnotation> finalStereotypes = stereotypes;
        return processedAnnotation.mutateAnnotationValue(builder ->
            builder.replaceStereotypes(finalStereotypes.stream().<AnnotationValue<?>>map(ProcessedAnnotation::getAnnotationValue).toList())
        );
    }

    private ProcessedAnnotation addDefaults(ProcessedAnnotation processedAnnotation) {
        if (processedAnnotation.getAnnotationValue().getDefaultValues() != null) {
            return processedAnnotation;
        }
        if (processedAnnotation.annotationType != null) {
            Map<CharSequence, Object> annotationDefaults = getCachedAnnotationDefaults(
                    processedAnnotation.getAnnotationValue().getAnnotationName(),
                    processedAnnotation.annotationType
            );
            processedAnnotation = processedAnnotation.mutateAnnotationValue(builder -> builder.defaultValues(annotationDefaults));
        } else {
            processedAnnotation = processedAnnotation.mutateAnnotationValue(builder -> builder.defaultValues(Collections.emptyMap()));
        }
        return processedAnnotation;
    }

    @NonNull
    private List<ProcessedAnnotation> extractStereotypes(@NonNull ProcessingContext context,
                                                         @NonNull ProcessedAnnotation processedAnnotation) {
        AnnotationValue<?> annotationValue = processedAnnotation.getAnnotationValue();
        ProcessingContext newContext = context.withParent(processedAnnotation.annotationValue);

        if (processedAnnotation.annotationType == null) {
            // The annotation is not on the classpath
            // We set an empty collection to mark that stereotypes are processed
            return Collections.emptyList();
        }

        List<? extends A> nativeStereotypes = getAnnotationsForType(processedAnnotation.annotationType);
        if (nativeStereotypes.isEmpty()) {
            // We set an empty collection to mark that stereotypes are processed
            return Collections.emptyList();
        }
        String annotationName = annotationValue.getAnnotationName();
        String packageName = NameUtils.getPackageName(annotationName);
        boolean excludesStereotypes = AnnotationUtil.STEREOTYPE_EXCLUDES.contains(packageName) || annotationName.endsWith(".Nullable");
        return annotationMirrorToAnnotationValue(nativeStereotypes.stream(), processedAnnotation.annotationType)
                .filter(stereotypeAnnotation -> {
                    AnnotationValue<?> stereotypeAnnotationValue = stereotypeAnnotation.getAnnotationValue();
                    String stereotypeName = stereotypeAnnotationValue.getAnnotationName();
                    if (stereotypeName.equals(AnnotationUtil.ANN_INHERITED)) {
                        return true;
                    }
                    if (excludesStereotypes) {
                        return false;
                    }
                    // special case: don't add stereotype for @Nonnull when it's marked as UNKNOWN/MAYBE/NEVER.
                    // https://github.com/micronaut-projects/micronaut-core/issues/6795
                    if (stereotypeName.equals("jakarta.annotation.Nonnull")) {
                        String when = Objects.toString(stereotypeAnnotationValue.getValues().get("when"));
                        return !(when.equals("UNKNOWN") || when.equals("MAYBE") || when.equals("NEVER"));
                    }
                    return true;
                }).flatMap(stereotype -> processAnnotation(newContext, stereotype)).toList();
    }

    @NonNull
    private Stream<ProcessedAnnotation> transform(@NonNull ProcessingContext context,
                                                  @NonNull ProcessedAnnotation toTransform) {
        // Transform annotation using:
        // - io.micronaut.inject.annotation.AnnotationMapper
        // - io.micronaut.inject.annotation.AnnotationRemapper
        // - io.micronaut.inject.annotation.AnnotationTransformer
        // Each result of the transformation will be also transformed
        return processAnnotationMappers(context, toTransform)
                .flatMap(annotation -> processAnnotationRemappers(context, annotation))
                .flatMap(annotation -> processAnnotationTransformers(context, annotation));
    }

    @NonNull
    private Stream<ProcessedAnnotation> flattenRepeatable(@NonNull ProcessedAnnotation processedAnnotation) {
        // In a case of a repeatable container process it as a stream of repeatable annotation values
        AnnotationValue<?> annotationValue = processedAnnotation.getAnnotationValue();
        if (isRepeatableAnnotationContainer(annotationValue)) {
            // Repeatable annotations container is being added with values
            // We will add every repeatable annotation separately to properly detect its container and run transformations
            Map<CharSequence, Object> containerValues = new LinkedHashMap<>(annotationValue.getValues());
            containerValues.remove(AnnotationMetadata.VALUE_MEMBER);
            return Stream.concat(
                    Stream.of(
                            // Add repeatable container for possible stereotype annotation retrieval
                            // and additional members defined in the container annotation
                            toProcessedAnnotation(new AnnotationValue<>(
                                    annotationValue.getAnnotationName(),
                                    containerValues,
                                    getRetentionPolicy(annotationValue.getAnnotationName())))
                    ),
                    annotationValue.getAnnotations(AnnotationMetadata.VALUE_MEMBER).stream().map(this::toProcessedAnnotation)
            );
        }
        return Stream.of(processedAnnotation);
    }

    /**
     * @param annotationValue The annotation value
     * @return true if the annotation is a repeatable container
     */
    protected boolean isRepeatableAnnotationContainer(AnnotationValue<?> annotationValue) {
        List<AnnotationValue<Annotation>> repeatableAnnotations = annotationValue.getAnnotations(AnnotationMetadata.VALUE_MEMBER);
        if (repeatableAnnotations.isEmpty()) {
            return false;
        }
        String repeatableAnnotationName = null;
        for (AnnotationValue<Annotation> repeatableAnnotation : repeatableAnnotations) {
            if (repeatableAnnotationName == null) {
                repeatableAnnotationName = repeatableAnnotation.getAnnotationName();
            } else if (!repeatableAnnotationName.equals(repeatableAnnotation.getAnnotationName())) {
                // Unexpected state: different repeatable name
                return false;
            }
        }
        // The container should be this annotation value name
        return findRepeatableContainerNameForType(repeatableAnnotationName) != null;
    }

    @NonNull
    private ProcessedAnnotation processAliases(@NonNull ProcessedAnnotation processedAnnotation,
                                               @NonNull List<ProcessedAnnotation> introducedAnnotations) {
        T annotationType = processedAnnotation.getAnnotationType();
        if (annotationType == null) {
            return processedAnnotation;
        }
        AnnotationValue<?> annotationValue = processedAnnotation.getAnnotationValue();
        Map<CharSequence, Object> newValues = new LinkedHashMap<>(annotationValue.getValues());
        for (Map.Entry<CharSequence, Object> entry : annotationValue.getValues().entrySet()) {
            CharSequence key = entry.getKey();
            Object value = entry.getValue();
            T member = getAnnotationMember(annotationType, key);
            if (member != null) {
                handleAnnotationAlias(
                        annotationType,
                        newValues,
                        member,
                        value,
                        introducedAnnotations
                );
            }
        }

        // @AliasFor can modify the annotation values by aliasing to a member from the same annotation
        if (newValues.equals(annotationValue.getValues())) {
            return processedAnnotation;
        }
        return processedAnnotation.mutateAnnotationValue(builder -> builder.members(newValues));
    }

    private void addAnnotation(@NonNull MutableAnnotationMetadata mutableAnnotationMetadata,
                               @NonNull List<String> parentAnnotations,
                               boolean isDeclared,
                               boolean isStereotype,
                               @NonNull AnnotationValue<?> annotationValue,
                               @NonNull Map<String, String> repeatableToContainer) {

        String annotationName = annotationValue.getAnnotationName();
        Map<CharSequence, Object> annotationDefaults = annotationValue.getDefaultValues();
        if (annotationDefaults != null) {
            mutableAnnotationMetadata.addDefaultAnnotationValues(annotationName, annotationDefaults, annotationValue.getRetentionPolicy());
        } else {
            throw new IllegalStateException("Annotation should contain default values and an empty list " + annotationValue.getAnnotationName());
        }

        String repeatableContainer = repeatableToContainer.get(annotationName);
        if (repeatableContainer == null) {
            repeatableContainer = AnnotationMetadataSupport.getCoreRepeatableAnnotationsContainers().get(annotationName);
        }
        if (repeatableContainer == null) {
            repeatableContainer = findRepeatableContainerNameForType(annotationName);
        }
        if (isStereotype) {
            if (repeatableContainer != null) {
                if (isDeclared) {
                    mutableAnnotationMetadata.addDeclaredRepeatableStereotype(
                            parentAnnotations,
                            repeatableContainer,
                            annotationValue
                    );
                } else {
                    mutableAnnotationMetadata.addRepeatableStereotype(
                            parentAnnotations,
                            repeatableContainer,
                            annotationValue
                    );
                }
            } else {
                if (isDeclared) {
                    mutableAnnotationMetadata.addDeclaredStereotype(
                            parentAnnotations,
                            annotationValue.getAnnotationName(),
                            annotationValue.getValues(),
                            annotationValue.getRetentionPolicy()
                    );
                } else {
                    mutableAnnotationMetadata.addStereotype(
                            parentAnnotations,
                            annotationValue.getAnnotationName(),
                            annotationValue.getValues(),
                            annotationValue.getRetentionPolicy()
                        );
                }
            }
        } else {
            if (repeatableContainer != null) {
                if (isDeclared) {
                    mutableAnnotationMetadata.addDeclaredRepeatable(repeatableContainer, annotationValue);
                } else {
                    mutableAnnotationMetadata.addRepeatable(repeatableContainer, annotationValue);
                }
            } else {
                if (isDeclared) {
                    mutableAnnotationMetadata.addDeclaredAnnotation(
                            annotationValue.getAnnotationName(),
                            annotationValue.getValues(),
                            annotationValue.getRetentionPolicy()
                    );
                } else {
                    mutableAnnotationMetadata.addAnnotation(
                            annotationValue.getAnnotationName(),
                            annotationValue.getValues(),
                            annotationValue.getRetentionPolicy()
                    );
                }
            }
        }
    }

    /**
     * Find the repeatable container for given annotation type.
     * @param annotationName The repeatable annotation
     * @return The repeatable container if exists
     */
    @Nullable
    protected String findRepeatableContainerNameForType(@NonNull String annotationName) {
        T annotationMirror = getAnnotationMirror(annotationName).orElse(null);
        return annotationMirror != null ? getRepeatableContainerNameForType(annotationMirror) : null;
    }

    /**
     * Is the given annotation excluded for the specified element.
     *
     * @param element        The element
     * @param annotationName The annotation name
     * @return True if it is excluded
     */
    protected boolean isExcludedAnnotation(@NonNull T element, @NonNull String annotationName) {
        return AnnotationUtil.INTERNAL_ANNOTATION_NAMES.contains(annotationName);
    }

    @NonNull
    private List<ProcessedAnnotation> getAddedStereotypes(@NonNull ProcessingContext context,
                                                          @Nullable T element) {
        if (element == null) {
            return List.of();
        }
        CachedAnnotationMetadata modifiedStereotypes = MUTATED_ANNOTATION_METADATA.get(element);
        if (modifiedStereotypes == null || modifiedStereotypes.isEmpty() || !modifiedStereotypes.isMutated()) {
            return List.of();
        }
        return Stream.concat(
                modifiedStereotypes.getStereotypeAnnotationNames().stream().flatMap(stereotypeName -> {
                    final AnnotationValue<Annotation> a = modifiedStereotypes.getAnnotation(stereotypeName);
                    if (a == null) {
                        return Stream.of();
                    }
                    AnnotationValue<?> parent = null;
                    final List<String> stereotypeParents = modifiedStereotypes.getAnnotationNamesByStereotype(stereotypeName);
                    for (String stereotype : stereotypeParents) {
                        AnnotationValue<Annotation> annotationValue = AnnotationValue.builder(stereotype).build();
                        if (parent == null) {
                            parent = annotationValue;
                        } else {
                            parent = parent.mutate().stereotype(annotationValue).build();
                        }
                    }
                    if (parent == null) {
                        return processAnnotation(
                                context.withParents(stereotypeParents),
                                toProcessedAnnotation(a)
                        );
                    } else {
                        return processAnnotation(
                                context.withParents(stereotypeParents),
                                toProcessedAnnotation(parent.mutate().stereotype(a).build())
                        );
                    }

                }),
                modifiedStereotypes.getAnnotationNames().stream().flatMap(annotationName -> {
                    AnnotationValue<Annotation> a = modifiedStereotypes.getAnnotation(annotationName);
                    if (a == null) {
                        return Stream.empty();
                    }
                    return processAnnotation(
                            context,
                            toProcessedAnnotation(a)
                    );
                })
        ).toList();
    }

    @NonNull
    private <K> List<K> eliminateProcessed(@NonNull ProcessingContext context, @NonNull List<K> visitors) {
        if (visitors == null) {
            return Collections.emptyList();
        }
        return visitors.stream().filter(v -> !context.processedVisitors.contains(v.getClass())).toList();
    }

    @NonNull
    private Stream<ProcessedAnnotation> processAnnotationRemappers(@NonNull ProcessingContext context,
                                                                   @NonNull ProcessedAnnotation processedAnnotation) {
        AnnotationValue<?> annotationValue = processedAnnotation.getAnnotationValue();
        String packageName = NameUtils.getPackageName(annotationValue.getAnnotationName());
        List<AnnotationRemapper> annotationRemappers = ANNOTATION_REMAPPERS.get(packageName);
        if (annotationRemappers == null) {
            annotationRemappers = ALL_ANNOTATION_REMAPPERS;
        } else {
            annotationRemappers = CollectionUtils.concat(annotationRemappers, ALL_ANNOTATION_REMAPPERS);
        }
        annotationRemappers = eliminateProcessed(context, annotationRemappers);
        return remapAnnotation(
                context,
                processedAnnotation,
                annotationValue,
                annotationRemappers.iterator()
        );
    }

    @NonNull
    private Stream<ProcessedAnnotation> remapAnnotation(@NonNull ProcessingContext context,
                                                        @NonNull ProcessedAnnotation processedAnnotation,
                                                        @NonNull AnnotationValue<?> annotationValue,
                                                        @NonNull Iterator<AnnotationRemapper> remappers) {
        if (!remappers.hasNext()) {
            return Stream.of(processedAnnotation);
        }
        AnnotationRemapper annotationRemapper = remappers.next();
        ProcessingContext newContext = context.withProcessedVisitor(annotationRemapper.getClass());
        List<AnnotationValue<?>> annotationValues = annotationRemapper.remap(annotationValue, context.visitorContext);
        return annotationValues.stream().flatMap(newAnnotationValue -> {
            if (newAnnotationValue == annotationValue) {
                // Value didn't change, continue with other remappers
                return remapAnnotation(newContext, processedAnnotation, annotationValue, remappers);
            }
            if (annotationValue.getAnnotationName().equals(newAnnotationValue.getAnnotationName())) {
                // Retain the same value native element
                return processAnnotation(newContext, processedAnnotation.withAnnotationValue(newAnnotationValue));
            }
            return processAnnotation(newContext, toProcessedAnnotation(newAnnotationValue));
        });
    }

    private <K extends Annotation> Stream<ProcessedAnnotation> processAnnotationTransformers(@NonNull ProcessingContext context,
                                                                                             @NonNull ProcessedAnnotation processedAnnotation) {
        AnnotationValue<K> annotationValue = (AnnotationValue<K>) processedAnnotation.getAnnotationValue();
        List<AnnotationTransformer<K>> annotationTransformers = getAnnotationTransformers(annotationValue.getAnnotationName());
        annotationTransformers = eliminateProcessed(context, annotationTransformers);
        if (CollectionUtils.isEmpty(annotationTransformers)) {
            return Stream.of(processedAnnotation);
        }
        Iterator<AnnotationTransformer<K>> transformers = annotationTransformers.iterator();
        return transformAnnotation(context, processedAnnotation, annotationValue, transformers);
    }

    @NonNull
    private <K extends Annotation> Stream<ProcessedAnnotation> transformAnnotation(@NonNull ProcessingContext context,
                                                                                   @NonNull ProcessedAnnotation processedAnnotation,
                                                                                   @NonNull AnnotationValue<K> annotationValue,
                                                                                   @NonNull Iterator<AnnotationTransformer<K>> transformers) {
        if (!transformers.hasNext()) {
            return Stream.of(processedAnnotation);
        }
        AnnotationTransformer<K> annotationTransformer = transformers.next();
        ProcessingContext newContext = context.withProcessedVisitor(annotationTransformer.getClass());
        List<AnnotationValue<?>> transform = annotationTransformer.transform(annotationValue, context.visitorContext);
        return transform.stream().flatMap(newAnnotationValue -> {
            if (newAnnotationValue == annotationValue) {
                // Value didn't change, continue with other transformers
                return transformAnnotation(newContext, processedAnnotation, annotationValue, transformers);
            }
            if (annotationValue.getAnnotationName().equals(newAnnotationValue.getAnnotationName())) {
                // Retain the same value native element
                return processAnnotation(newContext, processedAnnotation.withAnnotationValue(newAnnotationValue));
            }
            return processAnnotation(newContext, toProcessedAnnotation(newAnnotationValue));
        });
    }

    @NonNull
    private <K extends Annotation> Stream<ProcessedAnnotation> processAnnotationMappers(@NonNull ProcessingContext context,
                                                                                        @NonNull ProcessedAnnotation processedAnnotation) {
        AnnotationValue<K> annotationValue = (AnnotationValue<K>) processedAnnotation.getAnnotationValue();
        List<AnnotationMapper<K>> mappers = getAnnotationMappers(annotationValue.getAnnotationName());
        mappers = eliminateProcessed(context, mappers);
        if (CollectionUtils.isEmpty(mappers)) {
            return Stream.of(processedAnnotation);
        }
        return mappers.stream().flatMap(mapper -> {
            Stream<ProcessedAnnotation> mappedAnnotationsStream;
            ProcessingContext newContext = context.withProcessedVisitor(mapper.getClass());
            List<AnnotationValue<?>> mappedToAnnotationValues = mapper.map(annotationValue, context.visitorContext);
            if (mappedToAnnotationValues == null) {
                mappedAnnotationsStream = Stream.empty();
            } else {
                mappedAnnotationsStream = mappedToAnnotationValues
                        .stream()
                        .filter(newAnnotationValue -> newAnnotationValue != annotationValue)
                        .flatMap(newAnnotationValue -> processAnnotation(newContext, toProcessedAnnotation(newAnnotationValue)));
            }
            return Stream.concat(
                    Stream.of(processedAnnotation), // Mapper retains the original value
                    mappedAnnotationsStream
            );
        });
    }

    @NonNull
    private ProcessedAnnotation toProcessedAnnotation(@NonNull AnnotationValue<?> av) {
        return new ProcessedAnnotation(
                getAnnotationMirror(av.getAnnotationName()).orElse(null),
                av
        );
    }


    /**
     * Used to clear mutated metadata at the end of a compilation cycle.
     */
    @Internal
    public static void clearMutated() {
        MUTATED_ANNOTATION_METADATA.clear();
    }

    /**
     * Used to clear mutated metadata at the end of a compilation cycle.
     * @param key The mutated annotation metadata to remove
     */
    @Internal
    public static void clearMutated(@NonNull Object key) {
        MUTATED_ANNOTATION_METADATA.remove(key);
    }

    /**
     * Used to clear caches at the end of a compilation cycle.
     */
    @Internal
    public static void clearCaches() {
        ANNOTATION_DEFAULTS.clear();
    }

    /**
     * This is used for testing scenarios only where annotation metadata
     * is created without bean creation. It is needed because at compile time
     * there are no defaults added via DefaultAnnotationMetadata.
     */
    @Internal
    public static void copyToRuntime() {
        ANNOTATION_DEFAULTS.forEach(DefaultAnnotationMetadata::registerAnnotationDefaults);
    }

    /**
     * @return Additional mapped annotation names
     */
    @Internal
    public static Set<String> getMappedAnnotationNames() {
        return CollectionUtils.concat(ANNOTATION_MAPPERS.keySet(), ANNOTATION_TRANSFORMERS.keySet());
    }

    /**
     * @return Additional mapped annotation names
     */
    @Internal
    public static Set<String> getMappedAnnotationPackages() {
        return ANNOTATION_REMAPPERS.keySet();
    }

    /**
     * Annotate an existing annotation metadata object.
     *
     * @param annotationMetadata The annotation metadata
     * @param annotationValue    The annotation value
     * @param <A2>               The annotation type
     * @return The mutated metadata
     */
    @NonNull
    public <A2 extends Annotation> AnnotationMetadata annotate(@NonNull AnnotationMetadata annotationMetadata,
                                                               @NonNull AnnotationValue<A2> annotationValue) {
        return modify(annotationMetadata, metadata -> {
            addAnnotations(
                    metadata,
                    Stream.of(toProcessedAnnotation(annotationValue)).map(this::addDefaults),
                    true,
                    false
            );
        });
    }

    /**
     * Removes an annotation from the given annotation metadata.
     *
     * @param annotationMetadata The annotation metadata
     * @param annotationType     The annotation type
     * @return The updated metadata
     * @since 3.0.0
     */
    @NonNull
    public AnnotationMetadata removeAnnotation(@NonNull AnnotationMetadata annotationMetadata,
                                               @NonNull String annotationType) {
        return modify(annotationMetadata, metadata -> {
            T annotationMirror = getAnnotationMirror(annotationType).orElse(null);
            if (annotationMirror != null) {
                String repeatableName = getRepeatableContainerNameForType(annotationMirror);
                if (repeatableName != null) {
                    metadata.removeAnnotation(repeatableName);
                } else {
                    metadata.removeAnnotation(annotationType);
                }
            } else {
                metadata.removeAnnotation(annotationType);
            }
        });
    }

    /**
     * Removes an annotation from the given annotation metadata.
     *
     * @param annotationMetadata The annotation metadata
     * @param annotationType     The annotation type
     * @return The updated metadata
     * @since 3.0.0
     */
    @NonNull
    public AnnotationMetadata removeStereotype(@NonNull AnnotationMetadata annotationMetadata,
                                               @NonNull String annotationType) {
        return modify(annotationMetadata, metadata -> {
            T annotationMirror = getAnnotationMirror(annotationType).orElse(null);
            if (annotationMirror != null) {
                String repeatableName = getRepeatableContainerNameForType(annotationMirror);
                if (repeatableName != null) {
                    metadata.removeStereotype(repeatableName);
                } else {
                    metadata.removeStereotype(annotationType);
                }
            } else {
                metadata.removeStereotype(annotationType);
            }
        });
    }

    /**
     * Removes an annotation from the metadata for the given predicate.
     *
     * @param annotationMetadata The annotation metadata
     * @param predicate          The predicate
     * @param <T1>               The annotation type
     * @return The potentially modified metadata
     */
    @NonNull
    public <T1 extends Annotation> AnnotationMetadata removeAnnotationIf(@NonNull AnnotationMetadata annotationMetadata,
                                                                         @NonNull Predicate<AnnotationValue<T1>> predicate) {
        return modify(annotationMetadata, metadata -> metadata.removeAnnotationIf(predicate));
    }

    private AnnotationMetadata modify(AnnotationMetadata annotationMetadata, Consumer<MutableAnnotationMetadata> consumer) {
        final boolean isHierarchy = annotationMetadata instanceof AnnotationMetadataHierarchy;
        AnnotationMetadata declaredMetadata = annotationMetadata;
        if (isHierarchy) {
            declaredMetadata = annotationMetadata.getDeclaredMetadata();
        }
        MutableAnnotationMetadata mutableAnnotationMetadata;
        if (declaredMetadata == AnnotationMetadata.EMPTY_METADATA) {
            mutableAnnotationMetadata = new MutableAnnotationMetadata();
        } else if (declaredMetadata instanceof MutableAnnotationMetadata mutable) {
            mutableAnnotationMetadata = mutable;
        } else if (declaredMetadata instanceof DefaultAnnotationMetadata) {
            mutableAnnotationMetadata = MutableAnnotationMetadata.of(declaredMetadata);
        } else {
            throw new IllegalStateException("Unrecognized annotation metadata: " + annotationMetadata);
        }
        consumer.accept(mutableAnnotationMetadata);
        if (isHierarchy) {
            return ((AnnotationMetadataHierarchy) annotationMetadata).createSibling(mutableAnnotationMetadata);
        }
        return mutableAnnotationMetadata;
    }

    /**
     * The context of the annotation processing.
     *
     * @param visitorContext    The visitor context
     * @param parentAnnotations The parent annotations
     * @param processedVisitors The processed visitors
     * @param repeatableToContainer The repeatable to container
     * @since 4.0.0
     */
    private record ProcessingContext(@NonNull VisitorContext visitorContext,
                                     @NonNull Set<String> parentAnnotations,
                                     @NonNull Set<Class<?>> processedVisitors,
                                     @NonNull Map<String, String> repeatableToContainer) {

        ProcessingContext(@NonNull VisitorContext visitorContext) {
            this(visitorContext, Collections.emptySet(), Collections.emptySet(), new HashMap<>());
        }

        boolean isProcessed(@NonNull AnnotationValue<?> annotationValue) {
            return parentAnnotations.contains(annotationValue.getAnnotationName());
        }

        @NonNull
        ProcessingContext withParent(@NonNull AnnotationValue<?> parent) {
            Set<String> parents = CollectionUtils.concat(parentAnnotations, parent.getAnnotationName());
            return new ProcessingContext(visitorContext, Collections.unmodifiableSet(parents), processedVisitors, repeatableToContainer);
        }

        @NonNull
        ProcessingContext withParents(@NonNull List<String> newParents) {
            Set<String> parents = CollectionUtils.concat(parentAnnotations, newParents);
            return new ProcessingContext(visitorContext, Collections.unmodifiableSet(parents), processedVisitors, repeatableToContainer);
        }

        @NonNull
        public ProcessingContext withProcessedVisitor(@NonNull Class<?> processedVisitor) {
            Set<Class<?>> visitors = CollectionUtils.concat(processedVisitors, processedVisitor);
            return new ProcessingContext(visitorContext, parentAnnotations, Collections.unmodifiableSet(visitors), repeatableToContainer);
        }
    }

    /**
     * Simple tuple object combining the annotation value plus the native annotation type.
     * NOTE: Some implementation like Groovy don't return correct annotation native type with type hierarchies.
     * We need to carry the provided type.
     *
     * @since 4.0.0
     */
    private final class ProcessedAnnotation {
        @Nullable
        private final T annotationType;
        private final AnnotationValue<?> annotationValue;

        private ProcessedAnnotation(@Nullable T annotationType,
                                    AnnotationValue<?> annotationValue) {
            this.annotationType = annotationType;
            this.annotationValue = annotationValue;
        }

        public ProcessedAnnotation withAnnotationValue(AnnotationValue<?> annotationValue) {
            return new ProcessedAnnotation(annotationType, annotationValue);
        }

        public ProcessedAnnotation withAnnotationType(T annotationType) {
            return new ProcessedAnnotation(annotationType, annotationValue);
        }

        public ProcessedAnnotation mutateAnnotationValue(Function<AnnotationValueBuilder<?>, AnnotationValueBuilder<?>> fn) {
            return new ProcessedAnnotation(annotationType, fn.apply(annotationValue.mutate()).build());
        }

        @Nullable
        public T getAnnotationType() {
            return annotationType;
        }

        public AnnotationValue<?> getAnnotationValue() {
            return annotationValue;
        }

    }

    /**
     * The caching entry.
     *
     * @author Denis Stepanov
     * @since 4.0.0
     */
    public interface CachedAnnotationMetadata extends AnnotationMetadataDelegate {

        /**
         * @return annotation metadata in the cache or empty
         */
        @NonNull
        @Override
        AnnotationMetadata getAnnotationMetadata();

        /**
         * @return Is mutated?
         */
        boolean isMutated();

        /**
         * Modify the annotation metadata in the cache.
         *
         * @param annotationMetadata new value
         */
        void update(@NonNull AnnotationMetadata annotationMetadata);

    }

    /**
     * Key used to reference mutated metadata.
     *
     * @param e1  The element 1
     * @param e2  The element 2
     * @param <T> the element type
     */
    @Internal
    private record Key2<T>(T e1, T e2) {
    }

    /**
     * Key used to reference mutated metadata.
     *
     * @param e1  The element 1
     * @param e2  The element 2
     * @param e3  The element 3
     * @param <T> the element type
     */
    @Internal
    private record Key3<T>(T e1, T e2, T e3) {
    }

    private static final class DefaultCachedAnnotationMetadata implements CachedAnnotationMetadata {
        @Nullable
        private AnnotationMetadata annotationMetadata;
        private boolean isMutated;

        public DefaultCachedAnnotationMetadata(AnnotationMetadata annotationMetadata) {
            if (annotationMetadata instanceof AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata) {
                throw new IllegalStateException();
            }
            this.annotationMetadata = annotationMetadata;
        }

        @Override
        public boolean isMutated() {
            return isMutated;
        }

        @Override
        public AnnotationMetadata getAnnotationMetadata() {
            if (annotationMetadata == null || annotationMetadata.isEmpty()) {
                return AnnotationMetadata.EMPTY_METADATA;
            }
            return annotationMetadata;
        }

        @Override
        public void update(AnnotationMetadata annotationMetadata) {
            if (annotationMetadata instanceof AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata) {
                throw new IllegalStateException();
            }
            this.annotationMetadata = annotationMetadata;
            isMutated = true;
        }
    }

}