package org.robolectric.annotation.processing.generator; import com.google.common.base.Joiner; import org.robolectric.annotation.Implements; import org.robolectric.annotation.processing.RobolectricModel; import org.robolectric.annotation.processing.RobolectricProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; /** * Generator that creates the "ShadowProvider" implementation for a shadow package. */ public class ShadowProviderGenerator extends Generator { private final Filer filer; private final Messager messager; private final Elements elements; private final RobolectricModel model; private final String shadowPackage; private final boolean shouldInstrumentPackages; public ShadowProviderGenerator(RobolectricModel model, ProcessingEnvironment environment, String shadowPackage, boolean shouldInstrumentPackages) { this.messager = environment.getMessager(); this.elements = environment.getElementUtils(); this.filer = environment.getFiler(); this.model = model; this.shadowPackage = shadowPackage; this.shouldInstrumentPackages = shouldInstrumentPackages; } @Override public void generate() { if (shadowPackage == null) { return; } final String shadowClassName = shadowPackage + '.' + GEN_CLASS; // TODO: Because this was fairly simple to begin with I haven't // included a templating engine like Velocity but simply used // raw print() statements, in an effort to reduce the number of // dependencies that RAP has. However, if it gets too complicated // then using Velocity might be a good idea. PrintWriter writer = null; try { JavaFileObject jfo = filer.createSourceFile(shadowClassName); writer = new PrintWriter(jfo.openWriter()); generate(writer); } catch (IOException e) { messager.printMessage(Diagnostic.Kind.ERROR, "Failed to write shadow class file: " + e); throw new RuntimeException(e); } finally { if (writer != null) { writer.close(); } } } void generate(PrintWriter writer) { writer.print("package " + shadowPackage + ";\n"); for (String name : model.getImports()) { writer.println("import " + name + ';'); } writer.println(); writer.println("/**"); writer.println(" * Shadow mapper. Automatically generated by the Robolectric Annotation Processor."); writer.println(" */"); writer.println("@Generated(\"" + RobolectricProcessor.class.getCanonicalName() + "\")"); writer.println("@SuppressWarnings({\"unchecked\",\"deprecation\"})"); writer.println("public class " + GEN_CLASS + " implements ShadowProvider {"); final int shadowSize = model.getAllShadowTypes().size(); writer.println(" private static final Map<String, String> SHADOW_MAP = new HashMap<>(" + shadowSize + ");"); writer.println(); writer.println(" static {"); for (Map.Entry<TypeElement, TypeElement> entry : model.getAllShadowTypes().entrySet()) { final String shadow = elements.getBinaryName(entry.getKey()).toString(); final String actual = entry.getValue().getQualifiedName().toString(); writer.println(" SHADOW_MAP.put(\"" + actual + "\", \"" + shadow + "\");"); } for (Map.Entry<String, String> entry : model.getExtraShadowTypes().entrySet()) { final String shadow = entry.getKey(); final String actual = entry.getValue(); writer.println(" SHADOW_MAP.put(\"" + actual + "\", \"" + shadow + "\");"); } writer.println(" }"); writer.println(); for (Map.Entry<TypeElement, TypeElement> entry : model.getShadowOfMap().entrySet()) { final TypeElement shadowType = entry.getKey(); final TypeElement actualType = entry.getValue(); if (!actualType.getModifiers().contains(Modifier.PUBLIC)) { continue; } int paramCount = 0; StringBuilder paramDef = new StringBuilder("<"); StringBuilder paramUse = new StringBuilder("<"); for (TypeParameterElement typeParam : entry.getValue().getTypeParameters()) { if (paramCount > 0) { paramDef.append(','); paramUse.append(','); } boolean first = true; paramDef.append(typeParam); paramUse.append(typeParam); for (TypeMirror bound : model.getExplicitBounds(typeParam)) { if (first) { paramDef.append(" extends "); first = false; } else { paramDef.append(" & "); } paramDef.append(model.getReferentFor(bound)); } paramCount++; } String paramDefStr = ""; String paramUseStr = ""; if (paramCount > 0) { paramDefStr = paramDef.append("> ").toString(); paramUseStr = paramUse.append('>').toString(); } final String actual = model.getReferentFor(actualType) + paramUseStr; final String shadow = model.getReferentFor(shadowType) + paramUseStr; if (shadowType.getAnnotation(Deprecated.class) != null) { writer.println(" @Deprecated"); } writer.println(" public static " + paramDefStr + shadow + " shadowOf(" + actual + " actual) {"); writer.println(" return (" + shadow + ") ShadowExtractor.extract(actual);"); writer.println(" }"); writer.println(); } writer.println(" public void reset() {"); for (Map.Entry<TypeElement, ExecutableElement> entry : model.getResetters().entrySet()) { Implements annotation = entry.getKey().getAnnotation(Implements.class); int minSdk = annotation.minSdk(); int maxSdk = annotation.maxSdk(); String ifClause; if (minSdk != -1 && maxSdk != -1) { ifClause = "if (org.robolectric.RuntimeEnvironment.getApiLevel() >= " + minSdk + " && org.robolectric.RuntimeEnvironment.getApiLevel() <= " + maxSdk + ") "; } else if (maxSdk != -1) { ifClause = "if (org.robolectric.RuntimeEnvironment.getApiLevel() <= " + maxSdk + ") "; } else if (minSdk != -1) { ifClause = "if (org.robolectric.RuntimeEnvironment.getApiLevel() >= " + minSdk + ") "; } else { ifClause = ""; } writer.println(" " + ifClause + model.getReferentFor(entry.getKey()) + "." + entry.getValue().getSimpleName() + "();"); } writer.println(" }"); writer.println(); writer.println(" @Override"); writer.println(" public Map<String, String> getShadowMap() {"); writer.println(" return SHADOW_MAP;"); writer.println(" }"); writer.println(); writer.println(" @Override"); writer.println(" public String[] getProvidedPackageNames() {"); writer.println(" return new String[] {"); if (shouldInstrumentPackages) { writer.println(" " + Joiner.on(",\n ").join(model.getShadowedPackages())); } writer.println(" };"); writer.println(" }"); writer.println('}'); } }