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 doProcess(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 }