]> source.dussan.org Git - pf4j.git/commitdiff
Found extensions when using decorated annotations (#348)
authorDecebal Suiu <decebal.suiu@gmail.com>
Fri, 15 Nov 2019 15:53:51 +0000 (17:53 +0200)
committerGitHub <noreply@github.com>
Fri, 15 Nov 2019 15:53:51 +0000 (17:53 +0200)
pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java
pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java
pf4j/src/main/java/org/pf4j/util/ClassUtils.java
pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java

index b260c39fb81f4576eb9dc53fc41cf537955aa12e..3cfbc7ea9ab421e5c6ec65d2ec43c3c15c932091 100644 (file)
@@ -20,6 +20,7 @@ import org.pf4j.util.ClassUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -340,15 +341,30 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
     }
 
     private ExtensionWrapper createExtensionWrapper(Class<?> extensionClass) {
-        int ordinal = 0;
-        if (extensionClass.isAnnotationPresent(Extension.class)) {
-            ordinal = extensionClass.getAnnotation(Extension.class).ordinal();
-        }
+        Extension extensionAnnotation = findExtensionAnnotation(extensionClass);
+        int ordinal = extensionAnnotation != null ? extensionAnnotation.ordinal() : 0;
         ExtensionDescriptor descriptor = new ExtensionDescriptor(ordinal, extensionClass);
 
         return new ExtensionWrapper<>(descriptor, pluginManager.getExtensionFactory());
     }
 
+    private Extension findExtensionAnnotation(Class<?> clazz) {
+        if (clazz.isAnnotationPresent(Extension.class)) {
+            return clazz.getAnnotation(Extension.class);
+        }
+
+        // search recursively through all annotations
+        for (Annotation annotation : clazz.getAnnotations()) {
+            Class<? extends Annotation> annotationClass = annotation.annotationType();
+            Extension extensionAnnotation = findExtensionAnnotation(annotationClass);
+            if (extensionAnnotation != null) {
+                return extensionAnnotation;
+            }
+        }
+
+        return null;
+    }
+
     private void checkDifferentClassLoaders(Class<?> type, Class<?> extensionClass) {
         ClassLoader typeClassLoader = type.getClassLoader(); // class loader of extension point
         ClassLoader extensionClassLoader = extensionClass.getClassLoader();
index 5d05113f6060827ad48e9119fb0129933429f5f3..9c629dedb242e23d7e76c55292b11dafef806405 100644 (file)
@@ -25,6 +25,7 @@ import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
@@ -32,6 +33,7 @@ import javax.lang.model.type.TypeMirror;
 import javax.tools.Diagnostic;
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -60,7 +62,7 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
     public synchronized void init(ProcessingEnvironment processingEnv) {
         super.init(processingEnv);
 
-        info("%s init", ExtensionAnnotationProcessor.class);
+        info("%s init", ExtensionAnnotationProcessor.class.getName());
 
         initStorage();
     }
@@ -72,10 +74,7 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
 
     @Override
     public Set<String> getSupportedAnnotationTypes() {
-        Set<String> annotationTypes = new HashSet<>();
-        annotationTypes.add(Extension.class.getName());
-
-        return annotationTypes;
+        return Collections.singleton("*");
     }
 
     @Override
@@ -92,33 +91,26 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
             return false;
         }
 
-        info("Processing @%s", Extension.class);
+        info("Processing @%s", Extension.class.getName());
         for (Element element : roundEnv.getElementsAnnotatedWith(Extension.class)) {
-            // check if @Extension is put on class and not on method or constructor
-            if (!(element instanceof TypeElement)) {
-                error(element, "Put annotation only on classes (no methods, no fields)");
-                continue;
-            }
-
-            // check if class extends/implements an extension point
-            if (!isExtension(element.asType())) {
-                error(element, "%s is not an extension (it doesn't implement ExtensionPoint)", element);
-                continue;
+            if (element.getKind() != ElementKind.ANNOTATION_TYPE) {
+                processExtensionElement(element);
             }
+        }
 
-            TypeElement extensionElement = (TypeElement) element;
-//            Extension annotation = element.getAnnotation(Extension.class);
-            List<TypeElement> extensionPointElements = findExtensionPoints(extensionElement);
-            if (extensionPointElements.isEmpty()) {
-                // TODO throw error ?
-                continue;
+        // collect nested extension annotations
+        List<TypeElement> extensionAnnotations = new ArrayList<>();
+        for (TypeElement annotation : annotations) {
+            if (ClassUtils.getAnnotationMirror(annotation, Extension.class) != null) {
+                extensionAnnotations.add(annotation);
             }
+        }
 
-            String extension = getBinaryName(extensionElement);
-            for (TypeElement extensionPointElement : extensionPointElements) {
-                String extensionPoint = getBinaryName(extensionPointElement);
-                Set<String> extensionPoints = extensions.computeIfAbsent(extensionPoint, k -> new TreeSet<>());
-                extensionPoints.add(extension);
+        // process nested extension annotations
+        for (TypeElement te : extensionAnnotations) {
+            info("Processing @%s", te);
+            for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
+                processExtensionElement(element);
             }
         }
 
@@ -250,4 +242,32 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
         }
     }
 
