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

io.micronaut.inject.annotation.MutableAnnotationMetadata Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * 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.env.DefaultPropertyPlaceholderResolver;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.expressions.EvaluatedExpressionReference;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * A mutable various of {@link DefaultAnnotationMetadata} that is used only at build time.
 *
 * @author graemerocher
 * @since 2.4.0
 */
public class MutableAnnotationMetadata extends DefaultAnnotationMetadata {

    private boolean hasPropertyExpressions = false;
    private boolean hasEvaluatedExpressions = false;

    @Nullable
    Map> annotationDefaultValues;
    @Nullable
    private Set sourceRetentionAnnotations;
    @Nullable
    private Map> sourceAnnotationDefaultValues;
    @Nullable
    Map annotationRepeatableContainer;

    /**
     * Default constructor.
     */
    public MutableAnnotationMetadata() {
    }

    private MutableAnnotationMetadata(@Nullable Map> declaredAnnotations,
                                      @Nullable Map> declaredStereotypes,
                                      @Nullable Map> allStereotypes,
                                      @Nullable Map> allAnnotations,
                                      @Nullable Map> annotationsByStereotype,
                                      boolean hasPropertyExpressions) {
        this(declaredAnnotations, declaredStereotypes, allStereotypes, allAnnotations, annotationsByStereotype, hasPropertyExpressions, false);
    }

    private MutableAnnotationMetadata(@Nullable Map> declaredAnnotations,
                                     @Nullable Map> declaredStereotypes,
                                     @Nullable Map> allStereotypes,
                                     @Nullable Map> allAnnotations,
                                     @Nullable Map> annotationsByStereotype,
                                     boolean hasPropertyExpressions,
                                     boolean hasEvaluatedExpressions) {
        super(declaredAnnotations,
              declaredStereotypes,
              allStereotypes,
              allAnnotations,
              annotationsByStereotype,
              hasPropertyExpressions);
        this.hasPropertyExpressions = hasPropertyExpressions;
        this.hasEvaluatedExpressions = hasEvaluatedExpressions;
    }

