Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support accessing enum values annotations #10581

Merged
merged 2 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.EnumConstantElement;
import io.micronaut.inject.ast.EnumElement;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.KotlinParameterElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.beans.AbstractEnumBeanIntrospectionAndReference;
import io.micronaut.inject.beans.AbstractInitializableBeanIntrospection;
import io.micronaut.inject.beans.AbstractInitializableBeanIntrospectionAndReference;
import io.micronaut.inject.processing.JavaModelUtils;
Expand Down Expand Up @@ -88,6 +91,7 @@ final class BeanIntrospectionWriter extends AbstractClassFileWriter {
private static final String FIELD_CONSTRUCTOR_ARGUMENTS = "$CONSTRUCTOR_ARGUMENTS";
private static final String FIELD_BEAN_PROPERTIES_REFERENCES = "$PROPERTIES_REFERENCES";
private static final String FIELD_BEAN_METHODS_REFERENCES = "$METHODS_REFERENCES";
private static final String FIELD_ENUM_CONSTANTS_REFERENCES = "$ENUM_CONSTANTS_REFERENCES";
private static final Method FIND_PROPERTY_BY_INDEX_METHOD = Method.getMethod(
ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getPropertyByIndex", int.class)
);
Expand Down Expand Up @@ -334,7 +338,7 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE
}
}

private void buildStaticInit(ClassWriter classWriter) {
private void buildStaticInit(ClassWriter classWriter, boolean isEnum) {
GeneratorAdapter staticInit = visitStaticInitializer(classWriter);
Map<String, Integer> defaults = new HashMap<>();

Expand Down Expand Up @@ -388,6 +392,23 @@ private void buildStaticInit(ClassWriter classWriter) {
});
staticInit.putStatic(introspectionType, FIELD_BEAN_METHODS_REFERENCES, beanMethodsRefs);
}
if (isEnum) {
Type type = Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantRef[].class);
classWriter.visitField(
ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_ENUM_CONSTANTS_REFERENCES,
type.getDescriptor(),
null,
null
);
pushNewArray(staticInit, AbstractEnumBeanIntrospectionAndReference.EnumConstantRef.class, ((EnumElement) classElement).elements(), enumConstantElement -> {
pushEnumConstantReference(
classWriter,
staticInit,
enumConstantElement
);
});
staticInit.putStatic(introspectionType, FIELD_ENUM_CONSTANTS_REFERENCES, type);
}

int indexesIndex = 0;
for (String annotationName : indexByAnnotations.keySet()) {
Expand Down Expand Up @@ -555,6 +576,29 @@ private void pushBeanMethodReference(ClassWriter classWriter,
int.class);
}

private void pushEnumConstantReference(ClassWriter classWriter,
GeneratorAdapter staticInit,
EnumConstantElement enumConstantElement) {
staticInit.newInstance(Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantRef.class));
staticInit.dup();
// 1: value
staticInit.getStatic(getTypeReference(enumConstantElement.getOwningType()), enumConstantElement.getName(), getTypeReference(enumConstantElement.getOwningType()));
// 2: annotation metadata
AnnotationMetadata annotationMetadata = enumConstantElement.getAnnotationMetadata();
if (annotationMetadata.isEmpty()) {
staticInit.getStatic(Type.getType(AnnotationMetadata.class), "EMPTY_METADATA", Type.getType(AnnotationMetadata.class));
} else {
pushAnnotationMetadata(classWriter, staticInit, annotationMetadata);
}

invokeConstructor(
staticInit,
AbstractEnumBeanIntrospectionAndReference.EnumConstantRef.class,
Enum.class,
AnnotationMetadata.class
);
}

