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