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;
}
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();
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;
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;
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
- info("%s init", ExtensionAnnotationProcessor.class);
+ info("%s init", ExtensionAnnotationProcessor.class.getName());
initStorage();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
- Set<String> annotationTypes = new HashSet<>();
- annotationTypes.add(Extension.class.getName());
-
- return annotationTypes;
+ return Collections.singleton("*");
}
@Override
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);
}
}
}
}
+ 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);
+ }
+ }
+
}
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;
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.
return entry.getValue();
}
}
+
return null;
}
*/
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;
}
/**
" }",
"}");
- private static final JavaFileObject WhazzupGreeting_No_ExtensionPoint = JavaFileObjects.forSourceLines(
+ private static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines(
"WhazzupGreeting",
"package test;",
"import org.pf4j.Extension;",
" }",
"}");
+ 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
@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);
}
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());
+ }
+
}