private boolean hasAssociatedConstructorArgument(String name, TypedElement typedElement) {
if (constructor != null) {
ParameterElement[] parameters = constructor.getParameters();
Expand All @@ -568,7 +612,8 @@ private boolean hasAssociatedConstructorArgument(String name, TypedElement typed
}

private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {
final Type superType = Type.getType(AbstractInitializableBeanIntrospectionAndReference.class);
boolean isEnum = classElement.isEnum();
final Type superType = isEnum ? Type.getType(AbstractEnumBeanIntrospectionAndReference.class) : Type.getType(AbstractInitializableBeanIntrospectionAndReference.class);

ClassWriter classWriter = new AptClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES, visitorContext);
classWriter.visit(
Expand All @@ -584,7 +629,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV

annotateAsGeneratedAndService(classWriter, introspectionName);

buildStaticInit(classWriter);
buildStaticInit(classWriter, isEnum);

final GeneratorAdapter constructorWriter = startConstructor(classWriter);

Expand Down Expand Up @@ -632,17 +677,34 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV
FIELD_BEAN_METHODS_REFERENCES,
Type.getType(AbstractInitializableBeanIntrospection.BeanMethodRef[].class));
}

invokeConstructor(
constructorWriter,
AbstractInitializableBeanIntrospectionAndReference.class,
Class.class,
AnnotationMetadata.class,
AnnotationMetadata.class,
Argument[].class,
AbstractInitializableBeanIntrospection.BeanPropertyRef[].class,
AbstractInitializableBeanIntrospection.BeanMethodRef[].class
);
if (isEnum) {
constructorWriter.getStatic(introspectionType,
FIELD_ENUM_CONSTANTS_REFERENCES,
Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantRef[].class)
);
invokeConstructor(
constructorWriter,
AbstractEnumBeanIntrospectionAndReference.class,
Class.class,
AnnotationMetadata.class,
AnnotationMetadata.class,
Argument[].class,
AbstractInitializableBeanIntrospection.BeanPropertyRef[].class,
AbstractInitializableBeanIntrospection.BeanMethodRef[].class,
AbstractEnumBeanIntrospectionAndReference.EnumConstantRef[].class
);
} else {
invokeConstructor(
constructorWriter,
AbstractInitializableBeanIntrospectionAndReference.class,
Class.class,
AnnotationMetadata.class,
AnnotationMetadata.class,
Argument[].class,
AbstractInitializableBeanIntrospection.BeanPropertyRef[].class,
AbstractInitializableBeanIntrospection.BeanMethodRef[].class
);
}

