io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder 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.annotation.AliasFor;
import io.micronaut.context.annotation.Aliases;
import io.micronaut.context.annotation.DefaultScope;
import io.micronaut.context.annotation.Type;
import io.micronaut.core.annotation.*;
import io.micronaut.core.io.service.ServiceDefinition;
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.core.value.OptionalValues;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import javax.inject.Scope;
import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
import java.util.*;
import java.util.function.BiConsumer;
/**
* An abstract implementation that builds {@link AnnotationMetadata}.
*
* @param The element type
* @param The annotation type
* @author Graeme Rocher
* @since 1.0
*/
public abstract class AbstractAnnotationMetadataBuilder {
/**
* Names of annotations that should produce deprecation warnings.
* The key in the map is the deprecated annotation the value the replacement.
*/
@SuppressWarnings("unchecked")
private static final Map DEPRECATED_ANNOTATION_NAMES = CollectionUtils.mapOf(
AnnotationUtil.NULLABLE, Nullable.class.getName(),
AnnotationUtil.NON_NULL, NonNull.class.getName()
);
private static final Map>> ANNOTATION_MAPPERS = new HashMap<>(10);
private static final Map>> ANNOTATION_TRANSFORMERS = new HashMap<>(5);
private static final Map> ANNOTATION_REMAPPERS = new HashMap<>(5);
private static final Map MUTATED_ANNOTATION_METADATA = new HashMap<>(100);
private static final List DEFAULT_ANNOTATE_EXCLUDES = Arrays.asList(Internal.class.getName(), Experimental.class.getName());
static {
SoftServiceLoader serviceLoader = SoftServiceLoader.load(AnnotationMapper.class, AbstractAnnotationMetadataBuilder.class.getClassLoader());
for (ServiceDefinition definition : serviceLoader) {
if (definition.isPresent()) {
AnnotationMapper mapper = definition.load();
try {
String name = null;
if (mapper instanceof TypedAnnotationMapper) {
name = ((TypedAnnotationMapper) mapper).annotationType().getName();
} else if (mapper instanceof NamedAnnotationMapper) {
name = ((NamedAnnotationMapper) mapper).getName();
}
if (StringUtils.isNotEmpty(name)) {
ANNOTATION_MAPPERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(mapper);
}
} catch (Throwable e) {
// mapper, missing dependencies, continue
}
}
}
SoftServiceLoader transformerSoftServiceLoader =
SoftServiceLoader.load(AnnotationTransformer.class, AbstractAnnotationMetadataBuilder.class.getClassLoader());
for (ServiceDefinition definition : transformerSoftServiceLoader) {
if (definition.isPresent()) {
AnnotationTransformer transformer = definition.load();
try {
String name = null;
if (transformer instanceof TypedAnnotationTransformer) {
name = ((TypedAnnotationTransformer) transformer).annotationType().getName();
} else if (transformer instanceof NamedAnnotationTransformer) {
name = ((NamedAnnotationTransformer) transformer).getName();
}
if (StringUtils.isNotEmpty(name)) {
ANNOTATION_TRANSFORMERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(transformer);
}
} catch (Throwable e) {
// mapper, missing dependencies, continue
}
}
}
SoftServiceLoader remapperLoader = SoftServiceLoader.load(AnnotationRemapper.class, AbstractAnnotationMetadataBuilder.class.getClassLoader());
for (ServiceDefinition definition : remapperLoader) {
if (definition.isPresent()) {
AnnotationRemapper mapper = definition.load();
try {
String name = mapper.getPackageName();
if (StringUtils.isNotEmpty(name)) {
ANNOTATION_REMAPPERS.computeIfAbsent(name, s -> new ArrayList<>(2)).add(mapper);
}
} catch (Throwable e) {
// mapper, missing dependencies, continue
}
}
}
}
private boolean validating = true;
private final Set erroneousElements = new HashSet<>();
/**
* Default constructor.
*/
protected AbstractAnnotationMetadataBuilder() {
}
/**
* Build only metadata for declared annotations.
*
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildDeclared(T element) {
DefaultAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
try {
AnnotationMetadata metadata = buildInternal(null, element, annotationMetadata, true, true);
if (metadata.isEmpty()) {
return AnnotationMetadata.EMPTY_METADATA;
}
return metadata;
} catch (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
* @param annotations The annotations
* @param includeTypeAnnotations Whether to include type level annotations in the metadata for the element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildDeclared(T element, List extends A> annotations, boolean includeTypeAnnotations) {
if (CollectionUtils.isEmpty(annotations)) {
return AnnotationMetadata.EMPTY_METADATA;
}
DefaultAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
if (includeTypeAnnotations) {
buildInternal(element, element, annotationMetadata, false, true);
}
try {
includeAnnotations(annotationMetadata, element, true, annotations);
if (annotationMetadata.isEmpty()) {
return AnnotationMetadata.EMPTY_METADATA;
}
return annotationMetadata;
} catch (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 metadata for the given element, including any metadata that is inherited via method or type overrides.
*
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildOverridden(T element) {
final AnnotationMetadata existing = MUTATED_ANNOTATION_METADATA.get(new MetadataKey(getDeclaringType(element), element));
if (existing != null) {
return existing;
} else {
DefaultAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
try {
AnnotationMetadata metadata = buildInternal(null, element, annotationMetadata, false, false);
if (metadata.isEmpty()) {
return AnnotationMetadata.EMPTY_METADATA;
}
return metadata;
} catch (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 the meta data for the given element. If the element is a method the class metadata will be included.
*
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata build(T element) {
String declaringType = getDeclaringType(element);
return build(declaringType, element);
}
/**
* Build the meta data for the given element. If the element is a method the class metadata will be included.
*
* @param declaringType The declaring type
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata build(String declaringType, T element) {
final AnnotationMetadata existing = lookupExisting(declaringType, element);
if (existing != null) {
return existing;
} else {
DefaultAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
try {
AnnotationMetadata metadata = buildInternal(null, element, annotationMetadata, true, false);
if (metadata.isEmpty()) {
return AnnotationMetadata.EMPTY_METADATA;
}
return metadata;
} catch (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;
}
}
}
}
/**
* Whether the element is a field, method, class or constructor.
*
* @param element The element
* @return True if it is
*/
protected abstract boolean isMethodOrClassElement(T element);
/**
* Obtains the declaring type for an element.
*
* @param element The element
* @return The declaring type
*/
protected abstract @NonNull String getDeclaringType(@NonNull T element);
/**
* Build the meta data for the given method element excluding any class metadata.
*
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildForMethod(T element) {
String declaringType = getDeclaringType(element);
final AnnotationMetadata existing = lookupExisting(declaringType, element);
if (existing != null) {
return existing;
} else {
DefaultAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
return buildInternal(null, element, annotationMetadata, false, false);
}
}
/**
* Get the annotation metadata for the given element and the given parent.
* This method is used for cases when you need to combine annotation metadata for
* two elements, for example a JavaBean property where the field and the method metadata
* need to be combined.
*
* @param parent The parent element
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildForParent(T parent, T element) {
String declaringType = getDeclaringType(element);
return buildForParent(declaringType, parent, element);
}
/**
* Build the meta data for the given parent and method element excluding any class metadata.
*
* @param declaringType The declaring type
* @param parent The parent element
* @param element The element
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildForParent(String declaringType, T parent, T element) {
final AnnotationMetadata existing = lookupExisting(declaringType, element);
DefaultAnnotationMetadata annotationMetadata;
if (existing instanceof DefaultAnnotationMetadata) {
// ugly, but will have to do
annotationMetadata = ((DefaultAnnotationMetadata) existing).clone();
} else if (existing instanceof AnnotationMetadataHierarchy) {
final AnnotationMetadata declaredMetadata = ((AnnotationMetadataHierarchy) existing).getDeclaredMetadata();
if (declaredMetadata instanceof DefaultAnnotationMetadata) {
annotationMetadata = ((DefaultAnnotationMetadata) declaredMetadata).clone();
} else {
annotationMetadata = new MutableAnnotationMetadata();
}
} else {
annotationMetadata = new MutableAnnotationMetadata();
}
return buildInternal(parent, element, annotationMetadata, false, false);
}
/**
* Build the meta data for the given method element excluding any class metadata.
*
* @param parent The parent element
* @param element The element
* @param inheritTypeAnnotations Whether to inherit annotations from type as stereotypes
* @return The {@link AnnotationMetadata}
*/
public AnnotationMetadata buildForParent(T parent, T element, boolean inheritTypeAnnotations) {
String declaringType = getDeclaringType(element);
final AnnotationMetadata existing = lookupExisting(declaringType, element);
DefaultAnnotationMetadata annotationMetadata;
if (existing instanceof DefaultAnnotationMetadata) {
// ugly, but will have to do
annotationMetadata = ((DefaultAnnotationMetadata) existing).clone();
} else {
annotationMetadata = new MutableAnnotationMetadata();
}
return buildInternal(parent, element, annotationMetadata, inheritTypeAnnotations, false);
}
/**
* 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);
/**
* 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.
*
* For example, {@code @Parent(value = {@Child, @Child})} should result in the two
* child annotations being returned from this method instead 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 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 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 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.
*/
protected @Nullable
AnnotatedElementValidator getElementValidator() {
return null;
}
/**
* Adds an error.
*
* @param originatingElement The originating element
* @param error The error
*/
protected abstract void addError(@NonNull T originatingElement, @NonNull String error);
/**
* Adds an 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 memberName The member name
* @param annotationValue The value
* @return The object
*/
protected abstract Object readAnnotationValue(T originatingElement, T member, String memberName, Object annotationValue);
/**
* Read the raw default annotation values from the given annotation.
*
* @param annotationMirror The annotation
* @return The values
*/
protected abstract Map extends T, ?> readAnnotationDefaultValues(A annotationMirror);
/**
* 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
* @return The values
*/
protected abstract OptionalValues> getAnnotationValues(T originatingElement, T member, Class> 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 is one.
*
* @param annotationMirror The annotation mirror
* @return Return the name or null
*/
protected abstract @Nullable
String getRepeatableName(A annotationMirror);
/**
* Obtain the name of the repeatable annotation if the annotation is is one.
*
* @param annotationType The annotation mirror
* @return Return the name or null
*/
protected abstract @Nullable
String getRepeatableNameForType(T annotationType);
/**
* @param originatingElement The originating element
* @param annotationMirror The annotation
* @return The annotation value
*/
protected io.micronaut.core.annotation.AnnotationValue readNestedAnnotationValue(T originatingElement, A annotationMirror) {
io.micronaut.core.annotation.AnnotationValue av;
Map extends T, ?> annotationValues = readAnnotationRawValues(annotationMirror);
final String annotationTypeName = getAnnotationTypeName(annotationMirror);
if (annotationValues.isEmpty()) {
av = new io.micronaut.core.annotation.AnnotationValue(annotationTypeName);
} else {
Map resolvedValues = new LinkedHashMap<>();
for (Map.Entry extends T, ?> entry : annotationValues.entrySet()) {
T member = entry.getKey();
OptionalValues> aliasForValues = getAnnotationValues(originatingElement, member, AliasFor.class);
Object annotationValue = entry.getValue();
Optional> aliasMember = aliasForValues.get("member");
Optional> aliasAnnotation = aliasForValues.get("annotation");
Optional> aliasAnnotationName = aliasForValues.get("annotationName");
if (aliasMember.isPresent() && !(aliasAnnotation.isPresent() || aliasAnnotationName.isPresent())) {
String aliasedNamed = aliasMember.get().toString();
readAnnotationRawValues(originatingElement, annotationTypeName, member, aliasedNamed, annotationValue, resolvedValues);
}
String memberName = getAnnotationMemberName(member);
readAnnotationRawValues(originatingElement, annotationTypeName, member, memberName, annotationValue, resolvedValues);
}
av = new io.micronaut.core.annotation.AnnotationValue(annotationTypeName, resolvedValues);
}
return av;
}
/**
* Return a mirror for the given annotation.
*
* @param annotationName The annotation name
* @return An optional mirror
*/
protected abstract Optional getAnnotationMirror(String annotationName);
/**
* Populate the annotation data for the given annotation.
*
* @param originatingElement The element the annotation data originates from
* @param annotationMirror The annotation
* @param metadata the metadata
* @param isDeclared Is the annotation a declared annotation
* @param retentionPolicy The retention policy
* @return The annotation values
*/
protected Map populateAnnotationData(
T originatingElement,
A annotationMirror,
DefaultAnnotationMetadata metadata,
boolean isDeclared,
RetentionPolicy retentionPolicy) {
String annotationName = getAnnotationTypeName(annotationMirror);
if (retentionPolicy == RetentionPolicy.RUNTIME) {
processAnnotationDefaults(originatingElement, annotationMirror, metadata, annotationName);
}
List parentAnnotations = new ArrayList<>();
parentAnnotations.add(annotationName);
Map extends T, ?> elementValues = readAnnotationRawValues(annotationMirror);
Map annotationValues;
if (CollectionUtils.isEmpty(elementValues)) {
annotationValues = new LinkedHashMap<>(3);
} else {
annotationValues = new LinkedHashMap<>(5);
for (Map.Entry extends T, ?> entry : elementValues.entrySet()) {
T member = entry.getKey();
if (member == null) {
continue;
}
boolean isInstantiatedMember = hasAnnotation(member, InstantiatedMember.class);
Object annotationValue = entry.getValue();
if (isInstantiatedMember) {
final String memberName = getAnnotationMemberName(member);
final Object rawValue = readAnnotationValue(originatingElement, member, memberName, annotationValue);
if (rawValue instanceof AnnotationClassValue) {
AnnotationClassValue acv = (AnnotationClassValue) rawValue;
annotationValues.put(memberName, new AnnotationClassValue(acv.getName(), true));
}
} else {
handleAnnotationAlias(
originatingElement,
metadata,
isDeclared,
annotationName,
parentAnnotations,
annotationValues,
member,
annotationValue
);
}
}
}
List> mappers = getAnnotationMappers(annotationName);
if (mappers != null) {
AnnotationValue> annotationValue = new AnnotationValue(annotationName, annotationValues);
VisitorContext visitorContext = createVisitorContext();
for (AnnotationMapper mapper : mappers) {
List mapped = mapper.map(annotationValue, visitorContext);
if (mapped != null) {
for (Object o : mapped) {
if (o instanceof AnnotationValue) {
AnnotationValue av = (AnnotationValue) o;
retentionPolicy = av.getRetentionPolicy();
String mappedAnnotationName = av.getAnnotationName();
Optional mappedMirror = getAnnotationMirror(mappedAnnotationName);
String repeatableName = mappedMirror.map(this::getRepeatableNameForType).orElse(null);
if (repeatableName != null) {
if (isDeclared) {
metadata.addDeclaredRepeatable(
repeatableName,
av,
retentionPolicy
);
} else {
metadata.addRepeatable(
repeatableName,
av,
retentionPolicy
);
}
} else {
Map values = av.getValues();
if (isDeclared) {
metadata.addDeclaredAnnotation(
mappedAnnotationName,
values,
retentionPolicy
);
} else {
metadata.addAnnotation(
mappedAnnotationName,
values,
retentionPolicy
);
}
}
RetentionPolicy finalRetentionPolicy = retentionPolicy;
mappedMirror.ifPresent(annMirror -> {
Map values = av.getValues();
values.forEach((key, value) -> {
T member = getAnnotationMember(annMirror, key);
if (member != null) {
handleAnnotationAlias(
originatingElement,
metadata,
isDeclared,
mappedAnnotationName,
Collections.emptyList(),
annotationValues,
member,
value
);
}
});
final Map extends T, ?> defaultValues = readAnnotationDefaultValues(mappedAnnotationName, annMirror);
if (finalRetentionPolicy == RetentionPolicy.RUNTIME) {
processAnnotationDefaults(originatingElement, metadata, mappedAnnotationName, defaultValues);
}
final ArrayList parents = new ArrayList<>();
processAnnotationStereotype(
parents,
annMirror,
mappedAnnotationName,
metadata,
isDeclared);
});
}
}
}
}
}
return annotationValues;
}
private void handleAnnotationAlias(T originatingElement, DefaultAnnotationMetadata metadata, boolean isDeclared, String annotationName, List parentAnnotations, Map annotationValues, T member, Object annotationValue) {
Optional> aliases = getAnnotationValues(originatingElement, member, Aliases.class).get("value");
if (aliases.isPresent()) {
Object value = aliases.get();
if (value instanceof AnnotationValue[]) {
AnnotationValue[] values = (AnnotationValue[]) value;
for (AnnotationValue av : values) {
OptionalValues
© 2015 - 2025 Weber Informatics LLC | Privacy Policy