• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2008 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.auto.service.processor;
17 
18 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
19 import static com.google.auto.common.MoreElements.getAnnotationMirror;
20 import static com.google.auto.common.MoreStreams.toImmutableSet;
21 import static com.google.common.base.Throwables.getStackTraceAsString;
22 
23 import com.google.auto.common.MoreElements;
24 import com.google.auto.common.MoreTypes;
25 import com.google.auto.service.AutoService;
26 import com.google.common.annotations.VisibleForTesting;
27 import com.google.common.collect.HashMultimap;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.Multimap;
31 import com.google.common.collect.Sets;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.SortedSet;
41 import javax.annotation.processing.AbstractProcessor;
42 import javax.annotation.processing.Filer;
43 import javax.annotation.processing.RoundEnvironment;
44 import javax.annotation.processing.SupportedOptions;
45 import javax.lang.model.SourceVersion;
46 import javax.lang.model.element.AnnotationMirror;
47 import javax.lang.model.element.AnnotationValue;
48 import javax.lang.model.element.Element;
49 import javax.lang.model.element.Modifier;
50 import javax.lang.model.element.PackageElement;
51 import javax.lang.model.element.TypeElement;
52 import javax.lang.model.type.DeclaredType;
53 import javax.lang.model.type.TypeMirror;
54 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
55 import javax.lang.model.util.Types;
56 import javax.tools.Diagnostic.Kind;
57 import javax.tools.FileObject;
58 import javax.tools.StandardLocation;
59 
60 /**
61  * Processes {@link AutoService} annotations and generates the service provider
62  * configuration files described in {@link java.util.ServiceLoader}.
63  * <p>
64  * Processor Options:<ul>
65  *   <li>{@code -Adebug} - turns on debug statements</li>
66  *   <li>{@code -Averify=true} - turns on extra verification</li>
67  * </ul>
68  */
69 @SupportedOptions({"debug", "verify"})
70 public class AutoServiceProcessor extends AbstractProcessor {
71 
72   @VisibleForTesting
73   static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!";
74 
75   private final List<String> exceptionStacks = Collections.synchronizedList(new ArrayList<>());
76 
77   /**
78    * Maps the class names of service provider interfaces to the
79    * class names of the concrete classes which implement them.
80    * <p>
81    * For example,
82    *   {@code "com.google.apphosting.LocalRpcService" ->
83    *   "com.google.apphosting.datastore.LocalDatastoreService"}
84    */
85   private final Multimap<String, String> providers = HashMultimap.create();
86 
87   @Override
getSupportedAnnotationTypes()88   public ImmutableSet<String> getSupportedAnnotationTypes() {
89     return ImmutableSet.of(AutoService.class.getName());
90   }
91 
92   @Override
getSupportedSourceVersion()93   public SourceVersion getSupportedSourceVersion() {
94     return SourceVersion.latestSupported();
95   }
96 
97   /**
98    * <ol>
99    *  <li> For each class annotated with {@link AutoService}<ul>
100    *      <li> Verify the {@link AutoService} interface value is correct
101    *      <li> Categorize the class by its service interface
102    *      </ul>
103    *
104    *  <li> For each {@link AutoService} interface <ul>
105    *       <li> Create a file named {@code META-INF/services/<interface>}
106    *       <li> For each {@link AutoService} annotated class for this interface <ul>
107    *           <li> Create an entry in the file
108    *           </ul>
109    *       </ul>
110    * </ol>
111    */
112   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)113   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
114     try {
115       processImpl(annotations, roundEnv);
116     } catch (RuntimeException e) {
117       // We don't allow exceptions of any kind to propagate to the compiler
118       String trace = getStackTraceAsString(e);
119       exceptionStacks.add(trace);
120       fatalError(trace);
121     }
122     return false;
123   }
124 
exceptionStacks()125   ImmutableList<String> exceptionStacks() {
126     return ImmutableList.copyOf(exceptionStacks);
127   }
128 
processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)129   private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
130     if (roundEnv.processingOver()) {
131       generateConfigFiles();
132     } else {
133       processAnnotations(annotations, roundEnv);
134     }
135   }
136 
processAnnotations( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)137   private void processAnnotations(
138       Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
139 
140     Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
141 
142     log(annotations.toString());
143     log(elements.toString());
144 
145     for (Element e : elements) {
146       // TODO(gak): check for error trees?
147       TypeElement providerImplementer = MoreElements.asType(e);
148       AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
149       Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
150       if (providerInterfaces.isEmpty()) {
151         error(MISSING_SERVICES_ERROR, e, annotationMirror);
152         continue;
153       }
154       for (DeclaredType providerInterface : providerInterfaces) {
155         TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
156 
157         log("provider interface: " + providerType.getQualifiedName());
158         log("provider implementer: " + providerImplementer.getQualifiedName());
159 
160         if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
161           providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
162         } else {
163           String message =
164               "ServiceProviders must implement their service provider interface. "
165                   + providerImplementer.getQualifiedName()
166                   + " does not implement "
167                   + providerType.getQualifiedName();
168           error(message, e, annotationMirror);
169         }
170       }
171     }
172   }
173 
generateConfigFiles()174   private void generateConfigFiles() {
175     Filer filer = processingEnv.getFiler();
176 
177     for (String providerInterface : providers.keySet()) {
178       String resourceFile = "META-INF/services/" + providerInterface;
179       log("Working on resource file: " + resourceFile);
180       try {
181         SortedSet<String> allServices = Sets.newTreeSet();
182         try {
183           // would like to be able to print the full path
184           // before we attempt to get the resource in case the behavior
185           // of filer.getResource does change to match the spec, but there's
186           // no good way to resolve CLASS_OUTPUT without first getting a resource.
187           FileObject existingFile =
188               filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
189           log("Looking for existing resource file at " + existingFile.toUri());
190           Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
191           log("Existing service entries: " + oldServices);
192           allServices.addAll(oldServices);
193         } catch (IOException e) {
194           // According to the javadoc, Filer.getResource throws an exception
195           // if the file doesn't already exist.  In practice this doesn't
196           // appear to be the case.  Filer.getResource will happily return a
197           // FileObject that refers to a non-existent file but will throw
198           // IOException if you try to open an input stream for it.
199           log("Resource file did not already exist.");
200         }
201 
202         Set<String> newServices = new HashSet<>(providers.get(providerInterface));
203         if (!allServices.addAll(newServices)) {
204           log("No new service entries being added.");
205           continue;
206         }
207 
208         log("New service file contents: " + allServices);
209         FileObject fileObject =
210             filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
211         try (OutputStream out = fileObject.openOutputStream()) {
212           ServicesFiles.writeServiceFile(allServices, out);
213         }
214         log("Wrote to: " + fileObject.toUri());
215       } catch (IOException e) {
216         fatalError("Unable to create " + resourceFile + ", " + e);
217         return;
218       }
219     }
220   }
221 
222   /**
223    * Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these
224    * constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile
225    * time to be extra nice to our users.
226    */
checkImplementer( TypeElement providerImplementer, TypeElement providerType, AnnotationMirror annotationMirror)227   private boolean checkImplementer(
228       TypeElement providerImplementer,
229       TypeElement providerType,
230       AnnotationMirror annotationMirror) {
231 
232     if (!Boolean.parseBoolean(processingEnv.getOptions().getOrDefault("verify", "true"))
233         || suppresses(providerImplementer, "AutoService")) {
234       return true;
235     }
236 
237     // We check that providerImplementer does indeed inherit from providerType, and that it is not
238     // abstract (an abstract class or interface). For ServiceLoader, we could also check that it has
239     // a public no-arg constructor. But it turns out that people also use AutoService in contexts
240     // where the META-INF/services entries are read by things other than ServiceLoader. Those things
241     // still require the class to exist and inherit from providerType, but they don't necessarily
242     // require a public no-arg constructor.
243     // More background: https://github.com/google/auto/issues/1505.
244 
245     Types types = processingEnv.getTypeUtils();
246 
247     if (types.isSubtype(providerImplementer.asType(), providerType.asType())) {
248       return checkNotAbstract(providerImplementer, annotationMirror);
249     }
250 
251     // Maybe the provider has generic type, but the argument to @AutoService can't be generic.
252     // So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes").
253     // See https://github.com/google/auto/issues/870.
254     if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) {
255       if (!suppresses(providerImplementer, "rawtypes")) {
256         warning(
257             "Service provider "
258                 + providerType
259                 + " is generic, so it can't be named exactly by @AutoService."
260                 + " If this is OK, add @SuppressWarnings(\"rawtypes\").",
261             providerImplementer,
262             annotationMirror);
263       }
264       return checkNotAbstract(providerImplementer, annotationMirror);
265     }
266 
267     String message =
268         "ServiceProviders must implement their service provider interface. "
269             + providerImplementer.getQualifiedName()
270             + " does not implement "
271             + providerType.getQualifiedName();
272     error(message, providerImplementer, annotationMirror);
273 
274     return false;
275   }
276 
checkNotAbstract( TypeElement providerImplementer, AnnotationMirror annotationMirror)277   private boolean checkNotAbstract(
278       TypeElement providerImplementer, AnnotationMirror annotationMirror) {
279     if (providerImplementer.getModifiers().contains(Modifier.ABSTRACT)) {
280       error(
281           "@AutoService cannot be applied to an abstract class or an interface",
282           providerImplementer,
283           annotationMirror);
284       return false;
285     }
286     return true;
287   }
288 
suppresses(Element element, String warning)289   private static boolean suppresses(Element element, String warning) {
290     for (; element != null; element = element.getEnclosingElement()) {
291       SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
292       if (suppress != null && Arrays.asList(suppress.value()).contains(warning)) {
293         return true;
294       }
295     }
296     return false;
297   }
298 
299   /**
300    * Returns the binary name of a reference type. For example,
301    * {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}.
302    *
303    */
getBinaryName(TypeElement element)304   private String getBinaryName(TypeElement element) {
305     return getBinaryNameImpl(element, element.getSimpleName().toString());
306   }
307 
getBinaryNameImpl(TypeElement element, String className)308   private String getBinaryNameImpl(TypeElement element, String className) {
309     Element enclosingElement = element.getEnclosingElement();
310 
311     if (enclosingElement instanceof PackageElement) {
312       PackageElement pkg = MoreElements.asPackage(enclosingElement);
313       if (pkg.isUnnamed()) {
314         return className;
315       }
316       return pkg.getQualifiedName() + "." + className;
317     }
318 
319     TypeElement typeElement = MoreElements.asType(enclosingElement);
320     return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
321   }
322 
323   /**
324    * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code
325    * annotationMirror}.
326    */
getValueFieldOfClasses(AnnotationMirror annotationMirror)327   private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
328     return getAnnotationValue(annotationMirror, "value")
329         .accept(
330             new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) {
331               @Override
332               public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) {
333                 // TODO(ronshapiro): class literals may not always be declared types, i.e.
334                 // int.class, int[].class
335                 return ImmutableSet.of(MoreTypes.asDeclared(typeMirror));
336               }
337 
338               @Override
339               public ImmutableSet<DeclaredType> visitArray(
340                   List<? extends AnnotationValue> values, Void v) {
341                 return values.stream()
342                     .flatMap(value -> value.accept(this, null).stream())
343                     .collect(toImmutableSet());
344               }
345             },
346             null);
347   }
348 
349   private void log(String msg) {
350     if (processingEnv.getOptions().containsKey("debug")) {
351       processingEnv.getMessager().printMessage(Kind.NOTE, msg);
352     }
353   }
354 
355   private void warning(String msg, Element element, AnnotationMirror annotation) {
356     processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation);
357   }
358 
359   private void error(String msg, Element element, AnnotationMirror annotation) {
360     processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
361   }
362 
363   private void fatalError(String msg) {
364     processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
365   }
366 }
367