    public static MutableAnnotationMetadata of(AnnotationMetadata annotationMetadata) {
        if (annotationMetadata.isEmpty()) {
            return new MutableAnnotationMetadata();
        }
        annotationMetadata = annotationMetadata.getTargetAnnotationMetadata();
        if (annotationMetadata instanceof AnnotationMetadataHierarchy hierarchy) {
            return  hierarchy.merge();
        } else if (annotationMetadata instanceof MutableAnnotationMetadata metadata) {
            return  metadata.clone();
        } else if (annotationMetadata instanceof DefaultAnnotationMetadata defaultAnnotationMetadata) {
            MutableAnnotationMetadata metadata = new MutableAnnotationMetadata();
            metadata.addAnnotationMetadata(defaultAnnotationMetadata);
            return metadata;
        } else {
            throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata);
        }
    }

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

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

    @Override
    public MutableAnnotationMetadata clone() {
        final MutableAnnotationMetadata cloned = new MutableAnnotationMetadata(
                declaredAnnotations != null ? cloneMapOfMapValue(declaredAnnotations) : null,
                declaredStereotypes != null ? cloneMapOfMapValue(declaredStereotypes) : null,
                allStereotypes != null ? cloneMapOfMapValue(allStereotypes) : null,
                allAnnotations != null ? cloneMapOfMapValue(allAnnotations) : null,
                annotationsByStereotype != null ? cloneMapOfListValue(annotationsByStereotype) : null,
                hasPropertyExpressions,
                hasEvaluatedExpressions
        );
        if (annotationDefaultValues != null) {
            cloned.annotationDefaultValues = new LinkedHashMap<>(annotationDefaultValues);
        }
        if (annotationRepeatableContainer != null) {
            cloned.annotationRepeatableContainer = new HashMap<>(annotationRepeatableContainer);
        }
        if (sourceRetentionAnnotations != null) {
            cloned.sourceRetentionAnnotations = new HashSet<>(sourceRetentionAnnotations);
        }
        if (annotationDefaultValues != null) {
            cloned.annotationDefaultValues = cloneMapOfMapValue(annotationDefaultValues);
        }
        if (sourceAnnotationDefaultValues != null) {
            cloned.sourceAnnotationDefaultValues = cloneMapOfMapValue(sourceAnnotationDefaultValues);
        }
        cloned.hasPropertyExpressions = hasPropertyExpressions;
        cloned.hasEvaluatedExpressions = hasEvaluatedExpressions;
        return cloned;
    }

    @NonNull
    @Override
    public Map getDefaultValues(@NonNull String annotation) {
        Map values = super.getDefaultValues(annotation);
        if (!values.isEmpty() || annotationDefaultValues == null) {
            return values;
        }
        final Map compileTimeDefaults = annotationDefaultValues.get(annotation);
        if (compileTimeDefaults != null && !compileTimeDefaults.isEmpty()) {
            return compileTimeDefaults.entrySet().stream()
                    .collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue));
        }
        return values;
    }

    private boolean computeHasPropertyExpressions(Map values, RetentionPolicy retentionPolicy) {
        return hasPropertyExpressions || values != null && retentionPolicy == RetentionPolicy.RUNTIME && hasPropertyExpressions(values);
    }

    private boolean computeHasEvaluatedExpressions(Map values, RetentionPolicy retentionPolicy) {
        return hasEvaluatedExpressions || values != null && retentionPolicy == RetentionPolicy.RUNTIME  &&  hasEvaluatedExpressions(values);
    }

    private boolean hasPropertyExpressions(Map values) {
        if (CollectionUtils.isEmpty(values)) {
            return false;
        }
        return values.values().stream().anyMatch(v -> {
            if (v instanceof CharSequence) {
                return v.toString().contains(DefaultPropertyPlaceholderResolver.PREFIX);
            } else if (v instanceof String[] strings) {
                return Arrays.stream(strings).anyMatch(s -> s.contains(DefaultPropertyPlaceholderResolver.PREFIX));
            } else if (v instanceof AnnotationValue annotationValue) {
                return hasPropertyExpressions(annotationValue.getValues());
            } else if (v instanceof AnnotationValue[] annotationValues) {
                if (annotationValues.length > 0) {
                    return Arrays.stream(annotationValues).anyMatch(av -> hasPropertyExpressions(av.getValues()));
                } else {
                    return false;
                }
            } else {
                return false;
            }
        });
    }

    /**
     * @return The annotations that are source retention.
     */
    @Internal
    Set getSourceRetentionAnnotations() {
        if (sourceRetentionAnnotations != null) {
            return Collections.unmodifiableSet(sourceRetentionAnnotations);
        }
        return Collections.emptySet();
    }

    /**
     * Adds an annotation and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param annotation The annotation
     * @param values     The values
     */
    @SuppressWarnings("WeakerAccess")
    public void addAnnotation(String annotation, Map values) {
        addAnnotation(annotation, values, RetentionPolicy.RUNTIME);
    }


    /**
     * Adds an annotation and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param annotation      The annotation
     * @param values          The values
     * @param retentionPolicy The retention policy
     */
    @SuppressWarnings("WeakerAccess")
    public void addAnnotation(String annotation, Map values, RetentionPolicy retentionPolicy) {
        if (annotation == null) {
            return;
        }
        if (isRepeatableAnnotationContainer(annotation)) {
            Object v = values.get(AnnotationMetadata.VALUE_MEMBER);
            if (v instanceof io.micronaut.core.annotation.AnnotationValue[] annotationValues) {
                for (io.micronaut.core.annotation.AnnotationValue annotationValue : annotationValues) {
                    addRepeatable(annotation, annotationValue);
                }
            } else if (v instanceof Iterable iterable) {
                for (Object o : iterable) {
                    if (o instanceof io.micronaut.core.annotation.AnnotationValue annotationValue) {
                        addRepeatable(annotation, annotationValue);
                    }
                }
            }
        } else {
            Map> allAnnotations = getAllAnnotations();
            addAnnotation(annotation, values, null, allAnnotations, false, retentionPolicy);
        }
    }

    /**
     * Adds an annotation directly declared on the element and its member values, if the annotation already exists the
     * data will be merged with existing values replaced.
     *
     * @param annotation The annotation
     * @param values     The values
     */
    public final void addDefaultAnnotationValues(String annotation, Map values) {
        addDefaultAnnotationValues(annotation, values, RetentionPolicy.RUNTIME);
    }

    /**
     * Adds an annotation directly declared on the element and its member values, if the annotation already exists the
     * data will be merged with existing values replaced.
     *
     * @param annotation      The annotation
     * @param values          The values
     * @param retentionPolicy The retention policy
     */
    public final void addDefaultAnnotationValues(String annotation, Map values, RetentionPolicy retentionPolicy) {
        if (annotation == null) {
            return;
        }
        Map> annotationDefaults;
        if (retentionPolicy == RetentionPolicy.RUNTIME) {
            annotationDefaults = this.annotationDefaultValues;
            if (annotationDefaults == null) {
                this.annotationDefaultValues = new LinkedHashMap<>();
                annotationDefaults = this.annotationDefaultValues;
            }
        } else {
            annotationDefaults = this.sourceAnnotationDefaultValues;
            if (annotationDefaults == null) {
                this.sourceAnnotationDefaultValues = new LinkedHashMap<>();
                annotationDefaults = this.sourceAnnotationDefaultValues;
            }
        }
        putValues(annotation, values, annotationDefaults);
    }

    /**
     * Adds a repeatable annotation value. If a value already exists will be added
     *
     * @param annotationName  The annotation name
     * @param annotationValue The annotation value
     */
    public void addRepeatable(String annotationName, AnnotationValue annotationValue) {
        addRepeatable(annotationName, annotationValue, annotationValue.getRetentionPolicy());
    }

    /**
     * Adds a repeatable annotation value. If a value already exists will be added
     *
     * @param annotationName  The annotation name
     * @param annotationValue The annotation value
     * @param retentionPolicy The retention policy
     */
    public void addRepeatable(String annotationName, AnnotationValue annotationValue, RetentionPolicy retentionPolicy) {
        if (StringUtils.isNotEmpty(annotationName) && annotationValue != null) {
            Map> allAnnotations = getAllAnnotations();

            addRepeatableInternal(annotationName, annotationValue, allAnnotations, retentionPolicy);
        }
    }

    /**
     * Adds a repeatable stereotype value. If a value already exists will be added
     *
     * @param parents         The parent annotations
     * @param stereotype      The annotation name
     * @param annotationValue The annotation value
     */
    public void addRepeatableStereotype(List parents, String stereotype, AnnotationValue annotationValue) {
        Map> allStereotypes = getAllStereotypes();
        List annotationList = getAnnotationsByStereotypeInternal(stereotype);
        for (String parentAnnotation : parents) {
            if (!annotationList.contains(parentAnnotation)) {
                annotationList.add(parentAnnotation);
            }
        }
        addRepeatableInternal(stereotype, annotationValue, allStereotypes, RetentionPolicy.RUNTIME);
    }

    /**
     * Adds a repeatable declared stereotype value. If a value already exists will be added
     *
     * @param parents         The parent annotations
     * @param stereotype      The annotation name
     * @param annotationValue The annotation value
     */
    public void addDeclaredRepeatableStereotype(List parents, String stereotype, AnnotationValue annotationValue) {
        Map> declaredStereotypes = getDeclaredStereotypesInternal();
        List annotationList = getAnnotationsByStereotypeInternal(stereotype);
        for (String parentAnnotation : parents) {
            if (!annotationList.contains(parentAnnotation)) {
                annotationList.add(parentAnnotation);
            }
        }
        addRepeatableInternal(stereotype, annotationValue, declaredStereotypes, RetentionPolicy.RUNTIME);
        addRepeatableInternal(stereotype, annotationValue, getAllStereotypes(), RetentionPolicy.RUNTIME);
    }

    /**
     * Adds a repeatable annotation value. If a value already exists will be added
     *
     * @param annotationName  The annotation name
     * @param annotationValue The annotation value
     */
    public void addDeclaredRepeatable(String annotationName, AnnotationValue annotationValue) {
        addDeclaredRepeatable(annotationName, annotationValue, annotationValue.getRetentionPolicy());
    }

    /**
     * Adds a repeatable annotation value. If a value already exists will be added
     *
     * @param annotationName  The annotation name
     * @param annotationValue The annotation value
     * @param retentionPolicy The retention policy
     */
    public void addDeclaredRepeatable(String annotationName, AnnotationValue annotationValue, RetentionPolicy retentionPolicy) {
        if (StringUtils.isNotEmpty(annotationName) && annotationValue != null) {
            Map> allAnnotations = getDeclaredAnnotationsInternal();

            addRepeatableInternal(annotationName, annotationValue, allAnnotations, retentionPolicy);

            addRepeatable(annotationName, annotationValue);
        }
    }

    /**
     * Adds a stereotype and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param parentAnnotations The parent annotations
     * @param stereotype        The annotation
     * @param values            The values
     */
    @SuppressWarnings("WeakerAccess")
    public final void addStereotype(List parentAnnotations, String stereotype, Map values) {
        addStereotype(parentAnnotations, stereotype, values, RetentionPolicy.RUNTIME);
    }


    /**
     * Adds a stereotype and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param parentAnnotations The parent annotations
     * @param stereotype        The annotation
     * @param values            The values
     * @param retentionPolicy   The retention policy
     */
    @SuppressWarnings("WeakerAccess")
    public final void addStereotype(List parentAnnotations, String stereotype, Map values, RetentionPolicy retentionPolicy) {
        if (stereotype == null) {
            return;
        }
        if (isRepeatableAnnotationContainer(stereotype)) {
            Object v = values.get(AnnotationMetadata.VALUE_MEMBER);
            if (v instanceof io.micronaut.core.annotation.AnnotationValue[] annotationValues) {
                for (io.micronaut.core.annotation.AnnotationValue annotationValue : annotationValues) {
                    addRepeatableStereotype(parentAnnotations, stereotype, annotationValue);
                }
            } else if (v instanceof Iterable iterable) {
                for (Object o : iterable) {
                    if (o instanceof io.micronaut.core.annotation.AnnotationValue annotationValue) {
                        addRepeatableStereotype(parentAnnotations, stereotype, annotationValue);
                    }
                }
            }
        } else {
            Map> allStereotypes = getAllStereotypes();
            List annotationList = getAnnotationsByStereotypeInternal(stereotype);
            if (!parentAnnotations.isEmpty()) {
                final String parentAnnotation = CollectionUtils.last(parentAnnotations);
                if (!annotationList.contains(parentAnnotation)) {
                    annotationList.add(parentAnnotation);
                }
            }

            // add to stereotypes
            addAnnotation(
                    stereotype,
                    values,
                    null,
                    allStereotypes,
                    false,
                    retentionPolicy
            );
        }
    }

    /**
     * Adds a stereotype and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param parentAnnotations The parent annotations
     * @param stereotype        The annotation
     * @param values            The values
     */
    @SuppressWarnings("WeakerAccess")
    public void addDeclaredStereotype(List parentAnnotations, String stereotype, Map values) {
        addDeclaredStereotype(parentAnnotations, stereotype, values, RetentionPolicy.RUNTIME);
    }

    /**
     * Adds a stereotype and its member values, if the annotation already exists the data will be merged with existing
     * values replaced.
     *
     * @param parentAnnotations The parent annotations
     * @param stereotype        The annotation
     * @param values            The values
     * @param retentionPolicy   The retention policy
     */
    @SuppressWarnings("WeakerAccess")
    public void addDeclaredStereotype(List parentAnnotations, String stereotype, Map values, RetentionPolicy retentionPolicy) {
        if (stereotype == null) {
            return;
        }
        if (isRepeatableAnnotationContainer(stereotype)) {
            Object v = values.get(AnnotationMetadata.VALUE_MEMBER);
            if (v instanceof io.micronaut.core.annotation.AnnotationValue[] annotationValues) {
                for (io.micronaut.core.annotation.AnnotationValue annotationValue : annotationValues) {
                    addDeclaredRepeatableStereotype(parentAnnotations, stereotype, annotationValue);
                }
            } else if (v instanceof Iterable iterable) {
                for (Object o : iterable) {
                    if (o instanceof io.micronaut.core.annotation.AnnotationValue annotationValue) {
                        addDeclaredRepeatableStereotype(parentAnnotations, stereotype, annotationValue);
                    }
                }
            }
        } else {
            Map> declaredStereotypes = getDeclaredStereotypesInternal();
            Map> allStereotypes = getAllStereotypes();
            List annotationList = getAnnotationsByStereotypeInternal(stereotype);
            if (!parentAnnotations.isEmpty()) {
                final String parentAnnotation = CollectionUtils.last(parentAnnotations);
                if (!annotationList.contains(parentAnnotation)) {
                    annotationList.add(parentAnnotation);
                }
            }

            addAnnotation(
                    stereotype,
                    values,
                    declaredStereotypes,
                    allStereotypes,
                    true,
                    retentionPolicy
            );
        }
    }

    /**
     * Adds an annotation directly declared on the element and its member values, if the annotation already exists the
     * data will be merged with existing values replaced.
     *
     * @param annotation The annotation
     * @param values     The values
     */
    public void addDeclaredAnnotation(String annotation, Map values) {
        addDeclaredAnnotation(annotation, values, RetentionPolicy.RUNTIME);
    }

    /**
     * Adds an annotation directly declared on the element and its member values, if the annotation already exists the
     * data will be merged with existing values replaced.
     *
     * @param annotation      The annotation
     * @param values          The values
     * @param retentionPolicy The retention policy
     */
    public void addDeclaredAnnotation(String annotation, Map values, RetentionPolicy retentionPolicy) {
        if (annotation == null) {
            return;
        }
        boolean hasOtherMembers = false;
        boolean repeatableAnnotationContainer = isRepeatableAnnotationContainer(annotation);
        if (isRepeatableAnnotationContainer(annotation)) {
            for (Map.Entry entry : values.entrySet()) {
                if (entry.getKey().equals(AnnotationMetadata.VALUE_MEMBER)) {
                    Object v = entry.getValue();
                    if (v instanceof io.micronaut.core.annotation.AnnotationValue[] annotationValues) {
                        for (io.micronaut.core.annotation.AnnotationValue annotationValue : annotationValues) {
                            addDeclaredRepeatable(annotation, annotationValue);
                        }
                    } else if (v instanceof Iterable iterable) {
                        for (Object o : iterable) {
                            if (o instanceof io.micronaut.core.annotation.AnnotationValue annotationValue) {
                                addDeclaredRepeatable(annotation, annotationValue);
                            }
                        }
                    }
                } else {
                    hasOtherMembers = true;
                }
            }
        }
        if (!repeatableAnnotationContainer || hasOtherMembers) {
            Map> declaredAnnotations = getDeclaredAnnotationsInternal();
            Map> allAnnotations = getAllAnnotations();
            addAnnotation(annotation, values, declaredAnnotations, allAnnotations, true, retentionPolicy);
        }
    }

    @SuppressWarnings("java:S2259") // false positive
    private void addAnnotation(String annotation,
                               Map values,
                               Map> declaredAnnotations,
                               Map> allAnnotations,
                               boolean isDeclared,
                               RetentionPolicy retentionPolicy) {
        hasPropertyExpressions = computeHasPropertyExpressions(values, retentionPolicy);
        hasEvaluatedExpressions = computeHasEvaluatedExpressions(values, retentionPolicy);

        if (isDeclared && declaredAnnotations != null) {
            putValues(annotation, values, declaredAnnotations);
        }
        putValues(annotation, values, allAnnotations);

        // Annotations with retention CLASS need not be retained at run time
        if (retentionPolicy == RetentionPolicy.SOURCE || retentionPolicy == RetentionPolicy.CLASS) {
            addSourceRetentionAnnotation(annotation);
        }
    }

    private void addSourceRetentionAnnotation(String annotation) {
        if (sourceRetentionAnnotations == null) {
            sourceRetentionAnnotations = new HashSet<>(5);
        }
        sourceRetentionAnnotations.add(annotation);
    }

    @SuppressWarnings("java:S2259")
    private void putValues(String annotation, Map values, Map> currentAnnotationValues) {
        Map existing = currentAnnotationValues.get(annotation);
        boolean hasValues = CollectionUtils.isNotEmpty(values);
        if (existing != null && hasValues) {
            if (existing.isEmpty()) {
                existing = new LinkedHashMap<>();
                currentAnnotationValues.put(annotation, existing);
            }
            for (CharSequence key : values.keySet()) {
                if (!existing.containsKey(key)) {
                    existing.put(key, values.get(key));
                }
            }
        } else {
            if (!hasValues) {
                existing = existing == null ? CollectionUtils.newLinkedHashMap(3) : existing;
            } else {
                existing = CollectionUtils.newLinkedHashMap(values.size());
                existing.putAll(values);
            }
            currentAnnotationValues.put(annotation, existing);
        }
    }

    @SuppressWarnings("MagicNumber")
    private Map> getAllStereotypes() {
        Map> stereotypes = this.allStereotypes;
        if (stereotypes == null) {
            stereotypes = new HashMap<>(3);
            this.allStereotypes = stereotypes;
        }
        return stereotypes;
    }

    @SuppressWarnings("MagicNumber")
    private Map> getDeclaredStereotypesInternal() {
        Map> stereotypes = this.declaredStereotypes;
        if (stereotypes == null) {
            stereotypes = new HashMap<>(3);
            this.declaredStereotypes = stereotypes;
        }
        return stereotypes;
    }

    @SuppressWarnings("MagicNumber")
    private Map> getAllAnnotations() {
        Map> annotations = this.allAnnotations;
        if (annotations == null) {
            annotations = new HashMap<>(3);
            this.allAnnotations = annotations;
        }
        return annotations;
    }

    @SuppressWarnings("MagicNumber")
    private Map> getDeclaredAnnotationsInternal() {
        Map> annotations = this.declaredAnnotations;
        if (annotations == null) {
            annotations = new HashMap<>(3);
            this.declaredAnnotations = annotations;
        }
        return annotations;
    }

    private List getAnnotationsByStereotypeInternal(String stereotype) {
        return getAnnotationsByStereotypeInternal().computeIfAbsent(stereotype, s -> new ArrayList<>());
    }

    @SuppressWarnings("MagicNumber")
    private Map> getAnnotationsByStereotypeInternal() {
        Map> annotations = this.annotationsByStereotype;
        if (annotations == null) {
            annotations = new HashMap<>(3);
            this.annotationsByStereotype = annotations;
        }
        return annotations;
    }

    private void addRepeatableInternal(String repeatableAnnotationContainer,
                                       AnnotationValue annotationValue,
                                       Map> allAnnotations,
                                       RetentionPolicy retentionPolicy) {
        hasPropertyExpressions = computeHasPropertyExpressions(annotationValue.getValues(), retentionPolicy);
        hasEvaluatedExpressions = computeHasEvaluatedExpressions(annotationValue.getValues(), retentionPolicy);

        if (annotationRepeatableContainer == null) {
            annotationRepeatableContainer = new HashMap<>(2);
        }
        annotationRepeatableContainer.put(annotationValue.getAnnotationName(), repeatableAnnotationContainer);
        if (retentionPolicy == RetentionPolicy.SOURCE) {
            addSourceRetentionAnnotation(repeatableAnnotationContainer);
        }

        Map values = allAnnotations.computeIfAbsent(repeatableAnnotationContainer, s -> new HashMap<>());
        Object v = values.get(AnnotationMetadata.VALUE_MEMBER);
        if (v != null) {
            if (v.getClass().isArray()) {
                Object[] array = (Object[]) v;
                Set newValues = CollectionUtils.newLinkedHashSet(array.length + 1);
                newValues.addAll(Arrays.asList(array));
                newValues.add(annotationValue);
                values.put(AnnotationMetadata.VALUE_MEMBER, newValues);
            } else if (v instanceof Collection collection) {
                collection.add(annotationValue);
            }
        } else {
            Set newValues = new LinkedHashSet<>(2);
            newValues.add(annotationValue);
            values.put(AnnotationMetadata.VALUE_MEMBER, newValues);
        }
    }

    /**
     * 

Sets a member of the given {@link AnnotationMetadata} return a new annotation metadata instance without * mutating the existing.

* *

WARNING: for internal use only be the framework

* * @param annotationMetadata The metadata * @param annotationName The annotation name * @param member The member * @param value The value * @return The metadata */ @Internal public static MutableAnnotationMetadata mutateMember(MutableAnnotationMetadata annotationMetadata, String annotationName, String member, Object value) { return mutateMember(annotationMetadata, annotationName, Collections.singletonMap(member, value)); } @Override protected AnnotationValue newAnnotationValue(String annotationType, Map values) { Map defaultValues = null; if (annotationDefaultValues != null) { defaultValues = annotationDefaultValues.get(annotationType); } if (defaultValues == null && sourceAnnotationDefaultValues != null) { defaultValues = sourceAnnotationDefaultValues.get(annotationType); } if (defaultValues == null) { defaultValues = AnnotationMetadataSupport.getDefaultValuesOrNull(annotationType); } return new AnnotationValue<>(annotationType, values, defaultValues); } /** * Include the annotation metadata from the other instance of {@link DefaultAnnotationMetadata}. * * @param annotationMetadata The annotation metadata * @since 4.0.0 */ @Internal public void addAnnotationMetadata(DefaultAnnotationMetadata annotationMetadata) { hasPropertyExpressions |= annotationMetadata.hasPropertyExpressions(); hasEvaluatedExpressions |= annotationMetadata.hasEvaluatedExpressions(); if (annotationMetadata.declaredAnnotations != null && !annotationMetadata.declaredAnnotations.isEmpty()) { if (declaredAnnotations == null) { declaredAnnotations = new LinkedHashMap<>(); } for (Map.Entry> entry : annotationMetadata.declaredAnnotations.entrySet()) { putValues(entry.getKey(), entry.getValue(), declaredAnnotations); } } if (annotationMetadata.declaredStereotypes != null && !annotationMetadata.declaredStereotypes.isEmpty()) { if (declaredStereotypes == null) { declaredStereotypes = new LinkedHashMap<>(); } for (Map.Entry> entry : annotationMetadata.declaredStereotypes.entrySet()) { putValues(entry.getKey(), entry.getValue(), declaredStereotypes); } } if (annotationMetadata.allStereotypes != null && !annotationMetadata.allStereotypes.isEmpty()) { if (allStereotypes == null) { allStereotypes = new LinkedHashMap<>(); } for (Map.Entry> entry : annotationMetadata.allStereotypes.entrySet()) { putValues(entry.getKey(), entry.getValue(), allStereotypes); } } if (annotationMetadata.allAnnotations != null && !annotationMetadata.allAnnotations.isEmpty()) { if (allAnnotations == null) { allAnnotations = new LinkedHashMap<>(); } for (Map.Entry> entry : annotationMetadata.allAnnotations.entrySet()) { putValues(entry.getKey(), entry.getValue(), allAnnotations); } } Map> source = annotationMetadata.annotationsByStereotype; if (source != null && !source.isEmpty()) { if (annotationsByStereotype == null) { annotationsByStereotype = new LinkedHashMap<>(); } for (Map.Entry> entry : source.entrySet()) { String ann = entry.getKey(); List prevValues = annotationsByStereotype.get(ann); if (prevValues == null) { annotationsByStereotype.put(ann, new ArrayList<>(entry.getValue())); } else { Set prevValuesSet = new LinkedHashSet<>(prevValues); prevValuesSet.addAll(entry.getValue()); annotationsByStereotype.put(ann, new ArrayList<>(prevValuesSet)); } } } } /** * Include the annotation metadata from the other instance of {@link DefaultAnnotationMetadata}. * * @param annotationMetadata The annotation metadata * @since 4.0.0 */ @Internal public void addAnnotationMetadata(MutableAnnotationMetadata annotationMetadata) { addAnnotationMetadata((DefaultAnnotationMetadata) annotationMetadata); hasPropertyExpressions |= annotationMetadata.hasPropertyExpressions; hasEvaluatedExpressions |= annotationMetadata.hasEvaluatedExpressions; if (annotationMetadata.sourceRetentionAnnotations != null) { if (sourceRetentionAnnotations == null) { sourceRetentionAnnotations = new HashSet<>(annotationMetadata.sourceRetentionAnnotations); } else { sourceRetentionAnnotations.addAll(annotationMetadata.sourceRetentionAnnotations); } } if (annotationMetadata.annotationDefaultValues != null) { if (annotationDefaultValues == null) { annotationDefaultValues = new LinkedHashMap<>(annotationMetadata.annotationDefaultValues); } else { // No need to merge values annotationDefaultValues.putAll(annotationMetadata.annotationDefaultValues); } } if (annotationMetadata.sourceAnnotationDefaultValues != null) { if (sourceAnnotationDefaultValues == null) { sourceAnnotationDefaultValues = new LinkedHashMap<>(annotationMetadata.sourceAnnotationDefaultValues); } else { // No need to merge values sourceAnnotationDefaultValues.putAll(annotationMetadata.sourceAnnotationDefaultValues); } } if (annotationMetadata.annotationRepeatableContainer != null) { if (annotationRepeatableContainer == null) { annotationRepeatableContainer = new LinkedHashMap<>(annotationMetadata.annotationRepeatableContainer); } else { annotationRepeatableContainer.putAll(annotationMetadata.annotationRepeatableContainer); } } } /** * Contributes defaults to the given target. * *

WARNING: for internal use only be the framework

* * @param target The target * @param source The source */ @Internal public static void contributeDefaults(AnnotationMetadata target, AnnotationMetadata source) { source = source.getTargetAnnotationMetadata(); if (source instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { contributeDefaults(target, annotationMetadataHierarchy); return; } if (target instanceof MutableAnnotationMetadata damTarget && source instanceof MutableAnnotationMetadata damSource) { final Map> existingDefaults = damTarget.annotationDefaultValues; final Map> additionalDefaults = damSource.annotationDefaultValues; if (existingDefaults != null) { if (additionalDefaults != null) { existingDefaults.putAll( additionalDefaults ); } } else { if (additionalDefaults != null) { additionalDefaults.forEach(damTarget::addDefaultAnnotationValues); } } } // We don't need to contribute the default source annotation contributeRepeatable(target, source); } /** * Contributes defaults to the given target. * *

WARNING: for internal use only be the framework

* * @param target The target * @param source The source * @since 4.0.0 */ @Internal public static void contributeDefaults(AnnotationMetadata target, AnnotationMetadataHierarchy source) { for (AnnotationMetadata annotationMetadata : source) { if (annotationMetadata instanceof AnnotationMetadataReference) { continue; } contributeDefaults(target, annotationMetadata); } } /** * Contributes repeatable annotation metadata to the given target. * *

WARNING: for internal use only be the framework

* * @param target The target * @param source The source */ @Internal public static void contributeRepeatable(AnnotationMetadata target, AnnotationMetadata source) { source = source.getTargetAnnotationMetadata(); if (source instanceof AnnotationMetadataHierarchy hierarchy) { source = hierarchy.merge(); } if (target instanceof MutableAnnotationMetadata damTarget && source instanceof MutableAnnotationMetadata damSource) { if (damSource.annotationRepeatableContainer != null && !damSource.annotationRepeatableContainer.isEmpty()) { if (damTarget.annotationRepeatableContainer == null) { damTarget.annotationRepeatableContainer = new HashMap<>(damSource.annotationRepeatableContainer); } else { damTarget.annotationRepeatableContainer.putAll(damSource.annotationRepeatableContainer); } } } } /** *

Sets a member of the given {@link AnnotationMetadata} return a new annotation metadata instance without * mutating the existing.

* *

WARNING: for internal use only be the framework

* * @param annotationMetadata The metadata * @param annotationName The annotation name * @param members The key/value set of members and values * @return The metadata */ @Internal public static MutableAnnotationMetadata mutateMember(MutableAnnotationMetadata annotationMetadata, String annotationName, Map members) { if (StringUtils.isEmpty(annotationName)) { throw new IllegalArgumentException("Argument [annotationName] cannot be blank"); } if (!members.isEmpty()) { for (Map.Entry entry : members.entrySet()) { if (StringUtils.isEmpty(entry.getKey())) { throw new IllegalArgumentException("Argument [members] cannot have a blank key"); } if (entry.getValue() == null) { throw new IllegalArgumentException("Argument [members] cannot have a null value. Key [" + entry.getKey() + "]"); } } } annotationMetadata = annotationMetadata.clone(); annotationMetadata.addDeclaredAnnotation(annotationName, members); return annotationMetadata; } /** * Removes an annotation for the given predicate. * * @param predicate The predicate * @param The annotation */ public void removeAnnotationIf(@NonNull Predicate> predicate) { removeAnnotationsIf(predicate, this.declaredAnnotations); removeAnnotationsIf(predicate, this.allAnnotations); } private void removeAnnotationsIf(@NonNull Predicate> predicate, Map> annotations) { if (annotations == null) { return; } annotations.entrySet().removeIf(entry -> { final String annotationName = entry.getKey(); if (predicate.test(newAnnotationValue(annotationName, entry.getValue()))) { removeFromStereotypes(annotationName); return true; } return false; }); } /** * Removes an annotation for the given annotation type. * * @param annotationType The annotation type * @since 3.0.0 */ public void removeAnnotation(String annotationType) { if (annotationType == null) { return; } if (annotationDefaultValues != null) { annotationDefaultValues.remove(annotationType); } if (allAnnotations != null) { allAnnotations.remove(annotationType); } if (declaredAnnotations != null) { declaredAnnotations.remove(annotationType); removeFromStereotypes(annotationType); } if (annotationRepeatableContainer != null) { annotationRepeatableContainer.remove(annotationType); } } /** * Removes a stereotype annotation for the given annotation type. * * @param annotationType The annotation type * @since 3.0.0 */ public void removeStereotype(String annotationType) { if (annotationType == null) { return; } if (annotationsByStereotype == null || annotationsByStereotype.remove(annotationType) == null) { return; } if (allStereotypes != null) { allStereotypes.remove(annotationType); } if (declaredStereotypes != null) { declaredStereotypes.remove(annotationType); } final Iterator>> i = annotationsByStereotype.entrySet().iterator(); while (i.hasNext()) { Map.Entry> entry = i.next(); final List value = entry.getValue(); if (value.remove(annotationType) && value.isEmpty()) { i.remove(); } } } private void removeFromStereotypes(String annotationType) { if (annotationsByStereotype == null || annotationsByStereotype.isEmpty()) { return; } final Iterator>> i = annotationsByStereotype.entrySet().iterator(); Set removeNext = new LinkedHashSet<>(); while (i.hasNext()) { final Map.Entry> entry = i.next(); final String stereotypeName = entry.getKey(); final List value = entry.getValue(); if (value.remove(annotationType)) { if (value.isEmpty()) { removeNext.add(stereotypeName); i.remove(); if (allStereotypes != null) { this.allStereotypes.remove(stereotypeName); } if (declaredStereotypes != null) { this.declaredStereotypes.remove(stereotypeName); } if (annotationDefaultValues != null) { annotationDefaultValues.remove(stereotypeName); } removeNext.add(stereotypeName); } } } for (String stereotype : removeNext) { removeFromStereotypes(stereotype); } } private boolean isRepeatableAnnotationContainer(String annotation) { return annotationRepeatableContainer != null && annotationRepeatableContainer.containsValue(annotation); } @Override protected String findRepeatableAnnotationContainerInternal(String annotation) { if (annotationRepeatableContainer != null) { String repeatedName = annotationRepeatableContainer.get(annotation); if (repeatedName != null) { return repeatedName; } } return AnnotationMetadataSupport.getRepeatableAnnotation(annotation); } private boolean hasEvaluatedExpressions(Map annotationValues) { if (CollectionUtils.isEmpty(annotationValues)) { return false; } return annotationValues.values().stream().anyMatch(value -> { if (value instanceof EvaluatedExpressionReference) { return true; } else if (value instanceof AnnotationValue av) { return hasEvaluatedExpressions(av.getValues()); } else if (value instanceof AnnotationValue[] avArray) { return Arrays.stream(avArray) .map(AnnotationValue::getValues) .anyMatch(this::hasEvaluatedExpressions); } else { return false; } }); } }