• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.annotation.processing;
2 
3 import static com.google.common.collect.Maps.newHashMap;
4 import static com.google.common.collect.Maps.newTreeMap;
5 import static com.google.common.collect.Sets.newTreeSet;
6 
7 import com.google.common.collect.HashMultimap;
8 import com.google.common.collect.Iterables;
9 import com.google.common.collect.Multimaps;
10 import java.util.Collection;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.TreeMap;
15 import java.util.TreeSet;
16 import javax.annotation.processing.ProcessingEnvironment;
17 import javax.lang.model.element.Element;
18 import javax.lang.model.element.ElementVisitor;
19 import javax.lang.model.element.ExecutableElement;
20 import javax.lang.model.element.Modifier;
21 import javax.lang.model.element.PackageElement;
22 import javax.lang.model.element.TypeElement;
23 import javax.lang.model.element.TypeParameterElement;
24 import javax.lang.model.type.DeclaredType;
25 import javax.lang.model.type.TypeMirror;
26 import javax.lang.model.type.TypeVisitor;
27 import javax.lang.model.util.SimpleElementVisitor6;
28 import javax.lang.model.util.SimpleTypeVisitor6;
29 import org.robolectric.annotation.Implements;
30 import org.robolectric.shadow.api.ShadowPicker;
31 
32 /**
33  * Model describing the Robolectric source file.
34  */
35 public class RobolectricModel {
36 
37   private final TreeSet<String> imports;
38   /**
39    * Key: name of shadow class
40    */
41   private final TreeMap<String, ShadowInfo> shadowTypes;
42   private final TreeMap<String, String> extraShadowTypes;
43   /**
44    * Key: name of shadow class
45    */
46   private final TreeMap<String, ResetterInfo> resetterMap;
47 
48   private final TreeMap<String, DocumentedPackage> documentedPackages;
49 
getDocumentedPackages()50   public Collection<DocumentedPackage> getDocumentedPackages() {
51     return documentedPackages.values();
52   }
53 
RobolectricModel(TreeSet<String> imports, TreeMap<String, ShadowInfo> shadowTypes, TreeMap<String, String> extraShadowTypes, TreeMap<String, ResetterInfo> resetterMap, Map<String, DocumentedPackage> documentedPackages)54   public RobolectricModel(TreeSet<String> imports,
55       TreeMap<String, ShadowInfo> shadowTypes,
56       TreeMap<String, String> extraShadowTypes,
57       TreeMap<String, ResetterInfo> resetterMap,
58       Map<String, DocumentedPackage> documentedPackages) {
59     this.imports = new TreeSet<>(imports);
60     this.shadowTypes = new TreeMap<>(shadowTypes);
61     this.extraShadowTypes = new TreeMap<>(extraShadowTypes);
62     this.resetterMap = new TreeMap<>(resetterMap);
63     this.documentedPackages = new TreeMap<>(documentedPackages);
64   }
65 
66   private final static ElementVisitor<TypeElement, Void> TYPE_ELEMENT_VISITOR =
67       new SimpleElementVisitor6<TypeElement, Void>() {
68         @Override
69         public TypeElement visitType(TypeElement e, Void p) {
70           return e;
71         }
72       };
73 
74   public static class Builder {
75 
76     private final Helpers helpers;
77 
78     private final TreeSet<String> imports = newTreeSet();
79     private final TreeMap<String, ShadowInfo> shadowTypes = newTreeMap();
80     private final TreeMap<String, String> extraShadowTypes = newTreeMap();
81     private final TreeMap<String, ResetterInfo> resetterMap = newTreeMap();
82     private final Map<String, DocumentedPackage> documentedPackages = new TreeMap<>();
83 
84     private final Map<TypeElement, TypeElement> importMap = newHashMap();
85     private final Map<TypeElement, String> referentMap = newHashMap();
86     private HashMultimap<String, TypeElement> typeMap = HashMultimap.create();
87 
Builder(ProcessingEnvironment environment)88     Builder(ProcessingEnvironment environment) {
89       this.helpers = new Helpers(environment);
90     }
91 
addShadowType(TypeElement shadowType, TypeElement actualType, TypeElement shadowPickerType)92     public void addShadowType(TypeElement shadowType, TypeElement actualType,
93         TypeElement shadowPickerType) {
94       TypeElement shadowBaseType = null;
95       if (shadowPickerType != null) {
96         TypeMirror iface = helpers.findInterface(shadowPickerType, ShadowPicker.class);
97         if (iface != null) {
98           com.sun.tools.javac.code.Type type = ((com.sun.tools.javac.code.Type.ClassType) iface)
99               .allparams().get(0);
100           String baseClassName = type.asElement().getQualifiedName().toString();
101           shadowBaseType = helpers.getTypeElement(baseClassName);
102         }
103       }
104       ShadowInfo shadowInfo =
105           new ShadowInfo(shadowType, actualType, shadowPickerType, shadowBaseType);
106 
107       if (shadowInfo.isInAndroidSdk()) {
108         registerType(shadowInfo.shadowType);
109         registerType(shadowInfo.actualType);
110         registerType(shadowInfo.shadowBaseClass);
111       }
112 
113       shadowTypes.put(shadowType.getQualifiedName().toString(), shadowInfo);
114     }
115 
addExtraShadow(String sdkClassName, String shadowClassName)116     public void addExtraShadow(String sdkClassName, String shadowClassName) {
117       extraShadowTypes.put(shadowClassName, sdkClassName);
118     }
119 
addResetter(TypeElement shadowTypeElement, ExecutableElement elem)120     public void addResetter(TypeElement shadowTypeElement, ExecutableElement elem) {
121       registerType(shadowTypeElement);
122 
123       resetterMap.put(shadowTypeElement.getQualifiedName().toString(),
124           new ResetterInfo(shadowTypeElement, elem));
125     }
126 
documentPackage(String name, String documentation)127     public void documentPackage(String name, String documentation) {
128       getDocumentedPackage(name).setDocumentation(documentation);
129     }
130 
documentType(TypeElement type, String documentation, List<String> imports)131     public void documentType(TypeElement type, String documentation, List<String> imports) {
132       DocumentedType documentedType = getDocumentedType(type);
133       documentedType.setDocumentation(documentation);
134       documentedType.imports = imports;
135     }
136 
documentMethod(TypeElement shadowClass, DocumentedMethod documentedMethod)137     public void documentMethod(TypeElement shadowClass, DocumentedMethod documentedMethod) {
138       DocumentedType documentedType = getDocumentedType(shadowClass);
139       documentedType.methods.put(documentedMethod.getName(), documentedMethod);
140     }
141 
getDocumentedPackage(String name)142     private DocumentedPackage getDocumentedPackage(String name) {
143       DocumentedPackage documentedPackage = documentedPackages.get(name);
144       if (documentedPackage == null) {
145         documentedPackage = new DocumentedPackage(name);
146         documentedPackages.put(name, documentedPackage);
147       }
148       return documentedPackage;
149     }
150 
getDocumentedPackage(TypeElement type)151     private DocumentedPackage getDocumentedPackage(TypeElement type) {
152       Element pkgElement = type.getEnclosingElement();
153       return getDocumentedPackage(pkgElement.toString());
154     }
155 
getDocumentedType(TypeElement type)156     private DocumentedType getDocumentedType(TypeElement type) {
157       DocumentedPackage documentedPackage = getDocumentedPackage(type);
158       return documentedPackage.getDocumentedType(type.getQualifiedName().toString());
159     }
160 
build()161     RobolectricModel build() {
162       prepare();
163 
164       return new RobolectricModel(imports, shadowTypes, extraShadowTypes, resetterMap,
165           documentedPackages);
166     }
167 
168     /**
169      * Prepares the various derived parts of the model based on the class mappings that have been
170      * registered to date.
171      */
prepare()172     void prepare() {
173       while (!typeMap.isEmpty()) {
174         final HashMultimap<String, TypeElement> nextRound = HashMultimap.create();
175         for (Map.Entry<String, Set<TypeElement>> referents : Multimaps.asMap(typeMap).entrySet()) {
176           final Set<TypeElement> c = referents.getValue();
177           // If there is only one type left with the given simple
178           // name, then
179           if (c.size() == 1) {
180             final TypeElement type = c.iterator().next();
181             referentMap.put(type, referents.getKey());
182           } else {
183             for (TypeElement type : c) {
184               SimpleElementVisitor6<Void, TypeElement> visitor = new SimpleElementVisitor6<Void, TypeElement>() {
185                 @Override
186                 public Void visitType(TypeElement parent, TypeElement type) {
187                   nextRound.put(parent.getSimpleName() + "." + type.getSimpleName(), type);
188                   importMap.put(type, parent);
189                   return null;
190                 }
191 
192                 @Override
193                 public Void visitPackage(PackageElement parent, TypeElement type) {
194                   referentMap.put(type, type.getQualifiedName().toString());
195                   importMap.remove(type);
196                   return null;
197                 }
198               };
199               visitor.visit(importMap.get(type).getEnclosingElement(), type);
200             }
201           }
202         }
203         typeMap = nextRound;
204       }
205 
206       // FIXME: check this type lookup for NPEs (and also the ones in the validators)
207       Element javaLang = helpers.getPackageElement("java.lang");
208 
209       for (TypeElement imp : importMap.values()) {
210         if (imp.getModifiers().contains(Modifier.PUBLIC)
211             && !javaLang.equals(imp.getEnclosingElement())) {
212           imports.add(imp.getQualifiedName().toString());
213         }
214       }
215 
216       // Other imports that the generated class needs
217       imports.add("java.util.Map");
218       imports.add("java.util.HashMap");
219       imports.add("javax.annotation.Generated");
220       imports.add("org.robolectric.internal.ShadowProvider");
221       imports.add("org.robolectric.shadow.api.Shadow");
222 
223       ReferentResolver referentResolver = new ReferentResolver() {
224         @Override
225         public String getReferentFor(TypeMirror typeMirror) {
226           return findReferent.visit(typeMirror);
227         }
228 
229         @Override
230         public String getReferentFor(TypeElement type) {
231           return referentMap.get(type);
232         }
233       };
234       shadowTypes.values().forEach(shadowInfo -> shadowInfo.prepare(referentResolver, helpers));
235       resetterMap.values().forEach(resetterInfo -> resetterInfo.prepare(referentResolver));
236     }
237 
registerType(TypeElement type)238     private void registerType(TypeElement type) {
239       if (type != null && !importMap.containsKey(type)) {
240         typeMap.put(type.getSimpleName().toString(), type);
241         importMap.put(type, type);
242         for (TypeParameterElement typeParam : type.getTypeParameters()) {
243           for (TypeMirror bound : typeParam.getBounds()) {
244             // FIXME: get rid of cast using a visitor
245             TypeElement boundElement = TYPE_ELEMENT_VISITOR.visit(helpers.asElement(bound));
246             registerType(boundElement);
247           }
248         }
249       }
250     }
251 
252     private final TypeVisitor<String, Void> findReferent = new SimpleTypeVisitor6<String, Void>() {
253       @Override
254       public String visitDeclared(DeclaredType t, Void p) {
255         return referentMap.get(t.asElement());
256       }
257     };
258   }
259 
getResetters()260   public Collection<ResetterInfo> getResetters() {
261     return resetterMap.values();
262   }
263 
getImports()264   public Set<String> getImports() {
265     return imports;
266   }
267 
getAllShadowTypes()268   public Collection<ShadowInfo> getAllShadowTypes() {
269     return shadowTypes.values();
270   }
271 
getExtraShadowTypes()272   public Map<String, String> getExtraShadowTypes() {
273     return extraShadowTypes;
274   }
275 
getVisibleShadowTypes()276   public Iterable<ShadowInfo> getVisibleShadowTypes() {
277     return Iterables.filter(shadowTypes.values(),
278         ShadowInfo::isInAndroidSdk);
279   }
280 
getShadowPickers()281   public TreeMap<String, ShadowInfo> getShadowPickers() {
282     TreeMap<String, ShadowInfo> map = new TreeMap<>();
283     Iterables.filter(shadowTypes.values(), ShadowInfo::hasShadowPicker)
284         .forEach(shadowInfo -> {
285           String actualName = shadowInfo.getActualName();
286           String shadowPickerClassName = shadowInfo.getShadowPickerBinaryName();
287           ShadowInfo otherShadowInfo = map.get(actualName);
288           String otherPicker =
289               otherShadowInfo == null ? null : otherShadowInfo.getShadowPickerBinaryName();
290           if (otherPicker != null && !otherPicker.equals(shadowPickerClassName)) {
291             throw new IllegalArgumentException(
292                 actualName + " has conflicting pickers: " + shadowPickerClassName + " != "
293                     + otherPicker);
294           } else {
295             map.put(actualName, shadowInfo);
296           }
297         });
298     return map;
299   }
300 
getShadowedPackages()301   public Collection<String> getShadowedPackages() {
302     Set<String> packages = new TreeSet<>();
303     for (ShadowInfo shadowInfo : shadowTypes.values()) {
304       String packageName = shadowInfo.getActualPackage();
305 
306       // org.robolectric.* should never be instrumented
307       if (packageName.matches("org.robolectric(\\..*)?")) {
308         continue;
309       }
310 
311       packages.add("\"" + packageName + "\"");
312     }
313     return packages;
314   }
315 
316   interface ReferentResolver {
317 
getReferentFor(TypeMirror typeMirror)318     String getReferentFor(TypeMirror typeMirror);
319 
320     /**
321      * Returns a plain string to be used in the generated source to identify the given type. The
322      * returned string will have sufficient level of qualification in order to make the referent
323      * unique for the source file.
324      */
getReferentFor(TypeElement type)325     String getReferentFor(TypeElement type);
326   }
327 
328   public static class ShadowInfo {
329 
330     private final TypeElement shadowType;
331     private final TypeElement actualType;
332     private final TypeElement shadowPickerType;
333     private final TypeElement shadowBaseClass;
334 
335     private String paramDefStr;
336     private String paramUseStr;
337     private String actualBinaryName;
338     private String actualTypeReferent;
339     private String shadowTypeReferent;
340     private String actualTypePackage;
341     private String shadowBinaryName;
342     private String shadowPickerBinaryName;
343     private String shadowBaseName;
344 
ShadowInfo(TypeElement shadowType, TypeElement actualType, TypeElement shadowPickerType, TypeElement shadowBaseClass)345     ShadowInfo(TypeElement shadowType, TypeElement actualType, TypeElement shadowPickerType,
346         TypeElement shadowBaseClass) {
347       this.shadowType = shadowType;
348       this.actualType = actualType;
349       this.shadowPickerType = shadowPickerType;
350       this.shadowBaseClass = shadowBaseClass;
351     }
352 
prepare(ReferentResolver referentResolver, Helpers helpers)353     void prepare(ReferentResolver referentResolver, Helpers helpers) {
354       int paramCount = 0;
355       StringBuilder paramDef = new StringBuilder("<");
356       StringBuilder paramUse = new StringBuilder("<");
357       for (TypeParameterElement typeParam : actualType.getTypeParameters()) {
358         if (paramCount > 0) {
359           paramDef.append(',');
360           paramUse.append(',');
361         }
362         boolean first = true;
363         paramDef.append(typeParam);
364         paramUse.append(typeParam);
365         for (TypeMirror bound : helpers.getExplicitBounds(typeParam)) {
366           if (first) {
367             paramDef.append(" extends ");
368             first = false;
369           } else {
370             paramDef.append(" & ");
371           }
372           paramDef.append(referentResolver.getReferentFor(bound));
373         }
374         paramCount++;
375       }
376 
377       this.paramDefStr = "";
378       this.paramUseStr = "";
379       if (paramCount > 0) {
380         paramDefStr = paramDef.append('>').toString();
381         paramUseStr = paramUse.append('>').toString();
382       }
383 
384       actualTypeReferent = referentResolver.getReferentFor(actualType);
385       actualTypePackage = helpers.getPackageOf(actualType);
386       actualBinaryName = helpers.getBinaryName(actualType);
387       shadowTypeReferent = referentResolver.getReferentFor(shadowType);
388       shadowBinaryName = helpers.getBinaryName(shadowType);
389       shadowPickerBinaryName = helpers.getBinaryName(shadowPickerType);
390       shadowBaseName = referentResolver.getReferentFor(shadowBaseClass);
391     }
392 
getActualBinaryName()393     public String getActualBinaryName() {
394       return actualBinaryName;
395     }
396 
getActualName()397     public String getActualName() {
398       return actualType.getQualifiedName().toString();
399     }
400 
isInAndroidSdk()401     public boolean isInAndroidSdk() {
402       return shadowType.getAnnotation(Implements.class).isInAndroidSdk();
403     }
404 
getParamDefStr()405     public String getParamDefStr() {
406       return paramDefStr;
407     }
408 
shadowIsDeprecated()409     public boolean shadowIsDeprecated() {
410       return shadowType.getAnnotation(Deprecated.class) != null;
411     }
412 
actualIsPublic()413     public boolean actualIsPublic() {
414       return actualType.getModifiers().contains(Modifier.PUBLIC);
415     }
416 
getActualTypeWithParams()417     public String getActualTypeWithParams() {
418       return actualTypeReferent + paramUseStr;
419     }
420 
getShadowName()421     public String getShadowName() {
422       return shadowType.getQualifiedName().toString();
423     }
424 
getShadowBinaryName()425     public String getShadowBinaryName() {
426       return shadowBinaryName;
427     }
428 
getShadowTypeWithParams()429     public String getShadowTypeWithParams() {
430       return shadowTypeReferent + paramUseStr;
431     }
432 
getActualPackage()433     String getActualPackage() {
434       return actualTypePackage;
435     }
436 
hasShadowPicker()437     boolean hasShadowPicker() {
438       return shadowPickerType != null;
439     }
440 
getShadowPickerBinaryName()441     public String getShadowPickerBinaryName() {
442       return shadowPickerBinaryName;
443     }
444 
getShadowBaseName()445     public String getShadowBaseName() {
446       return shadowBaseName;
447     }
448   }
449 
450   public static class ResetterInfo {
451 
452     private final TypeElement shadowType;
453     private final ExecutableElement executableElement;
454     private String shadowTypeReferent;
455 
ResetterInfo(TypeElement shadowType, ExecutableElement executableElement)456     ResetterInfo(TypeElement shadowType, ExecutableElement executableElement) {
457       this.shadowType = shadowType;
458       this.executableElement = executableElement;
459     }
460 
prepare(ReferentResolver referentResolver)461     void prepare(ReferentResolver referentResolver) {
462       shadowTypeReferent = referentResolver.getReferentFor(shadowType);
463     }
464 
getImplementsAnnotation()465     private Implements getImplementsAnnotation() {
466       return shadowType.getAnnotation(Implements.class);
467     }
468 
getMethodCall()469     public String getMethodCall() {
470       return shadowTypeReferent + "." + executableElement.getSimpleName() + "();";
471     }
472 
getMinSdk()473     public int getMinSdk() {
474       return getImplementsAnnotation().minSdk();
475     }
476 
getMaxSdk()477     public int getMaxSdk() {
478       return getImplementsAnnotation().maxSdk();
479     }
480   }
481 
482 }
483