1 /*
   2  * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.serviceprovider.processor;
  26 
  27 import java.util.HashMap;
  28 import java.util.HashSet;
  29 import java.util.Map;
  30 import java.util.Map.Entry;
  31 import java.util.Set;
  32 
  33 import javax.annotation.processing.RoundEnvironment;
  34 import javax.annotation.processing.SupportedAnnotationTypes;
  35 import javax.lang.model.SourceVersion;
  36 import javax.lang.model.element.AnnotationMirror;
  37 import javax.lang.model.element.Element;
  38 import javax.lang.model.element.ElementKind;
  39 import javax.lang.model.element.PackageElement;
  40 import javax.lang.model.element.TypeElement;
  41 import javax.lang.model.type.TypeMirror;
  42 import javax.tools.Diagnostic.Kind;
  43 
  44 import org.graalvm.compiler.processor.AbstractProcessor;
  45 
  46 /**
  47  * Processes classes annotated with {@code ServiceProvider}. For a service defined by {@code S} and
  48  * a class {@code P} implementing the service, this processor generates the file
  49  * {@code META-INF/providers/P} whose contents are a single line containing the fully qualified name
  50  * of {@code S}.
  51  */
  52 @SupportedAnnotationTypes("org.graalvm.compiler.serviceprovider.ServiceProvider")
  53 public class ServiceProviderProcessor extends AbstractProcessor {
  54 
  55     private static final String SERVICE_PROVIDER_CLASS_NAME = "org.graalvm.compiler.serviceprovider.ServiceProvider";
  56     private final Set<TypeElement> processed = new HashSet<>();
  57     private final Map<TypeElement, String> serviceProviders = new HashMap<>();
  58 
  59     @Override
  60     public SourceVersion getSupportedSourceVersion() {
  61         return SourceVersion.latest();
  62     }
  63 
  64     private boolean verifyAnnotation(TypeMirror serviceInterface, TypeElement serviceProvider) {
  65         if (!processingEnv.getTypeUtils().isSubtype(serviceProvider.asType(), serviceInterface)) {
  66             String msg = String.format("Service provider class %s must implement service interface %s", serviceProvider.getSimpleName(), serviceInterface);
  67             processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider);
  68             return false;
  69         }
  70 
  71         return true;
  72     }
  73 
  74     private void processElement(TypeElement serviceProvider) {
  75         if (processed.contains(serviceProvider)) {
  76             return;
  77         }
  78 
  79         processed.add(serviceProvider);
  80         AnnotationMirror annotation = getAnnotation(serviceProvider, getType(SERVICE_PROVIDER_CLASS_NAME));
  81         if (annotation != null) {
  82             TypeMirror service = getAnnotationValue(annotation, "value", TypeMirror.class);
  83             if (verifyAnnotation(service, serviceProvider)) {
  84                 if (serviceProvider.getNestingKind().isNested()) {
  85                     /*
  86                      * This is a simplifying constraint that means we don't have to process the
  87                      * qualified name to insert '$' characters at the relevant positions.
  88                      */
  89                     String msg = String.format("Service provider class %s must be a top level class", serviceProvider.getSimpleName());
  90                     processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider);
  91                 } else {
  92                     /*
  93                      * Since the definition of the service class is not necessarily modifiable, we
  94                      * need to support a non-top-level service class and ensure its name is properly
  95                      * expressed with '$' separating nesting levels instead of '.'.
  96                      */
  97                     TypeElement serviceElement = (TypeElement) processingEnv.getTypeUtils().asElement(service);
  98                     String serviceName = serviceElement.getSimpleName().toString();
  99                     Element enclosing = serviceElement.getEnclosingElement();
 100                     while (enclosing != null) {
 101                         final ElementKind kind = enclosing.getKind();
 102                         if (kind == ElementKind.PACKAGE) {
 103                             serviceName = ((PackageElement) enclosing).getQualifiedName().toString() + "." + serviceName;
 104                             break;
 105                         } else if (kind == ElementKind.CLASS || kind == ElementKind.INTERFACE) {
 106                             serviceName = ((TypeElement) enclosing).getSimpleName().toString() + "$" + serviceName;
 107                             enclosing = enclosing.getEnclosingElement();
 108                         } else {
 109                             String msg = String.format("Cannot generate provider descriptor for service class %s as it is not nested in a package, class or interface",
 110                                             serviceElement.getQualifiedName());
 111                             processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider);
 112                             return;
 113                         }
 114                     }
 115                     serviceProviders.put(serviceProvider, serviceName);
 116                 }
 117             }
 118         }
 119     }
 120 
 121     @Override
 122     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 123         if (roundEnv.processingOver()) {
 124             for (Entry<TypeElement, String> e : serviceProviders.entrySet()) {
 125                 createProviderFile(e.getKey().getQualifiedName().toString(), e.getValue(), e.getKey());
 126             }
 127             serviceProviders.clear();
 128             return true;
 129         }
 130 
 131         TypeElement serviceProviderTypeElement = getTypeElement(SERVICE_PROVIDER_CLASS_NAME);
 132         for (Element element : roundEnv.getElementsAnnotatedWith(serviceProviderTypeElement)) {
 133             assert element.getKind().isClass();
 134             processElement((TypeElement) element);
 135         }
 136 
 137         return true;
 138     }
 139 }