1 package org.robolectric.annotation.processing; 2 3 import com.google.common.annotations.VisibleForTesting; 4 import java.io.File; 5 import java.util.ArrayList; 6 import java.util.HashMap; 7 import java.util.List; 8 import java.util.Locale; 9 import java.util.Map; 10 import java.util.Set; 11 import javax.annotation.processing.AbstractProcessor; 12 import javax.annotation.processing.ProcessingEnvironment; 13 import javax.annotation.processing.RoundEnvironment; 14 import javax.annotation.processing.SupportedAnnotationTypes; 15 import javax.annotation.processing.SupportedOptions; 16 import javax.lang.model.SourceVersion; 17 import javax.lang.model.element.Element; 18 import javax.lang.model.element.TypeElement; 19 import org.robolectric.annotation.processing.generator.Generator; 20 import org.robolectric.annotation.processing.generator.JavadocJsonGenerator; 21 import org.robolectric.annotation.processing.generator.ServiceLoaderGenerator; 22 import org.robolectric.annotation.processing.generator.ShadowProviderGenerator; 23 import org.robolectric.annotation.processing.validator.ImplementationValidator; 24 import org.robolectric.annotation.processing.validator.ImplementsValidator; 25 import org.robolectric.annotation.processing.validator.ImplementsValidator.SdkCheckMode; 26 import org.robolectric.annotation.processing.validator.RealObjectValidator; 27 import org.robolectric.annotation.processing.validator.ResetterValidator; 28 import org.robolectric.annotation.processing.validator.SdkStore; 29 import org.robolectric.annotation.processing.validator.Validator; 30 31 /** Annotation processor entry point for Robolectric annotations. */ 32 @SupportedOptions({ 33 RobolectricProcessor.PACKAGE_OPT, 34 RobolectricProcessor.SHOULD_INSTRUMENT_PKG_OPT 35 }) 36 @SupportedAnnotationTypes("org.robolectric.annotation.*") 37 public class RobolectricProcessor extends AbstractProcessor { 38 static final String PACKAGE_OPT = "org.robolectric.annotation.processing.shadowPackage"; 39 static final String SHOULD_INSTRUMENT_PKG_OPT = 40 "org.robolectric.annotation.processing.shouldInstrumentPackage"; 41 static final String JSON_DOCS_DIR = "org.robolectric.annotation.processing.jsonDocsDir"; 42 static final String JSON_DOCS_ENABLED = "org.robolectric.annotation.processing.jsonDocsEnabled"; 43 static final String SDK_CHECK_MODE = "org.robolectric.annotation.processing.sdkCheckMode"; 44 private static final String SDKS_FILE = "org.robolectric.annotation.processing.sdks"; 45 private static final String DISABLE_INDEVELOPMENT = 46 "org.robolectric.annotation.processing.disableInDevelopment"; 47 private static final String ALLOW_LOOSE_SIGNATURES = 48 "org.robolectric.annotation.processing.allowLooseSignatures"; 49 50 /** required for Android Development. */ 51 private static final String VALIDATE_COMPILE_SDKS = 52 "org.robolectric.annotation.processing.validateCompileSdk"; 53 54 private static final String PRIORITY = "org.robolectric.annotation.processing.priority"; 55 56 private RobolectricModel.Builder modelBuilder; 57 private String shadowPackage; 58 private boolean shouldInstrumentPackages; 59 private int priority; 60 private ImplementsValidator.SdkCheckMode sdkCheckMode; 61 private String sdksFile; 62 private Map<String, String> options; 63 private boolean generated = false; 64 private final List<Generator> generators = new ArrayList<>(); 65 private final Map<TypeElement, Validator> elementValidators = new HashMap<>(13); 66 private File jsonDocsDir; 67 private boolean jsonDocsEnabled; 68 private boolean validateCompiledSdk; 69 private boolean allowInDev; 70 private String overrideSdkLocation; 71 private int overrideSdkInt; 72 private boolean allowLooseSignatures; 73 74 /** Default constructor. */ RobolectricProcessor()75 public RobolectricProcessor() {} 76 77 /** 78 * Constructor to use for testing passing options in. Only necessary until compile-testing 79 * supports passing options in. 80 * 81 * @param options simulated options that would ordinarily be passed in the {@link 82 * ProcessingEnvironment}. 83 */ 84 @VisibleForTesting RobolectricProcessor(Map<String, String> options)85 public RobolectricProcessor(Map<String, String> options) { 86 this(options, null, -1); 87 } 88 RobolectricProcessor( Map<String, String> options, String overrideSdkLocation, int overrideSdkInt)89 public RobolectricProcessor( 90 Map<String, String> options, String overrideSdkLocation, int overrideSdkInt) { 91 processOptions(options); 92 this.overrideSdkLocation = overrideSdkLocation; 93 this.overrideSdkInt = overrideSdkInt; 94 } 95 96 @Override init(ProcessingEnvironment environment)97 public synchronized void init(ProcessingEnvironment environment) { 98 super.init(environment); 99 processOptions(environment.getOptions()); 100 modelBuilder = new RobolectricModel.Builder(environment); 101 102 SdkStore sdkStore = 103 new SdkStore(sdksFile, validateCompiledSdk, overrideSdkLocation, overrideSdkInt); 104 105 addValidator(new ImplementationValidator(modelBuilder, environment)); 106 addValidator( 107 new ImplementsValidator( 108 modelBuilder, environment, sdkCheckMode, sdkStore, allowInDev, allowLooseSignatures)); 109 addValidator(new RealObjectValidator(modelBuilder, environment)); 110 addValidator(new ResetterValidator(modelBuilder, environment)); 111 } 112 113 @Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)114 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 115 for (TypeElement annotation : annotations) { 116 Validator validator = elementValidators.get(annotation); 117 if (validator != null) { 118 for (Element elem : roundEnv.getElementsAnnotatedWith(annotation)) { 119 validator.visit(elem, elem.getEnclosingElement()); 120 } 121 } 122 } 123 124 if (!generated) { 125 RobolectricModel model = modelBuilder.build(); 126 127 generators.add( 128 new ShadowProviderGenerator( 129 model, processingEnv, shadowPackage, shouldInstrumentPackages, priority)); 130 generators.add(new ServiceLoaderGenerator(processingEnv, shadowPackage)); 131 if (jsonDocsEnabled) { 132 generators.add(new JavadocJsonGenerator(model, processingEnv, jsonDocsDir)); 133 } 134 for (Generator generator : generators) { 135 generator.generate(); 136 } 137 generated = true; 138 } 139 return false; 140 } 141 addValidator(Validator v)142 private void addValidator(Validator v) { 143 elementValidators.put(v.getAnnotationType(), v); 144 } 145 processOptions(Map<String, String> options)146 private void processOptions(Map<String, String> options) { 147 if (this.options == null) { 148 this.options = options; 149 this.shadowPackage = options.get(PACKAGE_OPT); 150 this.shouldInstrumentPackages = 151 !"false".equalsIgnoreCase(options.get(SHOULD_INSTRUMENT_PKG_OPT)); 152 this.jsonDocsDir = new File(options.getOrDefault(JSON_DOCS_DIR, "build/docs/json")); 153 this.jsonDocsEnabled = "true".equalsIgnoreCase(options.get(JSON_DOCS_ENABLED)); 154 this.sdkCheckMode = 155 SdkCheckMode.valueOf( 156 options.getOrDefault(SDK_CHECK_MODE, "WARN").toUpperCase(Locale.ROOT)); 157 this.validateCompiledSdk = 158 "true".equalsIgnoreCase(options.getOrDefault(VALIDATE_COMPILE_SDKS, "false")); 159 this.sdksFile = getSdksFile(options, SDKS_FILE); 160 this.priority = Integer.parseInt(options.getOrDefault(PRIORITY, "0")); 161 this.allowInDev = 162 !"true".equalsIgnoreCase(options.getOrDefault(DISABLE_INDEVELOPMENT, "false")); 163 this.allowLooseSignatures = 164 "true".equalsIgnoreCase(options.getOrDefault(ALLOW_LOOSE_SIGNATURES, "false")); 165 if (this.shadowPackage == null) { 166 throw new IllegalArgumentException("no package specified for " + PACKAGE_OPT); 167 } 168 } 169 } 170 171 /** 172 * Extendable to support Bazel environments, where the sdks file is generated as a build artifact. 173 */ getSdksFile(Map<String, String> options, String sdksFileParam)174 protected String getSdksFile(Map<String, String> options, String sdksFileParam) { 175 return options.get(sdksFileParam); 176 } 177 178 @Override getSupportedSourceVersion()179 public SourceVersion getSupportedSourceVersion() { 180 return SourceVersion.latest(); 181 } 182 } 183