Skip to content

Commit

Permalink
Fix regression where AliasFor is no longer recursive (micronaut-proje…
Browse files Browse the repository at this point in the history
…cts#8746)

---------

Co-authored-by: Denis Stepanov <denis.s.stepanov@oracle.com>
Rate limit · GitHub

Access has been restricted

You have triggered a rate limit.

Please wait a few minutes before you try again;
in some cases this may take up to an hour.

graemerocher and dstepanov authored Feb 17, 2023
1 parent 80a8749 commit 0d9d9e0
Showing 10 changed files with 193 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -38,7 +38,6 @@
import io.micronaut.inject.qualifiers.InterceptorBindingQualifier;
import io.micronaut.inject.visitor.VisitorContext;
import jakarta.inject.Qualifier;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
@@ -670,13 +669,16 @@ private void processAnnotationAlias(Map<CharSequence, Object> annotationValues,
aliasedAnnotation = aliasAnnotation.orElseGet(aliasAnnotationName::get);
String aliasedMemberName = aliasMember.get();
if (annotationValue != null) {
introducedAnnotations.add(
toProcessedAnnotation(
ProcessedAnnotation newAnnotation = toProcessedAnnotation(
AnnotationValue.builder(aliasedAnnotation, getRetentionPolicy(aliasedAnnotation))
.members(Collections.singletonMap(aliasedMemberName, annotationValue))
.build()
)
.members(Collections.singletonMap(aliasedMemberName, annotationValue))
.build()
);
introducedAnnotations.add(newAnnotation);
ProcessedAnnotation newNewAnnotation = processAliases(newAnnotation, introducedAnnotations);
if (newNewAnnotation != newAnnotation) {
introducedAnnotations.set(introducedAnnotations.indexOf(newAnnotation), newNewAnnotation);
}
}
}
} else if (aliasMember.isPresent()) {
@@ -771,7 +773,7 @@ private void addAnnotations(MutableAnnotationMetadata annotationMetadata,
addAnnotations(annotationMetadata, annotationValues, isDeclared, parentAnnotations);
}

@NotNull
@NonNull
private Stream<ProcessedAnnotation> annotationMirrorToAnnotationValue(Stream<? extends A> stream,
T element,
boolean originatingElementIsSameParent,
@@ -868,8 +870,14 @@ private ProcessedAnnotation createAnnotationValue(T originatingElement,
);
}

@NotNull
private Map<CharSequence, Object> getCachedAnnotationDefaults(String annotationName, T annotationType) {
/**
* Get the cached annotation defaults.
* @param annotationName The annotation name
* @param annotationType The annotation type
* @return The defaults
*/
@NonNull
protected Map<CharSequence, Object> getCachedAnnotationDefaults(String annotationName, T annotationType) {
Map<CharSequence, Object> defaultValues;
final Map<CharSequence, Object> defaults = ANNOTATION_DEFAULTS.get(annotationName);
if (defaults != null) {
Original file line number Diff line number Diff line change
@@ -257,15 +257,15 @@ public Optional<T> convert(
ArgumentConversionContext<T> context = (ArgumentConversionContext<T>) conversionContext;

String format = conversionContext.getAnnotationMetadata()
.getValue(Format.class, String.class).orElse(null);
.stringValue(Format.class).orElse(null);
if (format == null) {
return Optional.empty();
}

String name = conversionContext.getAnnotationMetadata().getValue(Bindable.class, String.class)
String name = conversionContext.getAnnotationMetadata().stringValue(Bindable.class)
.orElse(context.getArgument().getName());
String defaultValue = conversionContext.getAnnotationMetadata()
.getValue(Bindable.class, "defaultValue", String.class)
.stringValue(Bindable.class, "defaultValue")
.orElse(null);

switch (normalizeFormatName(format)) {
@@ -524,7 +524,7 @@ private Optional<Object> convertValues(ArgumentConversionContext<Object> context
Object[] constructorParameters = new Object[constructorArguments.length];
for (int i = 0; i < constructorArguments.length; ++i) {
Argument<?> argument = constructorArguments[i];
String name = argument.getAnnotationMetadata().getValue(Bindable.class, String.class)
String name = argument.getAnnotationMetadata().stringValue(Bindable.class)
.orElse(argument.getName());
constructorParameters[i] = conversionService.convert(values.get(name), ConversionContext.of(argument))
.orElse(null);
@@ -575,12 +575,12 @@ public Optional<ConvertibleMultiValues> convert(
// noinspection unchecked
ArgumentConversionContext<Object> context = (ArgumentConversionContext<Object>) conversionContext;

String format = conversionContext.getAnnotationMetadata().getValue(Format.class, String.class).orElse(null);
String format = conversionContext.getAnnotationMetadata().stringValue(Format.class).orElse(null);
if (format == null) {
return Optional.empty();
}

String name = conversionContext.getAnnotationMetadata().getValue(Bindable.class, String.class)
String name = conversionContext.getAnnotationMetadata().stringValue(Bindable.class)
.orElse(context.getArgument().getName());

MutableConvertibleMultiValuesMap<String> parameters = new MutableConvertibleMultiValuesMap<>();
@@ -775,7 +775,7 @@ private void processValues(ArgumentConversionContext<Object> context,
}

for (BeanProperty<Object, Object> property: beanWrapper.getBeanProperties()) {
String key = property.getValue(Bindable.class, String.class).orElse(property.getName());
String key = property.stringValue(Bindable.class).orElse(property.getName());
ArgumentConversionContext<String> conversionContext =
ConversionContext.STRING.with(property.getAnnotationMetadata());
conversionService.convert(property.get(object), conversionContext).ifPresent(value -> {
4 changes: 3 additions & 1 deletion http-client/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -12,4 +12,6 @@
<appender-ref ref="STDOUT" />
</root>

</configuration>
<!-- <logger name="io.micronaut.http.client" level="trace" />-->

</configuration>
Original file line number Diff line number Diff line change
@@ -31,7 +31,6 @@
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder;
import io.micronaut.inject.annotation.AnnotatedElementValidator;
import io.micronaut.inject.visitor.VisitorContext;
@@ -376,9 +375,7 @@ protected void readAnnotationRawValues(
if (expression instanceof ConstantExpression constantExpression) {
final Object v = constantExpression.getValue();
if (v instanceof String s) {
if (StringUtils.isNotEmpty(s)) {
defaultValues.put(method, new ConstantExpression(v));
}
defaultValues.put(method, new ConstantExpression(s));
} else if (v != null) {
defaultValues.put(method, expression);
}
@@ -597,7 +594,8 @@ private Object convertConstantValue(Object value) {
@Override
protected <K extends Annotation> Optional<AnnotationValue<K>> getAnnotationValues(AnnotatedNode originatingElement, AnnotatedNode member, Class<K> annotationType) {
if (member != null) {
final List<AnnotationNode> anns = member.getAnnotations(ClassHelper.make(annotationType));
ClassNode annotationTypeNode = ClassHelper.make(annotationType);
final List<AnnotationNode> anns = member.getAnnotations(annotationTypeNode);
if (CollectionUtils.isNotEmpty(anns)) {
AnnotationNode ann = anns.get(0);
Map<CharSequence, Object> converted = new LinkedHashMap<>();
@@ -608,6 +606,17 @@ protected <K extends Annotation> Optional<AnnotationValue<K>> getAnnotationValue
AnnotatedNode annotationMember = annotationNode.getMethod(key, new Parameter[0]);
readAnnotationRawValues(originatingElement, annotationType.getName(), annotationMember, key, value, converted);
}
Map<CharSequence, Object> annotationDefaults = getCachedAnnotationDefaults(annotationType.getName(), annotationTypeNode);
if (!annotationDefaults.isEmpty()) {
Iterator<Map.Entry<CharSequence, Object>> i = converted.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<CharSequence, Object> next = i.next();
Object v = annotationDefaults.get(next.getKey());
if (v != null && v.equals(next.getValue())) {
i.remove();
}
}
}
return Optional.of(AnnotationValue.builder(annotationType).members(converted).build());
}
}
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ class MyBean {
beanDefinition.getInjectedMethods()[0].name == 'setMyValue'
def metadata = beanDefinition.getInjectedMethods()[0].getAnnotationMetadata()
metadata.hasAnnotation(Property)
metadata.getValue(Property, "name", String).get() == 'endpoints.my-value'
metadata.getValue(Property, "name", String).get() == 'simple.my-value'
}

void "property path is overriding the existing one without base prefix"() {
@@ -75,7 +75,7 @@ class MyBean {
beanDefinition.getInjectedMethods()[0].name == 'setMyValue'
def metadata = beanDefinition.getInjectedMethods()[0].getAnnotationMetadata()
metadata.hasAnnotation(Property)
metadata.getValue(Property, "name", String).get() == 'endpoints.my-value'
metadata.getValue(Property, "name", String).get() == 'endpoints.simple.my-value'
}

void "property path is overriding the existing one"() {
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ package io.micronaut.inject.visitor

import io.micronaut.ast.groovy.TypeElementVisitorStart
import io.micronaut.ast.transform.test.AbstractBeanDefinitionSpec
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.exceptions.BeanContextException
import io.micronaut.core.annotation.Introspected
import io.micronaut.inject.ast.ClassElement
@@ -1996,6 +1997,56 @@ class MyBean {
returnType.hasAnnotation(Introspected.class)
}

void "test alias for recursion"() {
given:
ClassElement ce = buildClassElement('''\
package test;
import io.micronaut.inject.annotation.*;
import io.micronaut.context.annotation.*
import io.micronaut.test.annotation.MockBean;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
import java.util.List;
import java.lang.Integer;
interface MathService {
Integer compute(Integer num);
}
@Singleton
class MathServiceImpl implements MathService {
@Override
Integer compute(Integer num) {
return num * 4 // should never be called
}
}
@Singleton
class MathInnerServiceSpec {
@Inject
MathService mathService
@MockBean(MathService)
static class MyMock implements MathService {
@Override
Integer compute(Integer num) {
return 50
}
}
}
''')
when:
def replaces = ce.getAnnotation(Replaces)
then:
replaces.stringValue("bean").get() == "test.MathService"
}

void "test how the type annotations from the type are propagated"() {
given:
ClassElement ce = buildClassElement('''\
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ package io.micronaut.visitors

import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
import io.micronaut.annotation.processing.visitor.JavaClassElement
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.exceptions.BeanContextException
import io.micronaut.core.annotation.AnnotationUtil
import io.micronaut.core.annotation.Introspected
@@ -2302,6 +2303,57 @@ class MyBean {
returnType.hasAnnotation(Introspected.class)
}

void "test alias for recursion"() {
given:
ClassElement ce = buildClassElement('''\
package test;
import io.micronaut.inject.annotation.*;
import io.micronaut.context.annotation.*;
import io.micronaut.test.annotation.MockBean;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
import java.util.List;
import java.lang.Integer;
@Singleton
class MathInnerServiceSpec {
@Inject
MathService mathService;
@MockBean(MathService.class)
static class MyMock implements MathService {
@Override
public Integer compute(Integer num) {
return 50;
}
}
}
interface MathService {
Integer compute(Integer num);
}
@Singleton
class MathServiceImpl implements MathService {
@Override
public Integer compute(Integer num) {
return num * 4; // should never be called
}
}
''')
when:
def replaces = ce.getEnclosedElements(ElementQuery.ALL_INNER_CLASSES).get(0).getAnnotation(Replaces)
then:
replaces.stringValue("bean").get() == "test.MathService"
}

void "test how the type annotations from the type are propagated"() {
given:
ClassElement ce = buildClassElement('''\
Original file line number Diff line number Diff line change
@@ -110,7 +110,6 @@
import io.micronaut.inject.QualifiedBeanType;
import io.micronaut.inject.ValidatedBeanDefinition;
import io.micronaut.inject.provider.AbstractProviderDefinition;
import io.micronaut.inject.provider.BeanProviderDefinition;
import io.micronaut.inject.proxy.InterceptedBeanProxy;
import io.micronaut.inject.qualifiers.AnyQualifier;
import io.micronaut.inject.qualifiers.Qualified;
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@
/**
* @return The bean type that this bean replaces
*/
@AliasFor(member = "value")
Class<?> bean() default void.class;

/**
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.micronaut.test.replacesbug

import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.micronaut.test.annotation.MockBean
import jakarta.inject.Singleton
import spock.lang.Specification

import jakarta.inject.Inject

@MicronautTest
class MathInnerServiceSpec extends Specification {

@Inject
MathService mathService

void "should compute use inner mock"() {
when:
def result = mathService.compute(10)

then:
result == 50
}

@MockBean(MathService)
static class MyMock implements MathService {

@Override
Integer compute(Integer num) {
return 50
}
}
}

interface MathService {

Integer compute(Integer num);
}

@Singleton
class MathServiceImpl implements MathService {

@Override
Integer compute(Integer num) {
return num * 4 // should never be called
}
}

0 comments on commit 0d9d9e0

Please sign in to comment.