20 * or visit www.oracle.com if you need additional information or have any |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 package org.graalvm.compiler.serviceprovider.processor; |
23 package org.graalvm.compiler.serviceprovider.processor; |
24 |
24 |
25 import java.io.IOException; |
|
26 import java.io.OutputStreamWriter; |
|
27 import java.io.PrintWriter; |
|
28 import java.util.HashMap; |
25 import java.util.HashMap; |
29 import java.util.HashSet; |
26 import java.util.HashSet; |
30 import java.util.Map; |
27 import java.util.Map; |
31 import java.util.Map.Entry; |
28 import java.util.Map.Entry; |
32 import java.util.Set; |
29 import java.util.Set; |
33 |
30 |
34 import javax.annotation.processing.AbstractProcessor; |
|
35 import javax.annotation.processing.FilerException; |
|
36 import javax.annotation.processing.RoundEnvironment; |
31 import javax.annotation.processing.RoundEnvironment; |
37 import javax.annotation.processing.SupportedAnnotationTypes; |
32 import javax.annotation.processing.SupportedAnnotationTypes; |
38 import javax.lang.model.SourceVersion; |
33 import javax.lang.model.SourceVersion; |
|
34 import javax.lang.model.element.AnnotationMirror; |
39 import javax.lang.model.element.Element; |
35 import javax.lang.model.element.Element; |
40 import javax.lang.model.element.ElementKind; |
36 import javax.lang.model.element.ElementKind; |
41 import javax.lang.model.element.PackageElement; |
37 import javax.lang.model.element.PackageElement; |
42 import javax.lang.model.element.TypeElement; |
38 import javax.lang.model.element.TypeElement; |
43 import javax.lang.model.type.MirroredTypeException; |
|
44 import javax.lang.model.type.TypeMirror; |
39 import javax.lang.model.type.TypeMirror; |
45 import javax.tools.Diagnostic.Kind; |
40 import javax.tools.Diagnostic.Kind; |
46 import javax.tools.FileObject; |
|
47 import javax.tools.StandardLocation; |
|
48 |
41 |
49 import org.graalvm.compiler.serviceprovider.ServiceProvider; |
42 import org.graalvm.compiler.processor.AbstractProcessor; |
50 |
43 |
51 /** |
44 /** |
52 * Processes classes annotated with {@link ServiceProvider}. For a service defined by {@code S} and |
45 * Processes classes annotated with {@code ServiceProvider}. For a service defined by {@code S} and |
53 * a class {@code P} implementing the service, this processor generates the file |
46 * a class {@code P} implementing the service, this processor generates the file |
54 * {@code META-INF/providers/P} whose contents are a single line containing the fully qualified name |
47 * {@code META-INF/providers/P} whose contents are a single line containing the fully qualified name |
55 * of {@code S}. |
48 * of {@code S}. |
56 */ |
49 */ |
57 @SupportedAnnotationTypes("org.graalvm.compiler.serviceprovider.ServiceProvider") |
50 @SupportedAnnotationTypes("org.graalvm.compiler.serviceprovider.ServiceProvider") |
58 public class ServiceProviderProcessor extends AbstractProcessor { |
51 public class ServiceProviderProcessor extends AbstractProcessor { |
59 |
52 |
|
53 private static final String SERVICE_PROVIDER_CLASS_NAME = "org.graalvm.compiler.serviceprovider.ServiceProvider"; |
60 private final Set<TypeElement> processed = new HashSet<>(); |
54 private final Set<TypeElement> processed = new HashSet<>(); |
61 private final Map<TypeElement, String> serviceProviders = new HashMap<>(); |
55 private final Map<TypeElement, String> serviceProviders = new HashMap<>(); |
62 |
56 |
63 @Override |
57 @Override |
64 public SourceVersion getSupportedSourceVersion() { |
58 public SourceVersion getSupportedSourceVersion() { |
79 if (processed.contains(serviceProvider)) { |
73 if (processed.contains(serviceProvider)) { |
80 return; |
74 return; |
81 } |
75 } |
82 |
76 |
83 processed.add(serviceProvider); |
77 processed.add(serviceProvider); |
84 ServiceProvider annotation = serviceProvider.getAnnotation(ServiceProvider.class); |
78 AnnotationMirror annotation = getAnnotation(serviceProvider, getType(SERVICE_PROVIDER_CLASS_NAME)); |
85 if (annotation != null) { |
79 if (annotation != null) { |
86 try { |
80 TypeMirror service = getAnnotationValue(annotation, "value", TypeMirror.class); |
87 annotation.value(); |
81 if (verifyAnnotation(service, serviceProvider)) { |
88 } catch (MirroredTypeException ex) { |
82 if (serviceProvider.getNestingKind().isNested()) { |
89 TypeMirror service = ex.getTypeMirror(); |
83 /* |
90 if (verifyAnnotation(service, serviceProvider)) { |
84 * This is a simplifying constraint that means we don't have to process the |
91 if (serviceProvider.getNestingKind().isNested()) { |
85 * qualified name to insert '$' characters at the relevant positions. |
92 /* |
86 */ |
93 * This is a simplifying constraint that means we don't have to process the |
87 String msg = String.format("Service provider class %s must be a top level class", serviceProvider.getSimpleName()); |
94 * qualified name to insert '$' characters at the relevant positions. |
88 processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider); |
95 */ |
89 } else { |
96 String msg = String.format("Service provider class %s must be a top level class", serviceProvider.getSimpleName()); |
90 /* |
97 processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider); |
91 * Since the definition of the service class is not necessarily modifiable, we |
98 } else { |
92 * need to support a non-top-level service class and ensure its name is properly |
99 /* |
93 * expressed with '$' separating nesting levels instead of '.'. |
100 * Since the definition of the service class is not necessarily modifiable, |
94 */ |
101 * we need to support a non-top-level service class and ensure its name is |
95 TypeElement serviceElement = (TypeElement) processingEnv.getTypeUtils().asElement(service); |
102 * properly expressed with '$' separating nesting levels instead of '.'. |
96 String serviceName = serviceElement.getSimpleName().toString(); |
103 */ |
97 Element enclosing = serviceElement.getEnclosingElement(); |
104 TypeElement serviceElement = (TypeElement) processingEnv.getTypeUtils().asElement(service); |
98 while (enclosing != null) { |
105 String serviceName = serviceElement.getSimpleName().toString(); |
99 final ElementKind kind = enclosing.getKind(); |
106 Element enclosing = serviceElement.getEnclosingElement(); |
100 if (kind == ElementKind.PACKAGE) { |
107 while (enclosing != null) { |
101 serviceName = ((PackageElement) enclosing).getQualifiedName().toString() + "." + serviceName; |
108 final ElementKind kind = enclosing.getKind(); |
102 break; |
109 if (kind == ElementKind.PACKAGE) { |
103 } else if (kind == ElementKind.CLASS || kind == ElementKind.INTERFACE) { |
110 serviceName = ((PackageElement) enclosing).getQualifiedName().toString() + "." + serviceName; |
104 serviceName = ((TypeElement) enclosing).getSimpleName().toString() + "$" + serviceName; |
111 break; |
105 enclosing = enclosing.getEnclosingElement(); |
112 } else if (kind == ElementKind.CLASS || kind == ElementKind.INTERFACE) { |
106 } else { |
113 serviceName = ((TypeElement) enclosing).getSimpleName().toString() + "$" + serviceName; |
107 String msg = String.format("Cannot generate provider descriptor for service class %s as it is not nested in a package, class or interface", |
114 enclosing = enclosing.getEnclosingElement(); |
108 serviceElement.getQualifiedName()); |
115 } else { |
109 processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider); |
116 String msg = String.format("Cannot generate provider descriptor for service class %s as it is not nested in a package, class or interface", |
110 return; |
117 serviceElement.getQualifiedName()); |
|
118 processingEnv.getMessager().printMessage(Kind.ERROR, msg, serviceProvider); |
|
119 return; |
|
120 } |
|
121 } |
111 } |
122 serviceProviders.put(serviceProvider, serviceName); |
|
123 } |
112 } |
|
113 serviceProviders.put(serviceProvider, serviceName); |
124 } |
114 } |
125 } |
115 } |
126 } |
116 } |
127 } |
|
128 |
|
129 private void writeProviderFile(TypeElement serviceProvider, String interfaceName) { |
|
130 String filename = "META-INF/providers/" + serviceProvider.getQualifiedName(); |
|
131 try { |
|
132 FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", filename, serviceProvider); |
|
133 PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8")); |
|
134 writer.println(interfaceName); |
|
135 writer.close(); |
|
136 } catch (IOException e) { |
|
137 processingEnv.getMessager().printMessage(isBug367599(e) ? Kind.NOTE : Kind.ERROR, e.getMessage(), serviceProvider); |
|
138 } |
|
139 } |
|
140 |
|
141 /** |
|
142 * Determines if a given exception is (most likely) caused by |
|
143 * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>. |
|
144 */ |
|
145 private static boolean isBug367599(Throwable t) { |
|
146 if (t instanceof FilerException) { |
|
147 for (StackTraceElement ste : t.getStackTrace()) { |
|
148 if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) { |
|
149 // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599 |
|
150 return true; |
|
151 } |
|
152 } |
|
153 } |
|
154 return t.getCause() != null && isBug367599(t.getCause()); |
|
155 } |
117 } |
156 |
118 |
157 @Override |
119 @Override |
158 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
120 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
159 if (roundEnv.processingOver()) { |
121 if (roundEnv.processingOver()) { |
160 for (Entry<TypeElement, String> e : serviceProviders.entrySet()) { |
122 for (Entry<TypeElement, String> e : serviceProviders.entrySet()) { |
161 writeProviderFile(e.getKey(), e.getValue()); |
123 createProviderFile(e.getKey().getQualifiedName().toString(), e.getValue(), e.getKey()); |
162 } |
124 } |
163 serviceProviders.clear(); |
125 serviceProviders.clear(); |
164 return true; |
126 return true; |
165 } |
127 } |
166 |
128 |
167 for (Element element : roundEnv.getElementsAnnotatedWith(ServiceProvider.class)) { |
129 TypeElement serviceProviderTypeElement = getTypeElement(SERVICE_PROVIDER_CLASS_NAME); |
|
130 for (Element element : roundEnv.getElementsAnnotatedWith(serviceProviderTypeElement)) { |
168 assert element.getKind().isClass(); |
131 assert element.getKind().isClass(); |
169 processElement((TypeElement) element); |
132 processElement((TypeElement) element); |
170 } |
133 } |
171 |
134 |
172 return true; |
135 return true; |