1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.spi
;
5 import com
.google
.common
.collect
.HashMultimap
;
6 import com
.google
.common
.collect
.Multimap
;
9 import java
.io
.IOException
;
10 import java
.io
.OutputStream
;
11 import java
.io
.PrintWriter
;
12 import java
.io
.StringWriter
;
13 import java
.lang
.annotation
.Annotation
;
14 import java
.util
.HashSet
;
15 import java
.util
.List
;
19 import javax
.annotation
.processing
.AbstractProcessor
;
20 import javax
.annotation
.processing
.Filer
;
21 import javax
.annotation
.processing
.RoundEnvironment
;
22 import javax
.annotation
.processing
.SupportedAnnotationTypes
;
23 import javax
.annotation
.processing
.SupportedOptions
;
24 import javax
.annotation
.processing
.SupportedSourceVersion
;
25 import javax
.lang
.model
.SourceVersion
;
26 import javax
.lang
.model
.element
.AnnotationMirror
;
27 import javax
.lang
.model
.element
.AnnotationValue
;
28 import javax
.lang
.model
.element
.Element
;
29 import javax
.lang
.model
.element
.ExecutableElement
;
30 import javax
.lang
.model
.element
.PackageElement
;
31 import javax
.lang
.model
.element
.TypeElement
;
32 import javax
.lang
.model
.type
.DeclaredType
;
33 import javax
.lang
.model
.util
.Types
;
34 import javax
.tools
.Diagnostic
.Kind
;
35 import javax
.tools
.FileObject
;
36 import javax
.tools
.StandardLocation
;
39 * Processes {@link ServiceProvider} annotations and generates the service provider
40 * configuration files described in {@link java.util.ServiceLoader}.
42 * Processor Options:<ul>
43 * <li>debug - turns on debug statements</li>
47 @SupportedAnnotationTypes("com.google.appengine.spi.ServiceProvider")
48 @SupportedSourceVersion(SourceVersion
.RELEASE_6
)
49 @SupportedOptions({"debug", "verify"})
50 public class ServiceProviderProcessor
extends AbstractProcessor
{
52 private static final String SERVICE_DIR
= "META-INF" + File
.separator
+ "services" +
55 private static class VerifyException
extends Exception
{
56 VerifyException(String message
) {
62 * Maps the class names of service provider interfaces to the
63 * class names of the concrete classes which implement them.
66 * {@code "com.google.apphosting.LocalRpcService" ->
67 * "com.google.apphosting.datastore.LocalDatastoreService"}
69 private Multimap
<String
, String
> providers
= HashMultimap
.create();
73 * <li> For each class annotated with @ServiceProvider<ul>
74 * <li> Verify the @ServiceProvider interface value is correct
75 * <li> Categorize the class by its ServiceProvider interface
78 * <li> For each @ServiceProvider interface <ul>
79 * <li> Create a file named {@code META-INF/services/<interface>}
80 * <li> For each @ServiceProvider annotated class for this interface <ul>
81 * <li> Create an entry in the file
87 public boolean process(Set
<?
extends TypeElement
> annotations
, RoundEnvironment roundEnv
) {
89 return processImpl(annotations
, roundEnv
);
90 } catch (Exception e
) {
91 StringWriter writer
= new StringWriter();
92 e
.printStackTrace(new PrintWriter(writer
));
93 fatalError(writer
.toString());
98 private boolean processImpl(Set
<?
extends TypeElement
> annotations
, RoundEnvironment roundEnv
) {
99 if (roundEnv
.processingOver()) {
100 generateConfigFiles();
102 processAnnotations(annotations
, roundEnv
);
108 private void processAnnotations(Set
<?
extends TypeElement
> annotations
,
109 RoundEnvironment roundEnv
) {
111 Set
<?
extends Element
> elements
= roundEnv
.getElementsAnnotatedWith(ServiceProvider
.class);
113 log(annotations
.toString());
114 log(elements
.toString());
116 for (Element e
: elements
) {
117 TypeElement providerImplementer
= (TypeElement
) e
;
118 AnnotationMirror providerAnnotation
= getAnnotationMirror(e
, ServiceProvider
.class);
119 DeclaredType providerInterface
= getProviderInterface(providerAnnotation
);
120 TypeElement providerType
= (TypeElement
) providerInterface
.asElement();
122 log("provider interface: " + providerType
.getQualifiedName());
123 log("provider implementer: " + providerImplementer
.getQualifiedName());
126 verifyImplementer(providerImplementer
, providerType
);
127 } catch (VerifyException ex
) {
128 error(ex
.getMessage(), e
, providerAnnotation
);
131 String providerTypeName
= getBinaryName(providerType
);
132 String providerImplementerName
= getBinaryName(providerImplementer
);
133 log("provider interface binary name: " + providerTypeName
);
134 log("provider implementer binary name: " + providerImplementerName
);
136 providers
.put(providerTypeName
, providerImplementerName
);
140 private void generateConfigFiles() {
141 Filer filer
= processingEnv
.getFiler();
143 for (String providerInterface
: providers
.keySet()) {
144 String resourceFile
= SERVICE_DIR
+ providerInterface
;
145 log("Working on resource file: " + resourceFile
);
147 Set
<String
> allServices
= new HashSet
<String
>();
149 FileObject existingFile
= filer
.getResource(StandardLocation
.CLASS_OUTPUT
, "",
151 log("Looking for existing resource file at " + existingFile
.toUri());
152 Set
<String
> oldServices
= ServicesFile
.readServiceFile(existingFile
.openInputStream());
153 log("Existing service entries: " + oldServices
);
154 allServices
.addAll(oldServices
);
155 } catch (IOException e
) {
156 log("Resource file did not already exist.");
159 Set
<String
> newServices
= new HashSet
<String
>(providers
.get(providerInterface
));
160 if (allServices
.containsAll(newServices
)) {
161 log("No new service entries being added.");
165 allServices
.addAll(newServices
);
166 log("New service file contents: " + allServices
);
167 FileObject fileObject
= filer
.createResource(StandardLocation
.CLASS_OUTPUT
, "",
169 OutputStream out
= fileObject
.openOutputStream();
170 ServicesFile
.writeServiceFile(allServices
, out
);
172 log("Wrote to: " + fileObject
.toUri());
173 } catch (IOException e
) {
174 fatalError("Unable to create " + resourceFile
+ ", " + e
);
181 * Verifies {@link ServiceProvider} constraints on the concrete provider class.
182 * Note that these constraints are enforced at runtime via the ServiceLoader,
183 * we're just checking them at compile time to be extra nice to our users.
186 private void verifyImplementer(TypeElement providerImplementer
, TypeElement providerType
)
187 throws VerifyException
{
189 String verify
= processingEnv
.getOptions().get("verify");
190 if (verify
== null || !Boolean
.valueOf(verify
)) {
194 Types types
= processingEnv
.getTypeUtils();
196 if (!types
.isSubtype(providerImplementer
.asType(), providerType
.asType())) {
197 throw new VerifyException("ServiceProviders must implement their service provider interface. "
198 + providerImplementer
.getQualifiedName() + " does not implement "
199 + providerType
.getQualifiedName());
204 * Returns the binary name of a reference type. For example,
205 * {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}.
208 private String
getBinaryName(TypeElement element
) {
209 return getBinaryNameImpl(element
, element
.getSimpleName().toString());
212 private String
getBinaryNameImpl(TypeElement element
, String className
) {
213 Element enclosingElement
= element
.getEnclosingElement();
215 if (enclosingElement
instanceof PackageElement
) {
216 PackageElement pkg
= (PackageElement
) enclosingElement
;
217 if (pkg
.isUnnamed()) {
220 return pkg
.getQualifiedName() + "." + className
;
223 TypeElement typeElement
= (TypeElement
) enclosingElement
;
224 return getBinaryNameImpl(typeElement
, typeElement
.getSimpleName() + "$" + className
);
227 private DeclaredType
getProviderInterface(AnnotationMirror providerAnnotation
) {
229 Map
<?
extends ExecutableElement
, ?
extends AnnotationValue
> values
=
230 providerAnnotation
.getElementValues();
231 log("annotation values: " + values
);
233 AnnotationValue value
= values
.values().iterator().next();
234 return (DeclaredType
) value
.getValue();
237 private AnnotationMirror
getAnnotationMirror(Element e
, Class
<?
extends Annotation
> klass
) {
238 List
<?
extends AnnotationMirror
> annotationMirrors
= e
.getAnnotationMirrors();
239 for (AnnotationMirror mirror
: annotationMirrors
) {
240 log("mirror: " + mirror
);
241 DeclaredType type
= mirror
.getAnnotationType();
242 TypeElement typeElement
= (TypeElement
) type
.asElement();
243 if (typeElement
.getQualifiedName().contentEquals(klass
.getName())) {
246 log("klass name: [" + klass
.getName() + "]");
247 log("type name: [" + typeElement
.getQualifiedName() + "]");
253 private void log(String msg
) {
254 if (processingEnv
.getOptions().containsKey("debug")) {
255 processingEnv
.getMessager().printMessage(Kind
.NOTE
, msg
);
259 private void error(String msg
, Element element
, AnnotationMirror annotation
) {
260 processingEnv
.getMessager().printMessage(Kind
.ERROR
, msg
, element
, annotation
);
263 private void fatalError(String msg
) {
264 processingEnv
.getMessager().printMessage(Kind
.ERROR
, "FATAL ERROR: " + msg
);