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