constructorWriter.returnValue();
constructorWriter.visitMaxs(2, 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2017-2024 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.core.beans;

import io.micronaut.core.annotation.AnnotationMetadataDelegate;

import java.util.List;

/**
* A variation of {@link BeanIntrospection} that is representing an enum as a bean.
*
* @param <E> The type of the enum
* @author Denis Stepanov
* @since 4.4.0
*/
public interface EnumBeanIntrospection<E extends Enum<E>> extends BeanIntrospection<E> {

/**
* @return The constants of the enum
*/
List<EnumConstant<E>> getConstants();
dstepanov marked this conversation as resolved.
Show resolved Hide resolved

/**
* The enum constant.
*
* @param <E> The enum type
*/
interface EnumConstant<E> extends AnnotationMetadataDelegate {

/**
* @return The instance value.
*/
E getValue();
dstepanov marked this conversation as resolved.
Show resolved Hide resolved

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.micronaut.core.beans.BeanIntrospectionReference
import io.micronaut.core.beans.BeanIntrospector
import io.micronaut.core.beans.BeanMethod
import io.micronaut.core.beans.BeanProperty
import io.micronaut.core.beans.EnumBeanIntrospection
import io.micronaut.core.convert.ConversionContext
import io.micronaut.core.convert.TypeConverter
import io.micronaut.core.reflect.InstantiationUtils
Expand Down Expand Up @@ -3924,6 +3925,61 @@ public enum Test {
thrown(ClassNotFoundException)
}

void "test enum bean with annotations"() {
BeanIntrospection introspection = buildBeanIntrospection('test.Test', '''
package test;

import io.micronaut.core.annotation.*;
import com.fasterxml.jackson.annotation.JsonProperty;

@Introspected
public enum Test {

@JsonProperty("X")
A(0),
@JsonProperty("Y")
B(1),
@JsonProperty("Z")
C(2);

private final int number;

Test(int number) {
this.number = number;
}

public int getNumber() {
return number;
}
}
''')

expect:
introspection != null
introspection.beanProperties.size() == 1
introspection.getProperty("number").isPresent()
introspection instanceof EnumBeanIntrospection

when:
def enumIntrospection = introspection as EnumBeanIntrospection
def instance = introspection.instantiate("A")

then:
instance.name() == "A"
introspection.getRequiredProperty("number", int).get(instance) == 0

when:
def annotationMetadata = enumIntrospection.constants.find { it.value == instance }.annotationMetadata

then:
annotationMetadata.stringValue(JsonProperty).get() == "X"

and:
enumIntrospection.constants[0].stringValue(JsonProperty).get() == "X"
enumIntrospection.constants[1].stringValue(JsonProperty).get() == "Y"
enumIntrospection.constants[2].stringValue(JsonProperty).get() == "Z"
}

void "test enum bean properties with custom getter"() {
BeanIntrospection introspection = buildBeanIntrospection('test.Test', '''
package test;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2017-2024 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.beans;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.beans.BeanIntrospectionReference;
import io.micronaut.core.beans.EnumBeanIntrospection;
import io.micronaut.core.type.Argument;

import java.util.List;

/**
* A variation of {@link AbstractInitializableBeanIntrospection} that is also a {@link BeanIntrospectionReference}.
*
* @param <E> The enum type
* @author Denis Stepanov
* @since 4.4.0
*/
@Internal
public abstract class AbstractEnumBeanIntrospectionAndReference<E extends Enum<E>> extends AbstractInitializableBeanIntrospectionAndReference<E> implements EnumBeanIntrospection<E> {

private final List<EnumConstant<E>> enumConstantRefs;

/**
* The default constructor.
*
* @param beanType The bean type
* @param annotationMetadata The annotation metadata
* @param constructorAnnotationMetadata The constructor annotation metadata
* @param constructorArguments The constructor arguments
* @param propertiesRefs The property references
* @param methodsRefs The method references
* @param enumValueRefs The enum references
*/
protected AbstractEnumBeanIntrospectionAndReference(Class<E> beanType,
AnnotationMetadata annotationMetadata,
AnnotationMetadata constructorAnnotationMetadata,
Argument<?>[] constructorArguments,
BeanPropertyRef<Object>[] propertiesRefs,
BeanMethodRef<Object>[] methodsRefs,
EnumConstantRef<E>[] enumValueRefs) {
super(beanType, annotationMetadata, constructorAnnotationMetadata, constructorArguments, propertiesRefs, methodsRefs);
this.enumConstantRefs = List.of(enumValueRefs);
}

@NonNull
@Override
public List<EnumConstant<E>> getConstants() {
return enumConstantRefs;
}

/**
* Enum value compile-time data container.
*/
@Internal
@UsedByGeneratedCode
public record EnumConstantRef<E extends Enum<E>>(@NonNull E value,
@NonNull AnnotationMetadata annotationMetadata) implements EnumConstant<E> {

@NonNull
@Override
public E getValue() {
return value;
}

@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ Args = --enable-http \
--initialize-at-build-time=io.micronaut.context.AbstractExecutableMethodsDefinition$MethodReference \
--initialize-at-build-time=io.micronaut.inject.beans.AbstractInitializableBeanIntrospection$BeanPropertyRef \
--initialize-at-build-time=io.micronaut.inject.beans.AbstractInitializableBeanIntrospection$BeanMethodRef \
--initialize-at-build-time=io.micronaut.inject.beans.AbstractEnumBeanIntrospectionAndReference$EnumConstantRef \
--initialize-at-run-time=io.micronaut.context.env.CachedEnvironment
Loading