1 package org.robolectric.annotation.processing; 2 3 import com.google.common.annotations.VisibleForTesting; 4 import java.util.ArrayList; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Set; 9 import javax.annotation.processing.AbstractProcessor; 10 import javax.annotation.processing.ProcessingEnvironment; 11 import javax.annotation.processing.RoundEnvironment; 12 import javax.annotation.processing.SupportedAnnotationTypes; 13 import javax.annotation.processing.SupportedOptions; 14 import javax.lang.model.SourceVersion; 15 import javax.lang.model.element.Element; 16 import javax.lang.model.element.TypeElement; 17 import org.robolectric.annotation.processing.generator.Generator; 18 import org.robolectric.annotation.processing.generator.JavadocJsonGenerator; 19 import org.robolectric.annotation.processing.generator.ServiceLoaderGenerator; 20 import org.robolectric.annotation.processing.generator.ShadowProviderGenerator; 21 import org.robolectric.annotation.processing.validator.ImplementationValidator; 22 import org.robolectric.annotation.processing.validator.ImplementsValidator; 23 import org.robolectric.annotation.processing.validator.RealObjectValidator; 24 import org.robolectric.annotation.processing.validator.ResetterValidator; 25 import org.robolectric.annotation.processing.validator.Validator; 26 27 /** 28 * Annotation processor entry point for Robolectric annotations. 29 */ 30 @SupportedOptions({ 31 RobolectricProcessor.PACKAGE_OPT, 32 RobolectricProcessor.SHOULD_INSTRUMENT_PKG_OPT}) 33 @SupportedAnnotationTypes("org.robolectric.annotation.*") 34 public class RobolectricProcessor extends AbstractProcessor { 35 static final String PACKAGE_OPT = "org.robolectric.annotation.processing.shadowPackage"; 36 static final String SHOULD_INSTRUMENT_PKG_OPT = 37 "org.robolectric.annotation.processing.shouldInstrumentPackage"; 38 39 private RobolectricModel model; 40 private String shadowPackage; 41 private boolean shouldInstrumentPackages; 42 private Map<String, String> options; 43 private boolean generated = false; 44 private final List<Generator> generators = new ArrayList<>(); 45 private final Map<TypeElement, Validator> elementValidators = new HashMap<>(13); 46 47 /** 48 * Default constructor. 49 */ 50 public RobolectricProcessor() { 51 } 52 53 /** 54 * Constructor to use for testing passing options in. Only 55 * necessary until compile-testing supports passing options 56 * in. 57 * 58 * @param options simulated options that would ordinarily 59 * be passed in the {@link ProcessingEnvironment}. 60 */ 61 @VisibleForTesting 62 public RobolectricProcessor(Map<String, String> options) { 63 processOptions(options); 64 } 65 66 @Override 67 public synchronized void init(ProcessingEnvironment environment) { 68 super.init(environment); 69 processOptions(environment.getOptions()); 70 model = new RobolectricModel(environment.getElementUtils(), environment.getTypeUtils()); 71 72 addValidator(new ImplementationValidator(model, environment)); 73 addValidator(new ImplementsValidator(model, environment)); 74 addValidator(new RealObjectValidator(model, environment)); 75 addValidator(new ResetterValidator(model, environment)); 76 77 generators.add(new ShadowProviderGenerator(model, environment, shadowPackage, shouldInstrumentPackages)); 78 generators.add(new ServiceLoaderGenerator(environment, shadowPackage)); 79 generators.add(new JavadocJsonGenerator(model, environment)); 80 } 81 82 @Override 83 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 84 for (TypeElement annotation : annotations) { 85 Validator validator = elementValidators.get(annotation); 86 if (validator != null) { 87 for (Element elem : roundEnv.getElementsAnnotatedWith(annotation)) { 88 validator.visit(elem, elem.getEnclosingElement()); 89 } 90 } 91 } 92 93 if (!generated) { 94 model.prepare(); 95 for (Generator generator : generators) { 96 generator.generate(); 97 } 98 generated = true; 99 } 100 return true; 101 } 102 103 private void addValidator(Validator v) { 104 elementValidators.put(v.getAnnotationType(), v); 105 } 106 107 private void processOptions(Map<String, String> options) { 108 if (this.options == null) { 109 this.options = options; 110 this.shadowPackage = options.get(PACKAGE_OPT); 111 this.shouldInstrumentPackages = 112 !"false".equalsIgnoreCase(options.get(SHOULD_INSTRUMENT_PKG_OPT)); 113 114 if (this.shadowPackage == null) { 115 throw new IllegalArgumentException("no package specified for " + PACKAGE_OPT); 116 } 117 } 118 } 119 120 @Override 121 public SourceVersion getSupportedSourceVersion() { 122 return SourceVersion.latest(); 123 } 124 } 125