From 28f03eb2877cfb369cf25e54d7f5701d16579915 Mon Sep 17 00:00:00 2001 From: Hari Nair Date: Sun, 2 Oct 2022 19:09:59 +0000 Subject: [PATCH] Resolve "inherit annotations values" --- README.md | 2 +- .../main/java/jackfruit/demo/DemoClass.java | 13 +- .../java/jackfruit/demo/DemoSuperClass.java | 6 + .../jackfruit/demo/DemoSuperSuperClass.java | 2 + .../jackfruit/processor/ConfigProcessor.java | 189 ++++++++++-------- 5 files changed, 116 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 25718b2..5a7ea12 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ The `@Comment` annotation specifies the comment that appears in the configuratio ### DefaultValue -The `@DefaultValue` annotation is a String used to initialize the parameter. This is a required annotation. Strings and primitives (and their corresponding wrapper types) are read natively. Other objects will need to use the `@ParserClass` annotation to specify a class which implements the `jackfruit.annotations.Parser` interface to convert the object to and from a String. +The `@DefaultValue` annotation is a String used to initialize the parameter. This is a required annotation. If it is absent no other annotations on this method will be processed. Strings and primitives (and their corresponding wrapper types) are read natively. Other objects will need to use the `@ParserClass` annotation to specify a class which implements the `jackfruit.annotations.Parser` interface to convert the object to and from a String. ### Key diff --git a/demo/src/main/java/jackfruit/demo/DemoClass.java b/demo/src/main/java/jackfruit/demo/DemoClass.java index dce718a..d8125c8 100644 --- a/demo/src/main/java/jackfruit/demo/DemoClass.java +++ b/demo/src/main/java/jackfruit/demo/DemoClass.java @@ -4,14 +4,12 @@ import java.util.List; import jackfruit.annotations.Comment; import jackfruit.annotations.DefaultValue; import jackfruit.annotations.Jackfruit; -import jackfruit.annotations.Key; import jackfruit.annotations.ParserClass; @Jackfruit(prefix = "prefix") public abstract class DemoClass extends DemoSuperClass { - @Key("key") - @Comment("This method overrides one defined in DemoSuperClass") + @Comment("This method's key name is inherited from DemoSuperSuperClass") @DefaultValue("1") @Override public abstract int intMethod(); @@ -29,15 +27,14 @@ public abstract class DemoClass extends DemoSuperClass { @DefaultValue("Default String") public abstract String StringMethod(); - @Comment("This string is serialized into an object\n\tThis comment contains a newline character, and this line starts with a tab.") - @DefaultValue("serialized string") - @ParserClass(SomeRandomClassParser.class) - public abstract SomeRandomClass randomClass(); - @Comment("List of Doubles") @DefaultValue("0. 5.34 17") public abstract List doubles(); + @DefaultValue("set in DemoClass, parser inherited from DemoSuperClass") + @Override + public abstract SomeRandomClass randomClass(); + @Comment("List of RandomClass") @DefaultValue(""" obj1 diff --git a/demo/src/main/java/jackfruit/demo/DemoSuperClass.java b/demo/src/main/java/jackfruit/demo/DemoSuperClass.java index 109c230..35535e4 100644 --- a/demo/src/main/java/jackfruit/demo/DemoSuperClass.java +++ b/demo/src/main/java/jackfruit/demo/DemoSuperClass.java @@ -3,6 +3,7 @@ package jackfruit.demo; import jackfruit.annotations.Comment; import jackfruit.annotations.DefaultValue; import jackfruit.annotations.Jackfruit; +import jackfruit.annotations.ParserClass; // prefix can be superseded by inherited classes @Jackfruit @@ -17,4 +18,9 @@ public abstract class DemoSuperClass extends DemoSuperSuperClass { @DefaultValue("2") @Override public abstract int intMethod(); + + @Comment("This string is serialized into an object\n\tThis comment contains a newline character, and this line starts with a tab.") + @DefaultValue("serialized string") + @ParserClass(SomeRandomClassParser.class) + public abstract SomeRandomClass randomClass(); } diff --git a/demo/src/main/java/jackfruit/demo/DemoSuperSuperClass.java b/demo/src/main/java/jackfruit/demo/DemoSuperSuperClass.java index ebd53a5..04b9d17 100644 --- a/demo/src/main/java/jackfruit/demo/DemoSuperSuperClass.java +++ b/demo/src/main/java/jackfruit/demo/DemoSuperSuperClass.java @@ -3,6 +3,7 @@ package jackfruit.demo; import jackfruit.annotations.Comment; import jackfruit.annotations.DefaultValue; import jackfruit.annotations.Jackfruit; +import jackfruit.annotations.Key; @Jackfruit public abstract class DemoSuperSuperClass { @@ -17,5 +18,6 @@ public abstract class DemoSuperSuperClass { @Comment("from DemoSuperSuperClass") @DefaultValue("3") + @Key("key") public abstract int intMethod(); } diff --git a/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java b/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java index 8cdd969..554d4f9 100644 --- a/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java +++ b/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java @@ -22,6 +22,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -66,16 +67,19 @@ import jackfruit.annotations.ParserClass; @AutoService(Processor.class) public class ConfigProcessor extends AbstractProcessor { + private List> supportedMethodAnnotations; + private Messager messager; + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - List> supportedMethodAnnotations = new ArrayList<>(); + supportedMethodAnnotations = new ArrayList<>(); supportedMethodAnnotations.add(Comment.class); supportedMethodAnnotations.add(DefaultValue.class); supportedMethodAnnotations.add(Key.class); supportedMethodAnnotations.add(ParserClass.class); - Messager messager = processingEnv.getMessager(); + messager = processingEnv.getMessager(); // find interfaces or abstract classes with the Jackfruit annotation List annotatedElements = roundEnv.getElementsAnnotatedWith(Jackfruit.class).stream() @@ -116,7 +120,7 @@ public class ConfigProcessor extends AbstractProcessor { */ // this contains a hierarchy of parent classes - List superClasses = new ArrayList<>(); + List classHierarchy = new ArrayList<>(); { TypeElement thisElement = annotatedType; TypeMirror superClass = thisElement.getSuperclass(); @@ -127,22 +131,27 @@ public class ConfigProcessor extends AbstractProcessor { break; } - superClasses.add(superType); + classHierarchy.add(superType); thisElement = (TypeElement) superType.asElement(); superClass = thisElement.getSuperclass(); } } - Collections.reverse(superClasses); - superClasses.add((DeclaredType) annotatedType.asType()); + Collections.reverse(classHierarchy); + classHierarchy.add((DeclaredType) annotatedType.asType()); // create a list of methods annotated with DefaultValue - ignore everything else - Map enclosedMethods = new LinkedHashMap<>(); - for (DeclaredType superClass : superClasses) { - for (Element e : superClass.asElement().getEnclosedElements()) { + Map enclosedMethods = new LinkedHashMap<>(); + Map defaultAnnotationsMap = new LinkedHashMap<>(); + for (DeclaredType thisType : classHierarchy) { + for (Element e : thisType.asElement().getEnclosedElements()) { if (e.getKind() == ElementKind.METHOD && e.getAnnotation(DefaultValue.class) != null && e instanceof ExecutableElement) { - enclosedMethods.put(e.getSimpleName().toString(), (ExecutableElement) e); + ExecutableElement ex = (ExecutableElement) e; + enclosedMethods.put(ex.getSimpleName(), ex); + AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName()); + defaultAnnotationsMap.put(ex.getSimpleName(), + buildAnnotationBundle(ex, defaultValues)); } } } @@ -150,83 +159,8 @@ public class ConfigProcessor extends AbstractProcessor { // holds the annotation information on each method Map annotationsMap = new LinkedHashMap<>(); for (ExecutableElement e : enclosedMethods.values()) { - - ImmutableAnnotationBundle.Builder builder = ImmutableAnnotationBundle.builder(); - builder.key(e.getSimpleName().toString()); - builder.comment(""); - - Types types = processingEnv.getTypeUtils(); - TypeMirror returnType = e.getReturnType(); - TypeMirror erasure = types.erasure(returnType); - builder.erasure(erasure); - - List typeArgs = new ArrayList<>(); - if (erasure.getKind() == TypeKind.DECLARED) { - // these are the parameter types for a generic class - List args = ((DeclaredType) returnType).getTypeArguments(); - typeArgs.addAll(args); - } else if (erasure.getKind().isPrimitive()) { - // no type arguments here - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - String.format("Unsupported kind %s for type %s!", erasure.getKind().toString(), - erasure.toString())); - } - - builder.addAllTypeArgs(typeArgs); - - String defaultValue = null; - - List methodAnnotations = new ArrayList<>(); - for (var a : supportedMethodAnnotations) - methodAnnotations.add(e.getAnnotation(a)); - - for (Annotation annotation : methodAnnotations) { - if (annotation == null) - continue; - - if (annotation instanceof Key) { - builder.key(((Key) annotation).value()); - } else if (annotation instanceof Comment) { - builder.comment(((Comment) annotation).value()); - } else if (annotation instanceof DefaultValue) { - DefaultValue d = (DefaultValue) annotation; - defaultValue = d.value(); - } else if (annotation instanceof ParserClass) { - ParserClass pc = (ParserClass) annotation; - - // this works, but there has to be a better way? - TypeMirror tm; - try { - // see - // https://stackoverflow.com/questions/7687829/java-6-annotation-processing-getting-a-class-from-an-annotation - tm = processingEnv.getElementUtils().getTypeElement(pc.value().toString()) - .asType(); - } catch (MirroredTypeException mte) { - tm = mte.getTypeMirror(); - } - builder.parserClass(tm); - } else { - throw new IllegalArgumentException( - "Unknown annotation type " + annotation.getClass().getSimpleName()); - } - } - - // if a method does not have a default value, it will not be included in the generated - // code - if (defaultValue == null) { - messager.printMessage(Diagnostic.Kind.WARNING, - String.format("No default value on method %s!", e.getSimpleName())); - continue; - } - - builder.defaultValue(defaultValue); - AnnotationBundle bundle = builder.build(); - if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv) - && bundle.typeArgs().size() == 0) - messager.printMessage(Diagnostic.Kind.ERROR, - String.format("No parameter type for List on method %s!", e.getSimpleName())); - annotationsMap.put(e, bundle); + AnnotationBundle defaultValues = defaultAnnotationsMap.get(e.getSimpleName()); + annotationsMap.put(e, buildAnnotationBundle(e, defaultValues)); } // generate the methods from the interface @@ -275,6 +209,87 @@ public class ConfigProcessor extends AbstractProcessor { } + /** + * + * @param e annotated method + * @param defaultValues default values for annotations - could be from a parent class + * @return annotation values + */ + private AnnotationBundle buildAnnotationBundle(ExecutableElement e, + AnnotationBundle defaultValues) { + + ImmutableAnnotationBundle.Builder builder = ImmutableAnnotationBundle.builder(); + builder.key(e.getSimpleName().toString()); + builder.comment(""); + builder.defaultValue(""); + if (defaultValues != null) { + builder.key(defaultValues.key()); + builder.comment(defaultValues.comment()); + builder.defaultValue(defaultValues.defaultValue()); + if (defaultValues.parserClass().isPresent()) + builder.parserClass(defaultValues.parserClass().get()); + } + + Types types = processingEnv.getTypeUtils(); + TypeMirror returnType = e.getReturnType(); + TypeMirror erasure = types.erasure(returnType); + builder.erasure(erasure); + + List typeArgs = new ArrayList<>(); + if (erasure.getKind() == TypeKind.DECLARED) { + // these are the parameter types for a generic class + List args = ((DeclaredType) returnType).getTypeArguments(); + typeArgs.addAll(args); + } else if (erasure.getKind().isPrimitive()) { + // no type arguments here + } else { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format( + "Unsupported kind %s for type %s!", erasure.getKind().toString(), erasure.toString())); + } + + builder.addAllTypeArgs(typeArgs); + + List methodAnnotations = new ArrayList<>(); + for (var a : supportedMethodAnnotations) + methodAnnotations.add(e.getAnnotation(a)); + + for (Annotation annotation : methodAnnotations) { + if (annotation == null) + continue; + + if (annotation instanceof Key) { + builder.key(((Key) annotation).value()); + } else if (annotation instanceof Comment) { + builder.comment(((Comment) annotation).value()); + } else if (annotation instanceof DefaultValue) { + builder.defaultValue(((DefaultValue) annotation).value()); + } else if (annotation instanceof ParserClass) { + ParserClass pc = (ParserClass) annotation; + + // this works, but there has to be a better way? + TypeMirror tm; + try { + // see + // https://stackoverflow.com/questions/7687829/java-6-annotation-processing-getting-a-class-from-an-annotation + tm = processingEnv.getElementUtils().getTypeElement(pc.value().toString()).asType(); + } catch (MirroredTypeException mte) { + tm = mte.getTypeMirror(); + } + builder.parserClass(tm); + } else { + throw new IllegalArgumentException( + "Unknown annotation type " + annotation.getClass().getSimpleName()); + } + } + + AnnotationBundle bundle = builder.build(); + if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv) + && bundle.typeArgs().size() == 0) + messager.printMessage(Diagnostic.Kind.ERROR, + String.format("No parameter type for List on method %s!", e.getSimpleName())); + return bundle; + } + /** * Build the method to generate an Apache Commons {@link Configuration} *