io.micronaut.inject.annotation.MutableAnnotationMetadata Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.inject.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) {
return ((AnnotationMetadataHierarchy) annotationMetadata).merge();
} else if (annotationMetadata instanceof MutableAnnotationMetadata) {
return ((MutableAnnotationMetadata) annotationMetadata).clone();
} else if (annotationMetadata instanceof DefaultAnnotationMetadata) {
MutableAnnotationMetadata metadata = new MutableAnnotationMetadata();
metadata.addAnnotationMetadata((DefaultAnnotationMetadata) annotationMetadata);
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 ? new LinkedHashMap<>(3) : existing;
} else {
existing = new LinkedHashMap<>(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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy