From 9f1655396ed1c76c6d2e461fbed38dd1bf44562f Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 5 May 2022 13:36:28 +0200 Subject: [PATCH] Allow using AnnotationCreator to represent nested annotations --- .../io/quarkus/gizmo/AnnotationCreator.java | 68 +++++- .../quarkus/gizmo/AnnotationCreatorImpl.java | 5 +- .../io/quarkus/gizmo/AnnotationUtils.java | 25 +- .../io/quarkus/gizmo/AnnotationTestCase.java | 214 ++++++++---------- 4 files changed, 172 insertions(+), 140 deletions(-) diff --git a/src/main/java/io/quarkus/gizmo/AnnotationCreator.java b/src/main/java/io/quarkus/gizmo/AnnotationCreator.java index e6bd3205..3c3f5124 100644 --- a/src/main/java/io/quarkus/gizmo/AnnotationCreator.java +++ b/src/main/java/io/quarkus/gizmo/AnnotationCreator.java @@ -16,8 +16,74 @@ package io.quarkus.gizmo; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public interface AnnotationCreator { + /** + * Returns an {@link AnnotationCreator} for annotation of given {@code annotationType}. + *

+ * Useful for representing nested annotations (see {@link #addValue(String, Object) addValue}). + * + * @param annotationType class name of the annotation to create, must not be {@code null} + * @return an {@code AnnotationCreator}, never {@code null} + */ + static AnnotationCreator of(String annotationType) { + return of(annotationType, RetentionPolicy.RUNTIME); + } - void addValue(String name, Object value); + /** + * Returns an {@link AnnotationCreator} for annotation of given {@code annotationType}. + *

+ * Useful for representing nested annotations (see {@link #addValue(String, Object) addValue}). + * + * @param annotationType type of the annotation to create, must not be {@code null} + * @return an {@code AnnotationCreator}, never {@code null} + */ + static AnnotationCreator of(Class annotationType) { + Retention retention = annotationType.getAnnotation(Retention.class); + return of(annotationType.getName(), retention == null ? RetentionPolicy.SOURCE : retention.value()); + } + + /** + * Returns an {@link AnnotationCreator} for annotation of given {@code annotationType} + * which has given {@code retentionPolicy}. + *

+ * Useful for representing nested annotations (see {@link #addValue(String, Object) addValue}). + * + * @param annotationType class name of the annotation to create, must not be {@code null} + * @param retentionPolicy retention policy of the annotation type + * @return an {@code AnnotationCreator}, never {@code null} + */ + static AnnotationCreator of(String annotationType, RetentionPolicy retentionPolicy) { + return new AnnotationCreatorImpl(annotationType, retentionPolicy); + } + /** + * Same as {@link #addValue(String, Object)}, but returns {@code this} to allow fluent usage. + */ + default AnnotationCreator add(String name, Object value) { + addValue(name, value); + return this; + } + + /** + * Add a new annotation element with the given {@code name} and {@code value}. The name may be any permissible + * annotation element name (for example, {@code toString} or {@code annotationType} are invalid). The value may be: + *

+ * In addition to the types listed above, the value may also be a Jandex + * {@link org.jboss.jandex.AnnotationValue AnnotationValue}. + * + * @param name name of the annotation element to add + * @param value value of the annotation element to add + */ + void addValue(String name, Object value); } diff --git a/src/main/java/io/quarkus/gizmo/AnnotationCreatorImpl.java b/src/main/java/io/quarkus/gizmo/AnnotationCreatorImpl.java index 455915ff..372232b9 100644 --- a/src/main/java/io/quarkus/gizmo/AnnotationCreatorImpl.java +++ b/src/main/java/io/quarkus/gizmo/AnnotationCreatorImpl.java @@ -16,9 +16,7 @@ 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; @@ -39,8 +37,7 @@ 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 + // TODO: If value is a nested annotation, check for nonexistent or missing elements? values.put(name, value); } diff --git a/src/main/java/io/quarkus/gizmo/AnnotationUtils.java b/src/main/java/io/quarkus/gizmo/AnnotationUtils.java index 3b5d2674..55e1c763 100644 --- a/src/main/java/io/quarkus/gizmo/AnnotationUtils.java +++ b/src/main/java/io/quarkus/gizmo/AnnotationUtils.java @@ -1,13 +1,11 @@ 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 { @@ -28,24 +26,15 @@ static void visitAnnotationValue(AnnotationVisitor visitor, String key, Object v visitAnnotationValue(nestedVisitor, annotationValue.name(), annotationValue); } nestedVisitor.visitEnd(); - } else if (value instanceof Map) { - Map annotationValueMap = (Map) value; - if (!annotationValueMap.containsKey("annotationType")) { - throw new IllegalStateException("The annotationValueMap (" + annotationValueMap + ") does not have entry for " + - "required value \"annotationType\"."); + } else if (value instanceof AnnotationCreator) { + if (!(value instanceof AnnotationCreatorImpl)) { + throw new IllegalArgumentException("Custom implementations of AnnotationCreator are not accepted"); } - Class annotationType = (Class) annotationValueMap.get("annotationType"); - String descriptor = Type.getDescriptor(annotationType); + AnnotationCreatorImpl nested = (AnnotationCreatorImpl) value; + String descriptor = DescriptorUtils.objectToDescriptor(nested.getAnnotationType()); AnnotationVisitor nestedVisitor = visitor.visitAnnotation(key, descriptor); - for (Map.Entry annotationInstanceValueEntry : annotationValueMap.entrySet()) { - final String parameterName = annotationInstanceValueEntry.getKey(); - final Object parameterValue = annotationInstanceValueEntry.getValue(); - - if (parameterName.equals("annotationType")) { - continue; - } - - visitAnnotationValue(nestedVisitor, parameterName, parameterValue); + for (Map.Entry member : nested.getValues().entrySet()) { + visitAnnotationValue(nestedVisitor, member.getKey(), member.getValue()); } nestedVisitor.visitEnd(); } else if (value instanceof AnnotationValue) { diff --git a/src/test/java/io/quarkus/gizmo/AnnotationTestCase.java b/src/test/java/io/quarkus/gizmo/AnnotationTestCase.java index 2e42e9ec..b713d178 100644 --- a/src/test/java/io/quarkus/gizmo/AnnotationTestCase.java +++ b/src/test/java/io/quarkus/gizmo/AnnotationTestCase.java @@ -53,7 +53,7 @@ public void testClassAnnotationWithAnnotationValueArray() throws ClassNotFoundEx } @Test - public void testClassFullAnnotation() throws ClassNotFoundException { + public void testClassFullAnnotationUsingJandex() throws ClassNotFoundException { TestClassLoader cl = new TestClassLoader(getClass().getClassLoader()); try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className("com.MyTest").build()) { addFullAnnotationUsingJandex(creator); @@ -65,10 +65,10 @@ public void testClassFullAnnotation() throws ClassNotFoundException { } @Test - public void testClassFullAnnotationWithoutJandex() throws ClassNotFoundException { + public void testClassFullAnnotationUsingNestedCreator() throws ClassNotFoundException { TestClassLoader cl = new TestClassLoader(getClass().getClassLoader()); try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className("com.MyTest").build()) { - addFullAnnotationWithoutJandex(creator.addAnnotation(MyFullAnnotation.class)); + addFullAnnotationUsingNestedCreator(creator); } MyFullAnnotation annotation = cl.loadClass("com.MyTest") @@ -143,7 +143,7 @@ public void testMethodAnnotationWithAnnotationValueArray() throws ClassNotFoundE } @Test - public void testMethodFullAnnotation() throws ClassNotFoundException, NoSuchMethodException { + public void testMethodFullAnnotationUsingJandex() 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)) { @@ -159,11 +159,11 @@ public void testMethodFullAnnotation() throws ClassNotFoundException, NoSuchMeth } @Test - public void testMethodFullAnnotationWithoutJandex() throws ClassNotFoundException, NoSuchMethodException { + public void testMethodFullAnnotationUsingNestedCreator() 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)); + addFullAnnotationUsingNestedCreator(methodCreator); methodCreator.returnValue(null); } } @@ -227,7 +227,7 @@ public void testMethodParamAnnotationWithAnnotationValueArray() throws ClassNotF } @Test - public void testMethodParamFullAnnotation() throws ClassNotFoundException, NoSuchMethodException { + public void testMethodParamFullAnnotationUsingJandex() 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, String.class)) { @@ -243,6 +243,23 @@ public void testMethodParamFullAnnotation() throws ClassNotFoundException, NoSuc verifyFullAnnotation(annotation); } + @Test + public void testMethodParamFullAnnotationUsingNestedCreator() 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, String.class)) { + addFullAnnotationUsingNestedCreator(methodCreator.getParameterAnnotations(0)); + methodCreator.returnValue(null); + } + } + + MyFullAnnotation annotation = cl.loadClass("com.MyTest") + .getMethod("test", String.class) + .getParameters()[0] + .getAnnotation(MyFullAnnotation.class); + verifyFullAnnotation(annotation); + } + @Test public void testFieldAnnotationWithString() throws ClassNotFoundException, NoSuchFieldException { TestClassLoader cl = new TestClassLoader(getClass().getClassLoader()); @@ -287,7 +304,7 @@ public void testFieldAnnotationWithAnnotationValueArray() throws ClassNotFoundEx } @Test - public void testFieldFullAnnotation() throws ClassNotFoundException, NoSuchFieldException { + public void testFieldFullAnnotationUsingJandex() 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); @@ -301,11 +318,11 @@ public void testFieldFullAnnotation() throws ClassNotFoundException, NoSuchField } @Test - public void testFieldFullAnnotationWithoutJandex() throws ClassNotFoundException, NoSuchFieldException { + public void testFieldFullAnnotationUsingNestedCreator() 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)); + addFullAnnotationUsingNestedCreator(fieldCreator); } MyFullAnnotation annotation = cl.loadClass("com.MyTest") @@ -477,114 +494,77 @@ 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 addFullAnnotationUsingNestedCreator(AnnotatedElement element) { + AnnotationCreator creator = element.addAnnotation(MyFullAnnotation.class); + + creator.addValue("bool", true); + creator.addValue("ch", 'c'); + creator.addValue("b", (byte) 42); + creator.addValue("s", (short) 42); + creator.addValue("i", 42); + creator.addValue("l", 42L); + creator.addValue("f", 42.0F); + creator.addValue("d", 42.0); + creator.addValue("str", "str"); + creator.addValue("enumerated", MyEnum.YES); + creator.addValue("cls", MyInterface.class); + creator.addValue("nested", AnnotationCreator.of(MyNestedAnnotation.class) + .add("cls", MyInterface.class) + .add("innerNested", AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested") + .add("enumVal", MyEnum.YES)) + .add("clsArray", new Class[] {MyInterface.class, boolean[].class}) + .add("innerNestedArray", new AnnotationCreator[] { + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested1") + .add("enumVal", MyEnum.YES), + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested2") + .add("enumVal", MyEnum.NO) + }) + ); - 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) - }) - }); + creator.addValue("boolArray", new boolean[] {true, false}); + creator.addValue("chArray", new char[] {'c', 'd'}); + creator.addValue("bArray", new byte[] {(byte) 42, (byte) 43}); + creator.addValue("sArray", new short[] {(short) 42, (short) 43}); + creator.addValue("iArray", new int[] {42, 43}); + creator.addValue("lArray", new long[] {42L, 43L}); + creator.addValue("fArray", new float[] {42.0F, 43.0F}); + creator.addValue("dArray", new double[] {42.0, 43.0}); + creator.addValue("strArray", new String[] {"foo", "bar"}); + creator.addValue("enumeratedArray", new MyEnum[] {MyEnum.YES, MyEnum.NO}); + creator.addValue("clsArray", new Class[] {MyInterface.class, char[][].class}); + creator.addValue("nestedArray", new AnnotationCreator[] { + AnnotationCreator.of(MyNestedAnnotation.class) + .add("cls", MyInterface.class) + .add("innerNested", AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested1") + .add("enumVal", MyEnum.YES)) + .add("clsArray", new Class[] {MyInterface.class, boolean[].class}) + .add("innerNestedArray", new AnnotationCreator[] { + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested11") + .add("enumVal", MyEnum.YES), + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested12") + .add("enumVal", MyEnum.NO) + }), + AnnotationCreator.of(MyNestedAnnotation.class) + .add("cls", MyInterface.class) + .add("innerNested", AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested2") + .add("enumVal", MyEnum.YES)) + .add("clsArray", new Class[] {MyInterface.class, boolean[].class}) + .add("innerNestedArray", new AnnotationCreator[] { + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested21") + .add("enumVal", MyEnum.YES), + AnnotationCreator.of(MyAnnotation.class) + .add("value", "nested22") + .add("enumVal", MyEnum.NO) + }), + }); } private void verifyFullAnnotation(MyFullAnnotation annotation) {