From 7d9f6a012bee888333e11c1284e41ea472e1838a Mon Sep 17 00:00:00 2001 From: Andreas Rudolph Date: Thu, 3 Jan 2019 19:42:30 +0100 Subject: [PATCH] explicitly configure extension points for an extension (#265) --- pf4j/src/main/java/org/pf4j/Extension.java | 13 ++++ .../ExtensionAnnotationProcessor.java | 42 +++++++++---- .../main/java/org/pf4j/util/ClassUtils.java | 59 +++++++++++++++++++ 3 files changed, 101 insertions(+), 13 deletions(-) diff --git a/pf4j/src/main/java/org/pf4j/Extension.java b/pf4j/src/main/java/org/pf4j/Extension.java index dceecbd..a5131f3 100644 --- a/pf4j/src/main/java/org/pf4j/Extension.java +++ b/pf4j/src/main/java/org/pf4j/Extension.java @@ -34,4 +34,17 @@ public @interface Extension { int ordinal() default 0; + /** + * An array of extension points, that are implemented by this extension. + * This explicit configuration overrides the automatic detection of extension points in the + * {@link org.pf4j.processor.ExtensionAnnotationProcessor}. + *

+ * In case your extension is directly derived from an extension point this attribute is NOT required. + * But under certain more complex scenarios it + * might be useful to explicitly set the extension points for an extension. + * + * @return classes of extension points, that are implemented by this extension + */ + Class[] points() default {}; + } diff --git a/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java b/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java index 2e89df3..e0f3513 100644 --- a/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java +++ b/pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java @@ -17,11 +17,13 @@ package org.pf4j.processor; import org.pf4j.Extension; import org.pf4j.ExtensionPoint; +import org.pf4j.util.ClassUtils; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; 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.TypeElement; import javax.lang.model.type.DeclaredType; @@ -168,22 +170,37 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor { private List findExtensionPoints(TypeElement extensionElement) { List extensionPointElements = new ArrayList<>(); - // search in interfaces - for (TypeMirror item : extensionElement.getInterfaces()) { - boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(item, getExtensionPointType()); - if (isExtensionPoint) { - TypeElement extensionPointElement = (TypeElement) ((DeclaredType) item).asElement(); + // use extension points, that were explicitly set in the extension annotation + AnnotationValue annotatedExtensionPoints = ClassUtils.getAnnotationValue(extensionElement, Extension.class, "points"); + List extensionPointClasses = (annotatedExtensionPoints != null) ? + (List) annotatedExtensionPoints.getValue() : + null; + if (extensionPointClasses != null && !extensionPointClasses.isEmpty()) { + for (AnnotationValue extensionPointClass : extensionPointClasses) { + String extensionPointClassName = extensionPointClass.getValue().toString(); + TypeElement extensionPointElement = processingEnv.getElementUtils().getTypeElement(extensionPointClassName); extensionPointElements.add(extensionPointElement); } } + // detect extension points automatically, if they are not explicitly configured (default behaviour) + else { + // search in interfaces + for (TypeMirror item : extensionElement.getInterfaces()) { + boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(item, getExtensionPointType()); + if (isExtensionPoint) { + TypeElement extensionPointElement = (TypeElement) ((DeclaredType) item).asElement(); + extensionPointElements.add(extensionPointElement); + } + } - // search in superclass - TypeMirror superclass = extensionElement.getSuperclass(); - if (superclass.getKind() != TypeKind.NONE) { - boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(superclass, getExtensionPointType()); - if (isExtensionPoint) { - TypeElement extensionPointElement = (TypeElement) ((DeclaredType) superclass).asElement(); - extensionPointElements.add(extensionPointElement); + // search in superclass + TypeMirror superclass = extensionElement.getSuperclass(); + if (superclass.getKind() != TypeKind.NONE) { + boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(superclass, getExtensionPointType()); + if (isExtensionPoint) { + TypeElement extensionPointElement = (TypeElement) ((DeclaredType) superclass).asElement(); + extensionPointElements.add(extensionPointElement); + } } } @@ -228,5 +245,4 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor { return storage; } - } diff --git a/pf4j/src/main/java/org/pf4j/util/ClassUtils.java b/pf4j/src/main/java/org/pf4j/util/ClassUtils.java index b85d54a..77e46e3 100644 --- a/pf4j/src/main/java/org/pf4j/util/ClassUtils.java +++ b/pf4j/src/main/java/org/pf4j/util/ClassUtils.java @@ -15,9 +15,14 @@ */ package org.pf4j.util; +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; /** * @author Decebal Suiu @@ -72,6 +77,60 @@ public class ClassUtils { } */ + /** + * Get a certain annotation of a {@link TypeElement}. + * See stackoverflow.com for more information. + * + * @param typeElement the type element, that contains the requested annotation + * @param annotationClass the class of the requested annotation + * @return the requested annotation or null, if no annotation of the provided class was found + * @throws NullPointerException if typeElement or annotationClass is null + */ + public static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class annotationClass) { + String annotationClassName = annotationClass.getName(); + for (AnnotationMirror m : typeElement.getAnnotationMirrors()) { + if (m.getAnnotationType().toString().equals(annotationClassName)) { + return m; + } + } + return null; + } + + /** + * Get a certain parameter of an {@link AnnotationMirror}. + * See stackoverflow.com for more information. + * + * @param annotationMirror the annotation, that contains the requested parameter + * @param annotationParameter the name of the requested annotation parameter + * @return the requested parameter or null, if no parameter of the provided name was found + * @throws NullPointerException if annotationMirror is null + */ + public static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String annotationParameter) { + for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals(annotationParameter)) { + return entry.getValue(); + } + } + return null; + } + + /** + * Get a certain annotation parameter of a {@link TypeElement}. + * See stackoverflow.com for more information. + * + * @param typeElement the type element, that contains the requested annotation + * @param annotationClass the class of the requested annotation + * @param annotationParameter the name of the requested annotation parameter + * @return the requested parameter or null, if no annotation for the provided class was found or no annotation parameter was found + * @throws NullPointerException if typeElement or annotationClass is null + */ + public static AnnotationValue getAnnotationValue(TypeElement typeElement, Class annotationClass, String annotationParameter) { + AnnotationMirror annotationMirror = getAnnotationMirror(typeElement, annotationClass); + return (annotationMirror != null) ? + getAnnotationValue(annotationMirror, annotationParameter) : + null; + } + /** * Uses {@link Class#getSimpleName()} to convert from {@link Class} to {@link String}. * -- 2.39.5