You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ExtensionAnnotationProcessor.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * Copyright (C) 2012-present the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.pf4j.processor;
  17. import org.pf4j.Extension;
  18. import org.pf4j.ExtensionPoint;
  19. import org.pf4j.util.ClassUtils;
  20. import javax.annotation.processing.AbstractProcessor;
  21. import javax.annotation.processing.ProcessingEnvironment;
  22. import javax.annotation.processing.RoundEnvironment;
  23. import javax.lang.model.SourceVersion;
  24. import javax.lang.model.element.AnnotationValue;
  25. import javax.lang.model.element.Element;
  26. import javax.lang.model.element.ElementKind;
  27. import javax.lang.model.element.TypeElement;
  28. import javax.lang.model.type.DeclaredType;
  29. import javax.lang.model.type.TypeKind;
  30. import javax.lang.model.type.TypeMirror;
  31. import javax.tools.Diagnostic;
  32. import java.lang.reflect.Constructor;
  33. import java.util.ArrayList;
  34. import java.util.Collections;
  35. import java.util.HashMap;
  36. import java.util.HashSet;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Set;
  40. import java.util.TreeSet;
  41. /**
  42. * Processes {@link Extension} annotations and generates an {@link ExtensionStorage}.
  43. * You can specify the concrete {@link ExtensionStorage} via processor's environment options
  44. * ({@link ProcessingEnvironment#getOptions()}) or system property.
  45. * In both variants the option/property name is {@code pf4j.storageClassName}.
  46. *
  47. * @author Decebal Suiu
  48. */
  49. public class ExtensionAnnotationProcessor extends AbstractProcessor {
  50. private static final String STORAGE_CLASS_NAME = "pf4j.storageClassName";
  51. private static final String IGNORE_EXTENSION_POINT = "pf4j.ignoreExtensionPoint";
  52. private Map<String, Set<String>> extensions = new HashMap<>(); // the key is the extension point
  53. private Map<String, Set<String>> oldExtensions = new HashMap<>(); // the key is the extension point
  54. private ExtensionStorage storage;
  55. private boolean ignoreExtensionPoint;
  56. @Override
  57. public synchronized void init(ProcessingEnvironment processingEnv) {
  58. super.init(processingEnv);
  59. info("%s init", ExtensionAnnotationProcessor.class.getName());
  60. info("Options %s", processingEnv.getOptions());
  61. initStorage();
  62. initIgnoreExtensionPoint();
  63. }
  64. @Override
  65. public SourceVersion getSupportedSourceVersion() {
  66. return SourceVersion.latest();
  67. }
  68. @Override
  69. public Set<String> getSupportedAnnotationTypes() {
  70. return Collections.singleton("*");
  71. }
  72. @Override
  73. public Set<String> getSupportedOptions() {
  74. Set<String> options = new HashSet<>();
  75. options.add(STORAGE_CLASS_NAME);
  76. options.add(IGNORE_EXTENSION_POINT);
  77. return options;
  78. }
  79. @Override
  80. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  81. if (roundEnv.processingOver()) {
  82. return false;
  83. }
  84. info("Processing @%s", Extension.class.getName());
  85. for (Element element : roundEnv.getElementsAnnotatedWith(Extension.class)) {
  86. if (element.getKind() != ElementKind.ANNOTATION_TYPE) {
  87. processExtensionElement(element);
  88. }
  89. }
  90. // collect nested extension annotations
  91. List<TypeElement> extensionAnnotations = new ArrayList<>();
  92. for (TypeElement annotation : annotations) {
  93. if (ClassUtils.getAnnotationMirror(annotation, Extension.class) != null) {
  94. extensionAnnotations.add(annotation);
  95. }
  96. }
  97. // process nested extension annotations
  98. for (TypeElement te : extensionAnnotations) {
  99. info("Processing @%s", te);
  100. for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
  101. processExtensionElement(element);
  102. }
  103. }
  104. // read old extensions
  105. oldExtensions = storage.read();
  106. for (Map.Entry<String, Set<String>> entry : oldExtensions.entrySet()) {
  107. String extensionPoint = entry.getKey();
  108. if (extensions.containsKey(extensionPoint)) {
  109. extensions.get(extensionPoint).addAll(entry.getValue());
  110. } else {
  111. extensions.put(extensionPoint, entry.getValue());
  112. }
  113. }
  114. // Even an empty extensions descriptor is semantically correct and should be
  115. // written to prevent classloaders from falling back to extension descriptor
  116. // resources from dependencies of the plugin being processed.
  117. storage.write(extensions);
  118. return false;
  119. }
  120. public ProcessingEnvironment getProcessingEnvironment() {
  121. return processingEnv;
  122. }
  123. public void error(String message, Object... args) {
  124. processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(message, args));
  125. }
  126. public void error(Element element, String message, Object... args) {
  127. processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(message, args), element);
  128. }
  129. public void info(String message, Object... args) {
  130. processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, String.format(message, args));
  131. }
  132. public void info(Element element, String message, Object... args) {
  133. processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, String.format(message, args), element);
  134. }
  135. public String getBinaryName(TypeElement element) {
  136. return processingEnv.getElementUtils().getBinaryName(element).toString();
  137. }
  138. public Map<String, Set<String>> getExtensions() {
  139. return extensions;
  140. }
  141. public Map<String, Set<String>> getOldExtensions() {
  142. return oldExtensions;
  143. }
  144. public ExtensionStorage getStorage() {
  145. return storage;
  146. }
  147. private void processExtensionElement(Element element) {
  148. // check if @Extension is put on class and not on method or constructor
  149. if (!(element instanceof TypeElement)) {
  150. error(element, "Put annotation only on classes (no methods, no fields)");
  151. return;
  152. }
  153. // check if class extends/implements an extension point
  154. if (!ignoreExtensionPoint && !isExtension(element.asType())) {
  155. error(element, "%s is not an extension (it doesn't implement ExtensionPoint)", element);
  156. return;
  157. }
  158. TypeElement extensionElement = (TypeElement) element;
  159. List<TypeElement> extensionPointElements = findExtensionPoints(extensionElement);
  160. if (extensionPointElements.isEmpty()) {
  161. error(element, "No extension points found for extension %s", extensionElement);
  162. return;
  163. }
  164. String extension = getBinaryName(extensionElement);
  165. for (TypeElement extensionPointElement : extensionPointElements) {
  166. String extensionPoint = getBinaryName(extensionPointElement);
  167. Set<String> extensionPoints = extensions.computeIfAbsent(extensionPoint, k -> new TreeSet<>());
  168. extensionPoints.add(extension);
  169. }
  170. }
  171. @SuppressWarnings("unchecked")
  172. private List<TypeElement> findExtensionPoints(TypeElement extensionElement) {
  173. List<TypeElement> extensionPointElements = new ArrayList<>();
  174. // use extension points, that were explicitly set in the extension annotation
  175. AnnotationValue annotatedExtensionPoints = ClassUtils.getAnnotationValue(extensionElement, Extension.class, "points");
  176. List<? extends AnnotationValue> extensionPointClasses = (annotatedExtensionPoints != null) ?
  177. (List<? extends AnnotationValue>) annotatedExtensionPoints.getValue() :
  178. null;
  179. if (extensionPointClasses != null && !extensionPointClasses.isEmpty()) {
  180. for (AnnotationValue extensionPointClass : extensionPointClasses) {
  181. String extensionPointClassName = extensionPointClass.getValue().toString();
  182. TypeElement extensionPointElement = processingEnv.getElementUtils().getTypeElement(extensionPointClassName);
  183. extensionPointElements.add(extensionPointElement);
  184. }
  185. }
  186. // detect extension points automatically, if they are not explicitly configured (default behaviour)
  187. else {
  188. // search in interfaces
  189. List<? extends TypeMirror> interfaces = extensionElement.getInterfaces();
  190. for (TypeMirror item : interfaces) {
  191. boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(item, getExtensionPointType());
  192. if (isExtensionPoint) {
  193. extensionPointElements.add(getElement(item));
  194. }
  195. }
  196. // search in superclass
  197. TypeMirror superclass = extensionElement.getSuperclass();
  198. if (superclass.getKind() != TypeKind.NONE) {
  199. boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(superclass, getExtensionPointType());
  200. if (isExtensionPoint) {
  201. extensionPointElements.add(getElement(superclass));
  202. }
  203. }
  204. // pickup the first interface
  205. if (extensionPointElements.isEmpty() && ignoreExtensionPoint) {
  206. if (interfaces.isEmpty()) {
  207. error(extensionElement, "Cannot use %s as extension point with %s compiler arg (it doesn't implement any interface)",
  208. extensionElement, IGNORE_EXTENSION_POINT);
  209. } else if (interfaces.size() == 1) {
  210. extensionPointElements.add(getElement(interfaces.get(0)));
  211. } else {
  212. error(extensionElement, "Cannot use %s as extension point with %s compiler arg (it implements multiple interfaces)",
  213. extensionElement, IGNORE_EXTENSION_POINT);
  214. }
  215. }
  216. }
  217. return extensionPointElements;
  218. }
  219. private boolean isExtension(TypeMirror typeMirror) {
  220. return processingEnv.getTypeUtils().isAssignable(typeMirror, getExtensionPointType());
  221. }
  222. private TypeMirror getExtensionPointType() {
  223. return processingEnv.getElementUtils().getTypeElement(ExtensionPoint.class.getName()).asType();
  224. }
  225. @SuppressWarnings("unchecked")
  226. private void initStorage() {
  227. // search in processing options
  228. String storageClassName = processingEnv.getOptions().get(STORAGE_CLASS_NAME);
  229. if (storageClassName == null) {
  230. // search in system properties
  231. storageClassName = System.getProperty(STORAGE_CLASS_NAME);
  232. }
  233. if (storageClassName != null) {
  234. // use reflection to create the storage instance
  235. try {
  236. Class storageClass = getClass().getClassLoader().loadClass(storageClassName);
  237. Constructor constructor = storageClass.getConstructor(ExtensionAnnotationProcessor.class);
  238. storage = (ExtensionStorage) constructor.newInstance(this);
  239. } catch (Exception e) {
  240. error(e.getMessage());
  241. }
  242. }
  243. if (storage == null) {
  244. // default storage
  245. storage = new LegacyExtensionStorage(this);
  246. }
  247. }
  248. private void initIgnoreExtensionPoint() {
  249. // search in processing options and system properties
  250. ignoreExtensionPoint = getProcessingEnvironment().getOptions().containsKey(IGNORE_EXTENSION_POINT) ||
  251. System.getProperty(IGNORE_EXTENSION_POINT) != null;
  252. }
  253. private TypeElement getElement(TypeMirror typeMirror) {
  254. return (TypeElement) ((DeclaredType) typeMirror).asElement();
  255. }
  256. }