Skip to content

Commit

Permalink
Merge pull request #9576 from mgorniew/private_default
Browse files Browse the repository at this point in the history
Fix #9260: Shared Annotations Literals with default private Class fields
  • Loading branch information
manovotn authored Jun 9, 2020
2 parents e49d819 + 3b91e6e commit 6eede7f
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.quarkus.arc.test.annotations;

import java.util.function.Supplier;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.test.annotations.prv.WithPrivateDefault;
import io.quarkus.test.QuarkusUnitTest;

public class SharedLiteralWithPrivateDefaultTest {

@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(WithPrivateDefault.TO_IMPORT)
.addClasses(PrivateDefaultBean.class, MySupplier.class, OtherSupplier.class));

@Inject
PrivateDefaultBean privateDefaultBean;

private WithPrivateDefault.HasPrivateDefault findAnnotation(InjectedField field) {
return (WithPrivateDefault.HasPrivateDefault) field.injectionPoint.getAnnotated().getAnnotations()
.stream().filter(ann -> ann.annotationType().equals(WithPrivateDefault.HasPrivateDefault.class))
.findFirst().orElseThrow(() -> new IllegalStateException("Didn't found HasPrivateDefault annotation"));
}

@Test
public void testLoadBeanAnnotatedWithPrivateDefault() {
WithPrivateDefault.HasPrivateDefault defaultAnnotation = findAnnotation(privateDefaultBean.usingDefault);
Assertions.assertEquals(WithPrivateDefault.PRIVATE_STRING_SUPPLIER, defaultAnnotation.privateDefault());
Assertions.assertArrayEquals(
new Class<?>[] { WithPrivateDefault.PRIVATE_STRING_SUPPLIER, WithPrivateDefault.PublicStringSupplier.class },
defaultAnnotation.privateDefaultArray());

WithPrivateDefault.HasPrivateDefault overwriteAnnotation = findAnnotation(privateDefaultBean.overwriteDefault);
Assertions.assertEquals(MySupplier.class, overwriteAnnotation.privateDefault());
Assertions.assertArrayEquals(new Class<?>[] { MySupplier.class, OtherSupplier.class },
overwriteAnnotation.privateDefaultArray());
}

@Dependent
public static class PrivateDefaultBean {

@WithPrivateDefault.HasPrivateDefault
@Inject
InjectedField usingDefault;

@WithPrivateDefault.HasPrivateDefault(privateDefault = MySupplier.class, privateDefaultArray = { MySupplier.class,
OtherSupplier.class })
@Inject
InjectedField overwriteDefault;
}

public static class MySupplier implements Supplier<String> {

@Override
public String get() {
return "MySupplier";
}
}

public static class OtherSupplier implements Supplier<String> {

@Override
public String get() {
return "OtherSupplier";
}
}

