Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / spi / ServiceProviderProcessor.java
blobba09bdbc90939bd13cc0cc74175659907d29a6b2
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;
8 import java.io.File;
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;
16 import java.util.Map;
17 import java.util.Set;
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;
38 /**
39 * Processes {@link ServiceProvider} annotations and generates the service provider
40 * configuration files described in {@link java.util.ServiceLoader}.
41 * <p>
42 * Processor Options:<ul>
43 * <li>debug - turns on debug statements</li>
44 * </ul>
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" +
53 File.separator;
55 private static class VerifyException extends Exception {
56 VerifyException(String message) {
57 super(message);
61 /**
62 * Maps the class names of service provider interfaces to the
63 * class names of the concrete classes which implement them.
64 * <p>
65 * For example,
66 * {@code "com.google.apphosting.LocalRpcService" ->
67 * "com.google.apphosting.datastore.LocalDatastoreService"}
69 private Multimap<String, String> providers = HashMultimap.create();
71 /**
72 * <ol>
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
76 * </ul>
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
82 * </ul>
83 * </ul>
84 * </ol>
86 @Override
87 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
88 try {
89 return processImpl(annotations, roundEnv);
90 } catch (Exception e) {
91 StringWriter writer = new StringWriter();
92 e.printStackTrace(new PrintWriter(writer));
93 fatalError(writer.toString());
94 return true;
98 private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
99 if (roundEnv.processingOver()) {
100 generateConfigFiles();
101 } else {
102 processAnnotations(annotations, roundEnv);
105 return true;
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());
125 try {
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);
146 try {
147 Set<String> allServices = new HashSet<String>();
148 try {
149 FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
150 resourceFile);
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.");
162 return;
165 allServices.addAll(newServices);
166 log("New service file contents: " + allServices);
167 FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
168 resourceFile);
169 OutputStream out = fileObject.openOutputStream();
170 ServicesFile.writeServiceFile(allServices, out);
171 out.close();
172 log("Wrote to: " + fileObject.toUri());
173 } catch (IOException e) {
174 fatalError("Unable to create " + resourceFile + ", " + e);
175 return;
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)) {
191 return;
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()) {
218 return className;
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())) {
244 return mirror;
245 } else {
246 log("klass name: [" + klass.getName() + "]");
247 log("type name: [" + typeElement.getQualifiedName() + "]");
250 return null;
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);