+    private void processExtensionElement(Element element) {
+        // check if @Extension is put on class and not on method or constructor
+        if (!(element instanceof TypeElement)) {
+            error(element, "Put annotation only on classes (no methods, no fields)");
+            return;
+        }
+
+        // check if class extends/implements an extension point
+        if (!isExtension(element.asType())) {
+            error(element, "%s is not an extension (it doesn't implement ExtensionPoint)", element);
+            return;
+        }
+
+        TypeElement extensionElement = (TypeElement) element;
+        List<TypeElement> extensionPointElements = findExtensionPoints(extensionElement);
+        if (extensionPointElements.isEmpty()) {
+            error(element, "No extension points found for extension %s", extensionElement);
+            return;
+        }
+
+        String extension = getBinaryName(extensionElement);
+        for (TypeElement extensionPointElement : extensionPointElements) {
+            String extensionPoint = getBinaryName(extensionPointElement);
+            Set<String> extensionPoints = extensions.computeIfAbsent(extensionPoint, k -> new TreeSet<>());
+            extensionPoints.add(extension);
+        }
+    }
+
 }
index 77e46e3ef4d0e1fb0a3b7461d841b0b4afa71f8e..97daa27998d36608a533a13c6a4ce9b706d6e17b 100644 (file)
@@ -19,7 +19,6 @@ import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -93,9 +92,17 @@ public class ClassUtils {
                 return m;
             }
         }
+
         return null;
     }
 
+    /*
+    public static Element getAnnotationMirrorElement(TypeElement typeElement, Class<?> annotationClass) {
+        AnnotationMirror annotationMirror = getAnnotationMirror(typeElement, annotationClass);
+        return annotationMirror != null ? annotationMirror.getAnnotationType().asElement() : null;
+    }
+    */
+
     /**
      * Get a certain parameter of an {@link AnnotationMirror}.
      * See <a href="https://stackoverflow.com/a/10167558">stackoverflow.com</a> for more information.
@@ -111,6 +118,7 @@ public class ClassUtils {
                 return entry.getValue();
             }
         }
+
         return null;
     }
 
@@ -126,9 +134,7 @@ public class ClassUtils {
      */
     public static AnnotationValue getAnnotationValue(TypeElement typeElement, Class<?> annotationClass, String annotationParameter) {
         AnnotationMirror annotationMirror = getAnnotationMirror(typeElement, annotationClass);
-        return (annotationMirror != null) ?
-            getAnnotationValue(annotationMirror, annotationParameter) :
-            null;
+        return annotationMirror != null ? getAnnotationValue(annotationMirror, annotationParameter) : null;
     }
 
     /**
index 1556472f7a1ff791be1fed0205377a9785834399..4f8dd2e1933399fb06c2b5beb5325ed273eb5294 100644 (file)
@@ -60,7 +60,7 @@ public class ExtensionAnnotationProcessorTest {
         "    }",
         "}");
 
-    private static final JavaFileObject WhazzupGreeting_No_ExtensionPoint = JavaFileObjects.forSourceLines(
+    private static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines(
         "WhazzupGreeting",
         "package test;",
         "import org.pf4j.Extension;",
@@ -73,12 +73,42 @@ public class ExtensionAnnotationProcessorTest {
         "    }",
         "}");
 
+    private static final JavaFileObject SpinnakerExtension = JavaFileObjects.forSourceLines(
+        "SpinnakerExtension",
+        "package test;",
+        "",
+        "import org.pf4j.Extension;",
+        "import java.lang.annotation.Documented;",
+        "import java.lang.annotation.ElementType;",
+        "import java.lang.annotation.Retention;",
+        "import java.lang.annotation.RetentionPolicy;",
+        "import java.lang.annotation.Target;",
+        "",
+        "@Extension",
+        "@Retention(RetentionPolicy.RUNTIME)",
+        "@Target(ElementType.TYPE)",
+        "@Documented",
+        "public @interface SpinnakerExtension {",
+        "}");
+
+    private static final JavaFileObject WhazzupGreeting_SpinnakerExtension = JavaFileObjects.forSourceLines(
+        "WhazzupGreeting",
+        "package test;",
+        "",
+        "@SpinnakerExtension",
+        "public class WhazzupGreeting implements Greeting {",
+        "   @Override",
+        "    public String getGreeting() {",
+        "       return \"Whazzup\";",
+        "    }",
+        "}");
+
     @Test
     public void getSupportedAnnotationTypes() {
         ExtensionAnnotationProcessor instance = new ExtensionAnnotationProcessor();
         Set<String> result = instance.getSupportedAnnotationTypes();
         assertEquals(1, result.size());
-        assertEquals(Extension.class.getName(), result.iterator().next());
+        assertEquals("*", result.iterator().next());
     }
 
     @Test
@@ -118,10 +148,10 @@ public class ExtensionAnnotationProcessorTest {
     @Test
     public void compileWithError() {
         ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
-        Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting_No_ExtensionPoint);
+        Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting_NoExtensionPoint);
         assertThat(compilation).failed();
         assertThat(compilation).hadErrorContaining("it doesn't implement ExtensionPoint")
-            .inFile(WhazzupGreeting_No_ExtensionPoint)
+            .inFile(WhazzupGreeting_NoExtensionPoint)
             .onLine(5)
             .atColumn(8);
     }
@@ -136,4 +166,14 @@ public class ExtensionAnnotationProcessorTest {
         assertEquals(extensions, processor.getExtensions());
     }
 
+    @Test
+    public void compileNestedExtensionAnnotation() {
+        ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
+        Compilation compilation = javac().withProcessors(processor).compile(Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension);
+        assertThat(compilation).succeededWithoutWarnings();
+        Map<String, Set<String>> extensions = new HashMap<>();
+        extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting")));
+        assertEquals(extensions, processor.getExtensions());
+    }
+
 }