@Dependent
public static class InjectedField {
@Inject
InjectionPoint injectionPoint;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.arc.test.annotations.prv;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Supplier;

public class WithPrivateDefault {

public static final Class<?> PRIVATE_STRING_SUPPLIER = PrivateStringSupplier.class;

public static final Class<?>[] TO_IMPORT = { WithPrivateDefault.class,
HasPrivateDefault.class, PrivateStringSupplier.class, PublicStringSupplier.class };

@Retention(RetentionPolicy.RUNTIME)
public @interface HasPrivateDefault {
Class<? extends Supplier<String>> privateDefault() default PrivateStringSupplier.class;

Class<? extends Supplier<String>>[] privateDefaultArray() default { PrivateStringSupplier.class,
PublicStringSupplier.class };
}

private static class PrivateStringSupplier implements Supplier<String> {

@Override
public String get() {
return "PrivateStringSupplier";
}
}

public static class PublicStringSupplier implements Supplier<String> {

@Override
public String get() {
return "PublicStringSupplier";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;

import io.quarkus.arc.impl.ComputingCache;
import io.quarkus.arc.processor.AnnotationLiteralProcessor.Key;
Expand All @@ -11,6 +12,7 @@
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.FieldCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
Expand All @@ -36,7 +38,6 @@
import org.jboss.logging.Logger;

/**
*
* @author Martin Kouba
*/
public class AnnotationLiteralGenerator extends AbstractGenerator {
Expand All @@ -55,7 +56,6 @@ public class AnnotationLiteralGenerator extends AbstractGenerator {
}

/**
*
* @param annotationLiterals
* @param beanDeployment
* @param existingClasses
Expand Down Expand Up @@ -89,6 +89,8 @@ static void createSharedAnnotationLiteral(ClassOutput classOutput, Key key, Lite
literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray());
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationLiteral.class), constructor.getThis());

List<MethodInfo> defaultOfClassType = new ArrayList<>();

for (ListIterator<MethodInfo> iterator = literal.constructorParams.listIterator(); iterator.hasNext();) {
MethodInfo param = iterator.next();
String returnType = param.returnType().name().toString();
Expand All @@ -102,8 +104,13 @@ static void createSharedAnnotationLiteral(ClassOutput classOutput, Key key, Lite
MethodCreator value = annotationLiteral.getMethodCreator(param.name(), returnType).setModifiers(ACC_PUBLIC);
value.returnValue(value.readInstanceField(
FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), value.getThis()));

if (param.defaultValue() != null && hasClassOrClassArrayReturnType(param)) {
defaultOfClassType.add(param);
}
}
constructor.returnValue(null);
generateStaticFieldsWithDefaultValues(annotationLiteral, defaultOfClassType);

annotationLiteral.close();
LOGGER.debugf("Shared annotation literal generated: %s", literal.className);
Expand All @@ -125,10 +132,15 @@ static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotatio
.superClass(AnnotationLiteral.class)
.interfaces(annotationClass.name().toString()).signature(signature).build();

List<MethodInfo> defaultOfClassType = new ArrayList<>();

for (MethodInfo method : annotationClass.methods()) {
if (method.name().equals(Methods.CLINIT) || method.name().equals(Methods.INIT)) {
continue;
}
if (method.defaultValue() != null && hasClassOrClassArrayReturnType(method)) {
defaultOfClassType.add(method);
}
MethodCreator valueMethod = annotationLiteral.getMethodCreator(MethodDescriptor.of(method));
AnnotationValue value = annotationValues.get(method.name());
if (value == null) {
Expand All @@ -139,13 +151,60 @@ static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotatio
"Value is not set for %s.%s(). Most probably an older version of Jandex was used to index an application dependency. Make sure that Jandex 2.1+ is used.",
method.declaringClass().name(), method.name()));
}
valueMethod.returnValue(loadValue(valueMethod, value, annotationClass, method));
valueMethod.returnValue(loadValue(literalName, valueMethod, value, annotationClass, method));
}
generateStaticFieldsWithDefaultValues(annotationLiteral, defaultOfClassType);
annotationLiteral.close();
LOGGER.debugf("Annotation literal generated: %s", literalName);
}

static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value, ClassInfo annotationClass,
private static boolean hasClassOrClassArrayReturnType(MethodInfo method) {
return DotNames.CLASS.equals(method.returnType().name())
|| (method.returnType().kind() == Type.Kind.ARRAY
&& DotNames.CLASS.equals(method.returnType().asArrayType().component().name()));
}

private static void generateStaticFieldsWithDefaultValues(ClassCreator annotationLiteral,
List<MethodInfo> defaultOfClassType) {
if (defaultOfClassType.isEmpty()) {
return;
}

MethodCreator staticConstructor = annotationLiteral.getMethodCreator(Methods.CLINIT, void.class);
staticConstructor.setModifiers(ACC_STATIC);

for (MethodInfo method : defaultOfClassType) {
Type returnType = method.returnType();
String returnTypeName = returnType.name().toString();
AnnotationValue defaultValue = method.defaultValue();

FieldCreator fieldCreator = annotationLiteral.getFieldCreator(defaultValueStaticFieldName(method), returnTypeName);
fieldCreator.setModifiers(ACC_PUBLIC | ACC_STATIC | ACC_FINAL);

if (defaultValue.kind() == AnnotationValue.Kind.ARRAY) {
Type[] clazzArray = defaultValue.asClassArray();
ResultHandle array = staticConstructor.newArray(returnTypeName, clazzArray.length);
for (int i = 0; i < clazzArray.length; ++i) {
staticConstructor.writeArrayValue(array, staticConstructor.load(i),
staticConstructor.loadClass(clazzArray[i].name().toString()));
}
staticConstructor.writeStaticField(fieldCreator.getFieldDescriptor(), array);
} else {
staticConstructor.writeStaticField(fieldCreator.getFieldDescriptor(),
staticConstructor.loadClass(defaultValue.asClass().name().toString()));

}
}

staticConstructor.returnValue(null);
}

private static String defaultValueStaticFieldName(MethodInfo methodInfo) {
return methodInfo.name() + "_default_value";
}

static ResultHandle loadValue(String literalClassName,
BytecodeCreator valueMethod, AnnotationValue value, ClassInfo annotationClass,
MethodInfo method) {
ResultHandle retValue;
switch (value.kind()) {
Expand Down Expand Up @@ -177,10 +236,16 @@ static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value
retValue = valueMethod.load(value.asChar());
break;
case CLASS:
retValue = valueMethod.loadClass(value.asClass().toString());
if (value.equals(method.defaultValue())) {
retValue = valueMethod.readStaticField(
FieldDescriptor.of(literalClassName, defaultValueStaticFieldName(method),
method.returnType().name().toString()));
} else {
retValue = valueMethod.loadClass(value.asClass().toString());
}
break;
case ARRAY:
retValue = arrayValue(value, valueMethod, method, annotationClass);
retValue = arrayValue(literalClassName, value, valueMethod, method, annotationClass);
break;
case ENUM:
retValue = valueMethod
Expand All @@ -194,16 +259,23 @@ static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value
return retValue;
}

static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMethod, MethodInfo method,
static ResultHandle arrayValue(String literalClassName,
AnnotationValue value, BytecodeCreator valueMethod, MethodInfo method,
ClassInfo annotationClass) {
ResultHandle retValue;
switch (value.componentKind()) {
case CLASS:
Type[] classArray = value.asClassArray();
retValue = valueMethod.newArray(componentType(method), valueMethod.load(classArray.length));
for (int i = 0; i < classArray.length; i++) {
valueMethod.writeArrayValue(retValue, i, valueMethod.loadClass(classArray[i].name()
.toString()));
if (value.equals(method.defaultValue())) {
retValue = valueMethod.readStaticField(
FieldDescriptor.of(literalClassName, defaultValueStaticFieldName(method),
method.returnType().name().toString()));
} else {
Type[] classArray = value.asClassArray();
retValue = valueMethod.newArray(componentType(method), valueMethod.load(classArray.length));
for (int i = 0; i < classArray.length; i++) {
valueMethod.writeArrayValue(retValue, i, valueMethod.loadClass(classArray[i].name()
.toString()));
}
}
break;
case STRING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, C
"Value is not set for %s.%s(). Most probably an older version of Jandex was used to index an application dependency. Make sure that Jandex 2.1+ is used.",
method.declaringClass().name(), method.name()));
}
ResultHandle retValue = AnnotationLiteralGenerator.loadValue(bytecode, value, annotationClass, method);
ResultHandle retValue = AnnotationLiteralGenerator.loadValue(literal.className, bytecode, value,
annotationClass, method);
constructorParams[iterator.previousIndex()] = retValue;
}
return bytecode
Expand Down

0 comments on commit 6eede7f

Please sign in to comment.