
org.robolectric.annotation.processing.RobolectricModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of robolectric-processor Show documentation
Show all versions of robolectric-processor Show documentation
An alternative Android testing framework.
package org.robolectric.annotation.processing;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Maps.*;
import static com.google.common.collect.Sets.*;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor6;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.Types;
import com.google.common.base.Equivalence;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.processing.validator.ImplementsValidator;
/**
* Model describing the Robolectric source file.
*/
public class RobolectricModel {
private static FQComparator fqComparator = new FQComparator();
private static SimpleComparator comparator = new SimpleComparator();
/** TypeElement representing the Robolectric.Anything interface, or null if the element isn't found. */
final TypeElement ANYTHING;
/** TypeMirror representing the Robolectric.Anything interface, or null if the element isn't found. */
public final TypeMirror ANYTHING_MIRROR;
/** TypeMirror representing the Object class. */
final TypeMirror OBJECT_MIRROR;
/** TypeElement representing the @Implements annotation. */
final TypeElement IMPLEMENTS;
/** PackageElement representing the java.lang package. */
final PackageElement JAVA_LANG;
/** Convenience reference for the processing environment's elements utilities. */
private final Elements elements;
/** Convenience reference for the processing environment's types utilities. */
private final Types types;
private HashMap referentMap = newHashMap();
private HashMultimap typeMap = HashMultimap.create();
private HashMap importMap = newHashMap();
private TreeMap shadowTypes = newTreeMap(fqComparator);
private TreeSet imports = newTreeSet();
private TreeMap resetterMap = newTreeMap(comparator);
private static class FQComparator implements Comparator {
@Override
public int compare(TypeElement o1, TypeElement o2) {
return o1.getQualifiedName().toString().compareTo(o2.getQualifiedName().toString());
}
}
private static class SimpleComparator implements Comparator {
@Override
public int compare(Element o1, Element o2) {
return o1.getSimpleName().toString().compareTo(o2.getSimpleName().toString());
}
}
public RobolectricModel(Elements elements, Types types) {
this.elements = elements;
this.types = types;
ANYTHING = elements.getTypeElement("org.robolectric.Robolectric.Anything");
ANYTHING_MIRROR = ANYTHING == null ? null : ANYTHING.asType();
// FIXME: check this type lookup for NPEs (and also the ones in the
// validators)
IMPLEMENTS = elements.getTypeElement(ImplementsValidator.IMPLEMENTS_CLASS);
JAVA_LANG = elements.getPackageElement("java.lang");
OBJECT_MIRROR = elements.getTypeElement(Object.class.getCanonicalName()).asType();
notObject = new Predicate() {
@Override
public boolean apply(TypeMirror t) {
return !RobolectricModel.this.types.isSameType(t, OBJECT_MIRROR);
}
};
}
public AnnotationMirror getAnnotationMirror(Element element, TypeElement annotation) {
TypeMirror expectedType = annotation.asType();
for (AnnotationMirror m : element.getAnnotationMirrors()) {
if (types.isSameType(expectedType, m.getAnnotationType())) {
return m;
}
}
return null;
}
public static ElementVisitor typeVisitor = new SimpleElementVisitor6() {
@Override
public TypeElement visitType(TypeElement e, Void p) {
return e;
}
};
public static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
for (Entry extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet() ) {
if (entry.getKey().getSimpleName().toString().equals(key)) {
return entry.getValue();
}
}
return null;
}
public static AnnotationValueVisitor valueVisitor = new SimpleAnnotationValueVisitor6() {
@Override
public TypeMirror visitType(TypeMirror t, Void arg) {
return t;
}
};
public static AnnotationValueVisitor classNameVisitor = new SimpleAnnotationValueVisitor6() {
@Override
public String visitString(String s, Void arg) {
return s;
}
};
public AnnotationMirror getImplementsMirror(Element elem) {
return getAnnotationMirror(elem, IMPLEMENTS);
}
private TypeMirror getImplementedClassName(AnnotationMirror am) {
AnnotationValue className = getAnnotationValue(am, "className");
if (className == null) {
return null;
}
String classNameString = classNameVisitor.visit(className);
if (classNameString == null) {
return null;
}
TypeElement impElement = elements.getTypeElement(classNameString.replace('$', '.'));
if (impElement == null) {
return null;
}
return impElement.asType();
}
public TypeMirror getImplementedClass(AnnotationMirror am) {
if (am == null) {
return null;
}
// RobolectricWiringTest prefers className (if provided) to value, so we do the same here.
TypeMirror impType = getImplementedClassName(am);
if (impType != null) {
return impType;
}
AnnotationValue av = getAnnotationValue(am, "value");
if (av == null) {
return null;
}
TypeMirror type = valueVisitor.visit(av);
if (type == null) {
return null;
}
// If the class is Robolectric.Anything, treat as if it wasn't specified at all.
if (ANYTHING_MIRROR != null && types.isSameType(type, ANYTHING_MIRROR)) {
return null;
}
return type;
}
private static ElementVisitor typeElementVisitor = new SimpleElementVisitor6() {
@Override
public TypeElement visitType(TypeElement e, Void p) {
return e;
}
};
private void registerType(TypeElement type) {
if (!Objects.equal(ANYTHING, type) && !importMap.containsKey(type)) {
typeMap.put(type.getSimpleName().toString(), type);
importMap.put(type, type);
for (TypeParameterElement typeParam : type.getTypeParameters()) {
for (TypeMirror bound : typeParam.getBounds()) {
// FIXME: get rid of cast using a visitor
TypeElement boundElement = typeElementVisitor.visit(types.asElement(bound));
registerType(boundElement);
}
}
}
}
/**
* Prepares the various derived parts of the model based on the class mappings
* that have been registered to date.
*/
public void prepare() {
for (Map.Entry entry : getVisibleShadowTypes().entrySet()) {
final TypeElement shadowType = entry.getKey();
registerType(shadowType);
final TypeElement solidType = entry.getValue();
registerType(solidType);
}
for (Map.Entry entry : getResetterShadowTypes().entrySet()) {
final TypeElement shadowType = entry.getKey();
registerType(shadowType);
}
while (!typeMap.isEmpty()) {
final HashMultimap nextRound = HashMultimap.create();
for (Map.Entry> referents : Multimaps.asMap(typeMap).entrySet()) {
final Set c = referents.getValue();
// If there is only one type left with the given simple
// name, then
if (c.size() == 1) {
final TypeElement type = c.iterator().next();
referentMap.put(type, referents.getKey());
}
else {
for (TypeElement type : c) {
SimpleElementVisitor6 visitor = new SimpleElementVisitor6() {
@Override
public Void visitType(TypeElement parent, TypeElement type) {
nextRound.put(parent.getSimpleName() + "." + type.getSimpleName(), type);
importMap.put(type, parent);
return null;
}
@Override
public Void visitPackage(PackageElement parent, TypeElement type) {
referentMap.put(type, type.getQualifiedName().toString());
importMap.remove(type);
return null;
}
};
visitor.visit(importMap.get(type).getEnclosingElement(), type);
}
}
}
typeMap = nextRound;
}
for (TypeElement imp: importMap.values()) {
if (imp.getModifiers().contains(Modifier.PUBLIC)
&& !JAVA_LANG.equals(imp.getEnclosingElement())) {
imports.add(imp.getQualifiedName().toString());
}
}
// Other imports that the generated class needs
imports.add("java.util.Map");
imports.add("java.util.HashMap");
imports.add("javax.annotation.Generated");
imports.add("org.robolectric.internal.ShadowExtractor");
imports.add("org.robolectric.internal.ShadowProvider");
}
public void addShadowType(TypeElement elem, TypeElement type) {
shadowTypes.put(elem, type);
}
public void addResetter(TypeElement parent, ExecutableElement elem) {
resetterMap.put(parent, elem);
}
public Set> getResetters() {
return resetterMap.entrySet();
}
public Set getImports() {
return imports;
}
public Map getAllShadowTypes() {
return shadowTypes;
}
public Map getResetterShadowTypes() {
return Maps.filterEntries(shadowTypes, new Predicate>() {
@Override
public boolean apply(Entry entry) {
return resetterMap.containsKey(entry.getKey());
}
});
}
public Map getVisibleShadowTypes() {
return Maps.filterEntries(shadowTypes, new Predicate>() {
@Override
public boolean apply(Entry entry) {
return entry.getKey().getAnnotation(Implements.class).isInAndroidSdk();
}
});
}
public Map getShadowOfMap() {
return Maps.filterEntries(getVisibleShadowTypes(), new Predicate> () {
@Override
public boolean apply(Entry entry) {
return !Objects.equal(ANYTHING, entry.getValue());
}
});
}
public Collection getShadowedPackages() {
Set packages = new HashSet<>();
for (TypeElement element : getVisibleShadowTypes().values()) {
packages.add("\"" + elements.getPackageOf(element).toString() + "\"");
}
return packages;
}
private Predicate notObject;
public List getExplicitBounds(TypeParameterElement typeParam) {
return newArrayList(Iterables.filter(typeParam.getBounds(), notObject));
}
/**
* Returns a plain string to be used in the generated source
* to identify the given type. The returned string will have
* sufficient level of qualification in order to make the referent
* unique for the source file.
* @param type
* @return
*/
public String getReferentFor(TypeElement type) {
return referentMap.get(type);
}
private TypeVisitor findReferent = new SimpleTypeVisitor6() {
@Override
public String visitDeclared(DeclaredType t, Void p) {
return referentMap.get(t.asElement());
}
};
public String getReferentFor(TypeMirror type) {
return findReferent.visit(type);
}
private Equivalence typeMirrorEq = new Equivalence() {
@Override
protected boolean doEquivalent(TypeMirror a, TypeMirror b) {
return types.isSameType(a, b);
}
@Override
protected int doHash(TypeMirror t) {
// We're not using the hash.
return 0;
}
};
private Equivalence typeEq = new Equivalence() {
@Override
@SuppressWarnings({"unchecked"})
protected boolean doEquivalent(TypeParameterElement arg0,
TypeParameterElement arg1) {
// Casts are necessary due to flaw in pairwise equivalence implementation.
return typeMirrorEq.pairwise().equivalent((List)arg0.getBounds(),
(List)arg1.getBounds());
}
@Override
protected int doHash(TypeParameterElement arg0) {
// We don't use the hash code.
return 0;
}
};
public void appendParameterList(StringBuilder message, List extends TypeParameterElement> tpeList) {
boolean first = true;
for (TypeParameterElement tpe : tpeList) {
if (first) {
first = false;
} else {
message.append(',');
}
message.append(tpe.toString());
boolean iFirst = true;
for (TypeMirror bound : getExplicitBounds(tpe)) {
if (iFirst) {
message.append(" extends ");
iFirst = false;
} else {
message.append(',');
}
message.append(bound);
}
}
}
@SuppressWarnings({"unchecked"})
public boolean isSameParameterList(List extends TypeParameterElement> l1, List extends TypeParameterElement> l2) {
// Cast is necessary because of a flaw in the API design of "PairwiseEquivalent",
// a flaw that is even acknowledged in the source.
// Our casts are safe because we're not trying to add elements to the list
// and therefore can't violate the constraint.
return typeEq.pairwise().equivalent((List)l1,
(List)l2);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy