diff --git a/pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java b/pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java index b260c39..3cfbc7e 100644 --- a/pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java +++ b/pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java @@ -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 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(); diff --git a/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java b/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java index 5d05113..9c629de 100644 --- a/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java +++ b/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java @@ -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 getSupportedAnnotationTypes() { - Set 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 extensionPointElements = findExtensionPoints(extensionElement); - if (extensionPointElements.isEmpty()) { - // TODO throw error ? - continue; + // collect nested extension annotations + List 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 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 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 extensionPoints = extensions.computeIfAbsent(extensionPoint, k -> new TreeSet<>()); + extensionPoints.add(extension); + } + } + } diff --git a/pf4j/src/main/java/org/pf4j/util/ClassUtils.java b/pf4j/src/main/java/org/pf4j/util/ClassUtils.java index 77e46e3..97daa27 100644 --- a/pf4j/src/main/java/org/pf4j/util/ClassUtils.java +++ b/pf4j/src/main/java/org/pf4j/util/ClassUtils.java @@ -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 stackoverflow.com 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; } /** diff --git a/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java b/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java index 1556472..4f8dd2e 100644 --- a/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java +++ b/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java @@ -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 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> extensions = new HashMap<>(); + extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting"))); + assertEquals(extensions, processor.getExtensions()); + } + }