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