Skip to content

Commit

Permalink
Merge pull request #107 from Christopher-Chianelli/add-support-for-ma…
Browse files Browse the repository at this point in the history
…p-as-annotation-instance

Allow using Maps to specify nested annotations
  • Loading branch information
stuartwdouglas authored May 1, 2022
2 parents 168fc65 + fe61e87 commit b85aae8
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 4 deletions.
1 change: 0 additions & 1 deletion src/main/java/io/quarkus/gizmo/AnnotationCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package io.quarkus.gizmo;

//TODO: support for nested annotations (currently only Jandex types can be used)
public interface AnnotationCreator {

void addValue(String name, Object value);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/io/quarkus/gizmo/AnnotationCreatorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package io.quarkus.gizmo;

import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

Expand All @@ -36,6 +38,9 @@ class AnnotationCreatorImpl implements AnnotationCreator {

@Override
public void addValue(String name, Object value) {
// TODO: Maybe typecheck value to ensure it matches the corresponding element type?
// TODO: If value is a Map, check if all its keys are elements for the annotation, and there are no
// missing required elements
values.put(name, value);
}

Expand Down
32 changes: 29 additions & 3 deletions src/main/java/io/quarkus/gizmo/AnnotationUtils.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package io.quarkus.gizmo;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Map;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Type;

final class AnnotationUtils {

static void visitAnnotationValue(AnnotationVisitor visitor, String key, Object value) {
if (value.getClass().isArray()) {
AnnotationVisitor arrayVisitor = visitor.visitArray(key);
for (Object arrayValue : (Object[]) value) {
// Default key is 'value'. It can be changed by using AnnotationValue type.
visitAnnotationValue(arrayVisitor, "value", arrayValue);
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
// Default key is 'value'. It can be changed by using AnnotationValue type
visitAnnotationValue(arrayVisitor, "value", Array.get(value, i));
}
arrayVisitor.visitEnd();
} else if (value instanceof AnnotationInstance) {
Expand All @@ -22,6 +28,26 @@ static void visitAnnotationValue(AnnotationVisitor visitor, String key, Object v
visitAnnotationValue(nestedVisitor, annotationValue.name(), annotationValue);
}
nestedVisitor.visitEnd();
} else if (value instanceof Map) {
Map<String, Object> annotationValueMap = (Map<String, Object>) value;
if (!annotationValueMap.containsKey("annotationType")) {
throw new IllegalStateException("The annotationValueMap (" + annotationValueMap + ") does not have entry for " +
"required value \"annotationType\".");
}
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) annotationValueMap.get("annotationType");
String descriptor = Type.getDescriptor(annotationType);
AnnotationVisitor nestedVisitor = visitor.visitAnnotation(key, descriptor);
for (Map.Entry<String, Object> annotationInstanceValueEntry : annotationValueMap.entrySet()) {
final String parameterName = annotationInstanceValueEntry.getKey();
final Object parameterValue = annotationInstanceValueEntry.getValue();

if (parameterName.equals("annotationType")) {
continue;
}

visitAnnotationValue(nestedVisitor, parameterName, parameterValue);
}
nestedVisitor.visitEnd();
} else if (value instanceof AnnotationValue) {
AnnotationValue annotationValue = (AnnotationValue) value;
if (annotationValue.kind() == AnnotationValue.Kind.NESTED) {
Expand Down
156 changes: 156 additions & 0 deletions src/test/java/io/quarkus/gizmo/AnnotationTestCase.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package io.quarkus.gizmo;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ArrayType;
Expand Down Expand Up @@ -60,6 +64,18 @@ public void testClassFullAnnotation() throws ClassNotFoundException {
verifyFullAnnotation(annotation);
}

@Test
public void testClassFullAnnotationWithoutJandex() throws ClassNotFoundException {
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className("com.MyTest").build()) {
addFullAnnotationWithoutJandex(creator.addAnnotation(MyFullAnnotation.class));
}

MyFullAnnotation annotation = cl.loadClass("com.MyTest")
.getAnnotation(MyFullAnnotation.class);
verifyFullAnnotation(annotation);
}

@Test
public void testMethodAnnotationWithString() throws ClassNotFoundException, NoSuchMethodException {
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
Expand Down Expand Up @@ -142,6 +158,22 @@ public void testMethodFullAnnotation() throws ClassNotFoundException, NoSuchMeth
verifyFullAnnotation(annotation);
}

@Test
public void testMethodFullAnnotationWithoutJandex() throws ClassNotFoundException, NoSuchMethodException {
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className("com.MyTest").build()) {
try (MethodCreator methodCreator = creator.getMethodCreator("test", void.class)) {
addFullAnnotationWithoutJandex(methodCreator.addAnnotation(MyFullAnnotation.class));
methodCreator.returnValue(null);
}
}

MyFullAnnotation annotation = cl.loadClass("com.MyTest")
.getMethod("test")
.getAnnotation(MyFullAnnotation.class);
verifyFullAnnotation(annotation);
}

@Test
public void testMethodParamAnnotationWithString() throws ClassNotFoundException, NoSuchMethodException {
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
Expand Down Expand Up @@ -268,6 +300,20 @@ public void testFieldFullAnnotation() throws ClassNotFoundException, NoSuchField
verifyFullAnnotation(annotation);
}

@Test
public void testFieldFullAnnotationWithoutJandex() throws ClassNotFoundException, NoSuchFieldException {
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className("com.MyTest").build()) {
FieldCreator fieldCreator = creator.getFieldCreator("test", String.class);
addFullAnnotationWithoutJandex(fieldCreator.addAnnotation(MyFullAnnotation.class));
}

MyFullAnnotation annotation = cl.loadClass("com.MyTest")
.getDeclaredField("test")
.getAnnotation(MyFullAnnotation.class);
verifyFullAnnotation(annotation);
}

private void addAnnotationWithString(AnnotatedElement element) {
AnnotationCreator annotationCreator = element.addAnnotation(MyAnnotation.class);
annotationCreator.addValue("value", "test");
Expand Down Expand Up @@ -431,6 +477,116 @@ private void addFullAnnotationUsingJandex(AnnotatedElement element) {
));
}

// Substitute for Map.of, which is only available in Java 11+
@SuppressWarnings({"rawtypes", "unchecked"})
private static Map mapOf(Object... entries) {
if (entries.length % 2 != 0) {
throw new IllegalArgumentException("entries (" + Arrays.toString(entries) + ") has odd length (" + entries.length +
"), thus cannot be put into key/value pairs.");
}
Map out = new HashMap();
for (int i = 0; i < entries.length; i += 2) {
Object key = entries[i];
Object value = entries[i+1];
out.put(key, value);
}
return out;
}

private void addFullAnnotationWithoutJandex(AnnotationCreator annotationCreator) {
annotationCreator.addValue("bool", true);
annotationCreator.addValue("ch", 'c');
annotationCreator.addValue("b", (byte) 42);
annotationCreator.addValue("s", (short) 42);
annotationCreator.addValue("i", 42);
annotationCreator.addValue("l", 42L);
annotationCreator.addValue("f", 42.0F);
annotationCreator.addValue("d", 42.0);
annotationCreator.addValue("str", "str");
annotationCreator.addValue("enumerated", MyEnum.YES);
annotationCreator.addValue("cls", MyInterface.class);

annotationCreator.addValue("nested",
mapOf("annotationType", MyNestedAnnotation.class,
"bool", true,
"ch", 'c',
"b", (byte) 42,
"s", (short) 42,
"i", 42,
"l", 42L,
"f", 42.0F,
"d", 42.0,
"str", "str",
"enumerated", MyEnum.YES,
"cls", MyInterface.class,
"innerNested", mapOf("annotationType", MyAnnotation.class,
"value", "nested",
"enumVal", MyEnum.YES),
"clsArray", new Class[]{
MyInterface.class,
boolean[].class
},
"innerNestedArray", new Map[]{
mapOf("annotationType", MyAnnotation.class,
"value", "nested1",
"enumVal", MyEnum.YES
),
mapOf("annotationType", MyAnnotation.class,
"value", "nested2",
"enumVal", MyEnum.NO
)
}));
annotationCreator.addValue("boolArray", new boolean[] {true, false});
annotationCreator.addValue("chArray", new char[] {'c', 'd'});
annotationCreator.addValue("bArray", new byte[] {(byte) 42, (byte) 43});
annotationCreator.addValue("sArray", new short[] {(short) 42, (short) 43});
annotationCreator.addValue("iArray", new int[] {42, 43});
annotationCreator.addValue("lArray", new long[] {42L, 43L});
annotationCreator.addValue("fArray", new float[] {42.0F, 43.0F});
annotationCreator.addValue("dArray", new double[] {42.0, 43.0});
annotationCreator.addValue("strArray", new String[] {"foo", "bar"});
annotationCreator.addValue("enumeratedArray", new MyEnum[] {MyEnum.YES, MyEnum.NO});
annotationCreator.addValue("clsArray", new Class[] {MyInterface.class, char[][].class});
annotationCreator.addValue("nestedArray", new Map[]{
mapOf("annotationType", MyNestedAnnotation.class,
"cls", MyInterface.class,
"innerNested", mapOf("annotationType", MyAnnotation.class,
"value", "nested1",
"enumVal", MyEnum.YES),
"clsArray", new Class[]{
MyInterface.class,
boolean[].class
},
"innerNestedArray", new Map[]{
mapOf("annotationType", MyAnnotation.class,
"value", "nested11",
"enumVal", MyEnum.YES
),
mapOf("annotationType", MyAnnotation.class,
"value", "nested12",
"enumVal", MyEnum.NO
)
}),
mapOf("annotationType", MyNestedAnnotation.class,
"cls", MyInterface.class,
"innerNested", mapOf("annotationType", MyAnnotation.class,
"value", "nested2",
"enumVal", MyEnum.YES),
"clsArray", new Class[]{
MyInterface.class,
boolean[].class,
},
"innerNestedArray", new Map[]{
mapOf("annotationType", MyAnnotation.class,
"value", "nested21",
"enumVal",MyEnum.YES),
mapOf("annotationType", MyAnnotation.class,
"value", "nested22",
"enumVal",MyEnum.NO)
})
});
}

private void verifyFullAnnotation(MyFullAnnotation annotation) {
Assert.assertEquals(true, annotation.bool());
Assert.assertEquals('c', annotation.ch());
Expand Down

0 comments on commit b85aae8

Please sign in to comment.