• 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.PackageElement;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.type.DeclaredType;
52 import javax.lang.model.type.TypeMirror;
53 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
54 import javax.lang.model.util.Types;
55 import javax.tools.Diagnostic.Kind;
56 import javax.tools.FileObject;
57 import javax.tools.StandardLocation;
58 
59 /**
60  * Processes {@link AutoService} annotations and generates the service provider
61  * configuration files described in {@link java.util.ServiceLoader}.
62  * <p>
63  * Processor Options:<ul>
64  *   <li>{@code -Adebug} - turns on debug statements</li>
65  *   <li>{@code -Averify=true} - turns on extra verification</li>
66  * </ul>
67  */
68 @SupportedOptions({"debug", "verify"})
69 public class AutoServiceProcessor extends AbstractProcessor {
70 
71   @VisibleForTesting
72   static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!";
73 
74   private final List<String> exceptionStacks = Collections.synchronizedList(new ArrayList<>());
75 
76   /**
77    * Maps the class names of service provider interfaces to the
78    * class names of the concrete classes which implement them.
79    * <p>
80    * For example,
81    *   {@code "com.google.apphosting.LocalRpcService" ->
82    *   "com.google.apphosting.datastore.LocalDatastoreService"}
83    */
84   private final Multimap<String, String> providers = HashMultimap.create();
85 
86   @Override
getSupportedAnnotationTypes()87   public ImmutableSet<String> getSupportedAnnotationTypes() {
88     return ImmutableSet.of(AutoService.class.getName());
89   }
90 
91   @Override
getSupportedSourceVersion()92   public SourceVersion getSupportedSourceVersion() {
93     return SourceVersion.latestSupported();
94   }
95 
96   /**
97    * <ol>
98    *  <li> For each class annotated with {@link AutoService}<ul>
99    *      <li> Verify the {@link AutoService} interface value is correct
100    *      <li> Categorize the class by its service interface
101    *      </ul>
102    *
103    *  <li> For each {@link AutoService} interface <ul>
104    *       <li> Create a file named {@code META-INF/services/<interface>}
105    *       <li> For each {@link AutoService} annotated class for this interface <ul>
106    *           <li> Create an entry in the file
107    *           </ul>
108    *       </ul>
109    * </ol>
110    */
111   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)112   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
113     try {
114       processImpl(annotations, roundEnv);
115     } catch (RuntimeException e) {
116       // We don't allow exceptions of any kind to propagate to the compiler
117       String trace = getStackTraceAsString(e);
118       exceptionStacks.add(trace);
119       fatalError(trace);
120     }
121     return false;
122   }
123 
exceptionStacks()124   ImmutableList<String> exceptionStacks() {
125     return ImmutableList.copyOf(exceptionStacks);
126   }
127 
processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)128   private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
129     if (roundEnv.processingOver()) {
130       generateConfigFiles();
131     } else {
132       processAnnotations(annotations, roundEnv);
133     }
134   }
135 
processAnnotations( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)136   private void processAnnotations(
137       Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
138 
139     Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
140 
141     log(annotations.toString());
142     log(elements.toString());
143 
144     for (Element e : elements) {
145       // TODO(gak): check for error trees?
146       TypeElement providerImplementer = MoreElements.asType(e);
147       AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
148       Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
149       if (providerInterfaces.isEmpty()) {
150         error(MISSING_SERVICES_ERROR, e, annotationMirror);
151         continue;
152       }
153       for (DeclaredType providerInterface : providerInterfaces) {
154         TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
155 
156         log("provider interface: " + providerType.getQualifiedName());
157         log("provider implementer: " + providerImplementer.getQualifiedName());
158 
159         if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
160           providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
161         } else {
162           String message =
163               "ServiceProviders must implement their service provider interface. "
164                   + providerImplementer.getQualifiedName()
165                   + " does not implement "
166                   + providerType.getQualifiedName();
167           error(message, e, annotationMirror);
168         }
169       }
170     }
171   }
172 
generateConfigFiles()173   private void generateConfigFiles() {
174     Filer filer = processingEnv.getFiler();
175 
176     for (String providerInterface : providers.keySet()) {
177       String resourceFile = "META-INF/services/" + providerInterface;
178       log("Working on resource file: " + resourceFile);
179       try {
180         SortedSet<String> allServices = Sets.newTreeSet();
181         try {
182           // would like to be able to print the full path
183           // before we attempt to get the resource in case the behavior
184           // of filer.getResource does change to match the spec, but there's
185           // no good way to resolve CLASS_OUTPUT without first getting a resource.
186           FileObject existingFile =
187               filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
188           log("Looking for existing resource file at " + existingFile.toUri());
189           Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
190           log("Existing service entries: " + oldServices);
191           allServices.addAll(oldServices);
192         } catch (IOException e) {
193           // According to the javadoc, Filer.getResource throws an exception
194           // if the file doesn't already exist.  In practice this doesn't
195           // appear to be the case.  Filer.getResource will happily return a
196           // FileObject that refers to a non-existent file but will throw
197           // IOException if you try to open an input stream for it.
198           log("Resource file did not already exist.");
199         }
200 
201         Set<String> newServices = new HashSet<>(providers.get(providerInterface));
202         if (!allServices.addAll(newServices)) {
203           log("No new service entries being added.");
204           continue;
205         }
206 
207         log("New service file contents: " + allServices);
208         FileObject fileObject =
209             filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
210         try (OutputStream out = fileObject.openOutputStream()) {
211           ServicesFiles.writeServiceFile(allServices, out);
212         }
213         log("Wrote to: " + fileObject.toUri());
214       } catch (IOException e) {
215         fatalError("Unable to create " + resourceFile + ", " + e);
216         return;
217       }
218     }
219   }
220 
221   /**
222    * Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these
223    * constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile
224    * time to be extra nice to our users.
225    */
checkImplementer( TypeElement providerImplementer, TypeElement providerType, AnnotationMirror annotationMirror)226   private boolean checkImplementer(
227       TypeElement providerImplementer,
228       TypeElement providerType,
229       AnnotationMirror annotationMirror) {
230 
231     String verify = processingEnv.getOptions().get("verify");
232     if (verify == null || !Boolean.parseBoolean(verify)) {
233       return true;
234     }
235 
236     // TODO: We're currently only enforcing the subtype relationship
237     // constraint. It would be nice to enforce them all.
238 
239     Types types = processingEnv.getTypeUtils();
240 
241     if (types.isSubtype(providerImplementer.asType(), providerType.asType())) {
242       return true;
243     }
244 
245     // Maybe the provider has generic type, but the argument to @AutoService can't be generic.
246     // So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes").
247     // See https://github.com/google/auto/issues/870.
248     if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) {
249       if (!rawTypesSuppressed(providerImplementer)) {
250         warning(
251             "Service provider "
252                 + providerType
253                 + " is generic, so it can't be named exactly by @AutoService."
254                 + " If this is OK, add @SuppressWarnings(\"rawtypes\").",
255             providerImplementer,
256             annotationMirror);
257       }
258       return true;
259     }
260 
261     return false;
262   }
263 
rawTypesSuppressed(Element element)264   private static boolean rawTypesSuppressed(Element element) {
265     for (; element != null; element = element.getEnclosingElement()) {
266       SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
267       if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) {
268         return true;
269       }
270     }
271     return false;
272   }
273 
274   /**
275    * Returns the binary name of a reference type. For example,
276    * {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}.
277    *
278    */
getBinaryName(TypeElement element)279   private String getBinaryName(TypeElement element) {
280     return getBinaryNameImpl(element, element.getSimpleName().toString());
281   }
282 
getBinaryNameImpl(TypeElement element, String className)283   private String getBinaryNameImpl(TypeElement element, String className) {
284     Element enclosingElement = element.getEnclosingElement();
285 
286     if (enclosingElement instanceof PackageElement) {
287       PackageElement pkg = MoreElements.asPackage(enclosingElement);
288       if (pkg.isUnnamed()) {
289         return className;
290       }
291       return pkg.getQualifiedName() + "." + className;
292     }
293 
294     TypeElement typeElement = MoreElements.asType(enclosingElement);
295     return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
296   }
297 
298   /**
299    * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code
300    * annotationMirror}.
301    */
getValueFieldOfClasses(AnnotationMirror annotationMirror)302   private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
303     return getAnnotationValue(annotationMirror, "value")
304         .accept(
305             new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) {
306               @Override
307               public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) {
308                 // TODO(ronshapiro): class literals may not always be declared types, i.e.
309                 // int.class, int[].class
310                 return ImmutableSet.of(MoreTypes.asDeclared(typeMirror));
311               }
312 
313               @Override
314               public ImmutableSet<DeclaredType> visitArray(
315                   List<? extends AnnotationValue> values, Void v) {
316                 return values.stream()
317                     .flatMap(value -> value.accept(this, null).stream())
318                     .collect(toImmutableSet());
319               }
320             },
321             null);
322   }
323 
324   private void log(String msg) {
325     if (processingEnv.getOptions().containsKey("debug")) {
326       processingEnv.getMessager().printMessage(Kind.NOTE, msg);
327     }
328   }
329 
330   private void warning(String msg, Element element, AnnotationMirror annotation) {
331     processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation);
332   }
333 
334   private void error(String msg, Element element, AnnotationMirror annotation) {
335     processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
336   }
337 
338   private void fatalError(String msg) {
339     processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
340   }
341 }
342