AbstractElementAnnotationMetadataFactory.java
/*
* Copyright 2017-2022 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.ast.annotation;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.AnnotationValueBuilder;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.EnumConstantElement;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PackageElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.WildcardElement;
import java.lang.annotation.Annotation;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Abstract element annotation metadata factory.
*
* @param <K> The element type
* @param <A> The annotation type
* @author Denis Stepanov
* @since 4.0.0
*/
public abstract class AbstractElementAnnotationMetadataFactory<K, A> implements ElementAnnotationMetadataFactory {
protected final boolean isReadOnly;
protected final AbstractAnnotationMetadataBuilder<K, A> metadataBuilder;
protected AbstractElementAnnotationMetadataFactory(boolean isReadOnly, AbstractAnnotationMetadataBuilder<K, A> metadataBuilder) {
this.isReadOnly = isReadOnly;
this.metadataBuilder = metadataBuilder;
}
@Override
public ElementAnnotationMetadata build(Element element) {
if (element instanceof ClassElement classElement) {
return buildForClass(classElement);
}
if (element instanceof ConstructorElement constructorElement) {
return buildForConstructor(constructorElement);
}
if (element instanceof MethodElement methodElement) {
return buildForMethod(methodElement);
}
if (element instanceof FieldElement fieldElement) {
return buildForField(fieldElement);
}
if (element instanceof ParameterElement parameterElement) {
return buildForParameter(parameterElement);
}
if (element instanceof PackageElement packageElement) {
return buildForPackage(packageElement);
}
if (element instanceof PropertyElement propertyElement) {
return buildForProperty(propertyElement);
}
if (element instanceof EnumConstantElement enumConstantElement) {
return buildForEnumConstantElement(enumConstantElement);
}
throw new IllegalStateException("Unknown element: " + element.getClass() + " with native type: " + element.getNativeType());
}
@Override
public ElementAnnotationMetadata buildMutable(AnnotationMetadata originalAnnotationMetadata) {
if (originalAnnotationMetadata instanceof AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata) {
throw new IllegalStateException();
}
if (originalAnnotationMetadata instanceof ElementAnnotationMetadata) {
throw new IllegalStateException("Not supported element annotation metadata: " + originalAnnotationMetadata);
}
return new MutableElementAnnotationMetadata() {
private AnnotationMetadata thisAnnotationMetadata = originalAnnotationMetadata;
@Override
public AnnotationMetadata getAnnotationMetadata() {
return thisAnnotationMetadata;
}
@Override
protected AnnotationMetadata getAnnotationMetadataToModify() {
return thisAnnotationMetadata;
}
@Override
protected AnnotationMetadata replaceAnnotationsInternal(AnnotationMetadata annotationMetadata) {
thisAnnotationMetadata = annotationMetadata;
return thisAnnotationMetadata;
}
};
}
@Override
public ElementAnnotationMetadata buildTypeAnnotations(ClassElement element) {
return buildTypeAnnotationsForClass(element);
}
@Override
public ElementAnnotationMetadata buildGenericTypeAnnotations(GenericElement element) {
if (element instanceof GenericPlaceholderElement placeholderElement) {
return buildTypeAnnotationsForGenericPlaceholder(placeholderElement);
}
if (element instanceof WildcardElement wildcardElement) {
return buildTypeAnnotationsForWildcard(wildcardElement);
}
throw new IllegalStateException("Unknown generic element: " + element.getClass() + " with native type: " + element.getNativeType());
}
/**
* Resolve native element.
*
* @param element The element
* @return The native element
*/
protected K getNativeElement(Element element) {
return (K) element.getNativeType();
}
/**
* Lookup annotation metadata for the package.
*
* @param packageElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupForPackage(PackageElement packageElement) {
return metadataBuilder.lookupOrBuildForType(getNativeElement(packageElement));
}
/**
* Lookup annotation metadata for the parameter.
*
* @param parameterElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupForParameter(ParameterElement parameterElement) {
return metadataBuilder.lookupOrBuildForParameter(
getNativeElement(parameterElement.getMethodElement().getOwningType()),
getNativeElement(parameterElement.getMethodElement()),
getNativeElement(parameterElement)
);
}
/**
* Lookup annotation metadata for the field.
*
* @param fieldElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupForField(FieldElement fieldElement) {
return metadataBuilder.lookupOrBuildForField(
getNativeElement(fieldElement.getOwningType()),
getNativeElement(fieldElement)
);
}
/**
* Lookup annotation metadata for the method.
*
* @param methodElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupForMethod(MethodElement methodElement) {
return metadataBuilder.lookupOrBuildForMethod(
getNativeElement(methodElement.getOwningType()),
getNativeElement(methodElement)
);
}
/**
* Lookup annotation metadata for the class.
*
* @param classElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupForClass(ClassElement classElement) {
return metadataBuilder.lookupOrBuildForType(getNativeElement(classElement));
}
/**
* Lookup annotation metadata for the class.
*
* @param classElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupTypeAnnotationsForClass(ClassElement classElement) {
return new AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return AnnotationMetadata.EMPTY_METADATA;
}
@Override
public boolean isMutated() {
return false;
}
@Override
public void update(AnnotationMetadata annotationMetadata) {
throw new IllegalStateException("Class element: [" + classElement + "] doesn't support type annotations!");
}
};
}
/**
* Lookup annotation metadata for the placeholder.
*
* @param placeholderElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupTypeAnnotationsForGenericPlaceholder(GenericPlaceholderElement placeholderElement) {
return metadataBuilder.lookupOrBuildForType(getNativeElement(placeholderElement));
}
/**
* Lookup annotation metadata for the wildcard.
*
* @param wildcardElement The element
* @return The annotation metadata
*/
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookupTypeAnnotationsForWildcard(WildcardElement wildcardElement) {
return metadataBuilder.lookupOrBuildForType(getNativeElement(wildcardElement));
}
@NonNull
private AbstractElementAnnotationMetadata buildForProperty(@NonNull PropertyElement propertyElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
throw new IllegalStateException("Properties should combine annotations for it's elements!");
}
@Override
public String toString() {
return propertyElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForEnumConstantElement(@NonNull EnumConstantElement enumConstantElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForField(enumConstantElement);
}
@Override
public String toString() {
return enumConstantElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForPackage(@NonNull PackageElement packageElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForPackage(packageElement);
}
@Override
public String toString() {
return packageElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForParameter(@NonNull ParameterElement parameterElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForParameter(parameterElement);
}
@Override
public String toString() {
return parameterElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForField(@NonNull FieldElement fieldElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForField(fieldElement);
}
@Override
public String toString() {
return fieldElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForMethod(@NonNull MethodElement methodElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForMethod(methodElement);
}
@Override
public String toString() {
return methodElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForConstructor(@NonNull ConstructorElement constructorElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForMethod(constructorElement);
}
@Override
public String toString() {
return constructorElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildTypeAnnotationsForClass(@NonNull ClassElement classElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupTypeAnnotationsForClass(classElement);
}
@Override
public String toString() {
return classElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildForClass(@NonNull ClassElement classElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupForClass(classElement);
}
@Override
public String toString() {
return classElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildTypeAnnotationsForGenericPlaceholder(@NonNull GenericPlaceholderElement placeholderElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupTypeAnnotationsForGenericPlaceholder(placeholderElement);
}
@Override
public String toString() {
return placeholderElement.toString();
}
};
}
@NonNull
private AbstractElementAnnotationMetadata buildTypeAnnotationsForWildcard(@NonNull WildcardElement wildcardElement) {
return new AbstractElementAnnotationMetadata() {
@Override
protected AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup() {
return lookupTypeAnnotationsForWildcard(wildcardElement);
}
@Override
public String toString() {
return wildcardElement.toString();
}
};
}
/**
* Abstract implementation of {@link ElementAnnotationMetadata}.
*
* @author Denis Stepanov
* @since 4.0.0
*/
protected abstract class AbstractElementAnnotationMetadata extends MutableElementAnnotationMetadata {
private final boolean readOnly;
private AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata cacheEntry;
private AnnotationMetadata annotationMetadata;
protected AbstractElementAnnotationMetadata() {
this(AbstractElementAnnotationMetadataFactory.this.isReadOnly);
}
protected AbstractElementAnnotationMetadata(boolean readOnly) {
this.readOnly = readOnly;
}
protected abstract AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata lookup();
private AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata getCacheEntry() {
if (cacheEntry == null) {
cacheEntry = lookup();
}
return cacheEntry;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
if (annotationMetadata != null) {
return annotationMetadata;
}
return getCacheEntry();
}
@Override
protected AnnotationMetadata getAnnotationMetadataToModify() {
if (annotationMetadata != null) {
return annotationMetadata;
}
return getCacheEntry().copyAnnotationMetadata();
}
@Override
protected AnnotationMetadata replaceAnnotationsInternal(AnnotationMetadata annotationMetadata) {
if (annotationMetadata instanceof AbstractAnnotationMetadataBuilder.CachedAnnotationMetadata) {
throw new IllegalStateException();
}
if (annotationMetadata instanceof MutableAnnotationMetadataDelegate) {
throw new IllegalStateException();
}
if (annotationMetadata instanceof AnnotationMetadataHierarchy) {
throw new IllegalStateException("Not supported to cache AnnotationMetadataHierarchy");
}
if (annotationMetadata.isEmpty()) {
annotationMetadata = EMPTY_METADATA;
}
if (readOnly) {
this.annotationMetadata = annotationMetadata;
} else {
getCacheEntry().update(annotationMetadata);
}
return getAnnotationMetadata();
}
}
/**
* Abstract mutable implementation of {@link ElementAnnotationMetadata}.
*
* @author Denis Stepanov
* @since 4.0.0
*/
protected abstract class MutableElementAnnotationMetadata implements ElementAnnotationMetadata {
/**
* Return the annotation metadata to modify.
*
* @return The annotation metadata to modify
*/
protected abstract AnnotationMetadata getAnnotationMetadataToModify();
/**
* Replaces existing annotation metadata.
*
* @param annotationMetadata new annotation metadata
* @return The annotation metadata
*/
protected abstract AnnotationMetadata replaceAnnotationsInternal(AnnotationMetadata annotationMetadata);
@Override
@SuppressWarnings("java:S1192")
public <T extends Annotation> AnnotationMetadata annotate(@NonNull String annotationType, @NonNull Consumer<AnnotationValueBuilder<T>> consumer) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
AnnotationValueBuilder<T> builder = AnnotationValue.builder(annotationType, metadataBuilder.getRetentionPolicy(annotationType));
//noinspection ConstantConditions
if (consumer != null) {
consumer.accept(builder);
AnnotationValue<T> av = builder.build();
return replaceAnnotationsInternal(metadataBuilder.annotate(getAnnotationMetadataToModify(), av));
}
return getAnnotationMetadata();
}
@Override
public <T extends Annotation> AnnotationMetadata annotate(AnnotationValue<T> annotationValue) {
ArgumentUtils.requireNonNull("annotationValue", annotationValue);
return replaceAnnotationsInternal(metadataBuilder.annotate(getAnnotationMetadataToModify(), annotationValue));
}
@Override
public AnnotationMetadata removeAnnotation(@NonNull String annotationType) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
return replaceAnnotationsInternal(metadataBuilder.removeAnnotation(getAnnotationMetadataToModify(), annotationType));
}
@Override
public <T extends Annotation> AnnotationMetadata removeAnnotationIf(@NonNull Predicate<AnnotationValue<T>> predicate) {
ArgumentUtils.requireNonNull("predicate", predicate);
return replaceAnnotationsInternal(metadataBuilder.removeAnnotationIf(getAnnotationMetadataToModify(), predicate));
}
@Override
public AnnotationMetadata removeStereotype(@NonNull String annotationType) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
return replaceAnnotationsInternal(metadataBuilder.removeStereotype(getAnnotationMetadataToModify(), annotationType));
}
}
}