package org.robolectric.internal.bytecode;
import java.util.Set;
import org.robolectric.annotation.Implements;
import org.robolectric.internal.ShadowProvider;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
public class ShadowMap {
public static final ShadowMap EMPTY = new ShadowMap(Collections.<String, ShadowConfig>emptyMap());
private final Map<String, ShadowConfig> map;
private static final Map<String, String> SHADOWS = new HashMap<>();
static {
for (ShadowProvider provider : ServiceLoader.load(ShadowProvider.class)) {
SHADOWS.putAll(provider.getShadowMap());
}
}
ShadowMap(Map<String, ShadowConfig> map) {
this.map = new HashMap<>(map);
}
public ShadowConfig get(Class<?> clazz) {
ShadowConfig shadowConfig = map.get(clazz.getName());
if (shadowConfig == null && clazz.getClassLoader() != null) {
Class<?> shadowClass = getShadowClass(clazz);
if (shadowClass == null) {
return null;
}
ShadowInfo shadowInfo = getShadowInfo(shadowClass);
if (shadowInfo != null && shadowInfo.shadowedClassName.equals(clazz.getName())) {
return shadowInfo.getShadowConfig();
}
}
return shadowConfig;
}
private static Class<?> getShadowClass(Class<?> clazz) {
try {
final String className = clazz.getCanonicalName();
if (className != null) {
final String shadowName = SHADOWS.get(className);
if (shadowName != null) {
return clazz.getClassLoader().loadClass(shadowName);
}
}
} catch (ClassNotFoundException e) {
return null;
} catch (IncompatibleClassChangeError e) {
return null;
}
return null;
}
public static ShadowInfo getShadowInfo(Class<?> clazz) {
Implements annotation = clazz.getAnnotation(Implements.class);
if (annotation == null) {
throw new IllegalArgumentException(clazz + " is not annotated with @Implements");
}
String className = annotation.className();
if (className.isEmpty()) {
className = annotation.value().getName();
}
return new ShadowInfo(className, new ShadowConfig(clazz.getName(), annotation));
}
public Set<String> getInvalidatedClasses(ShadowMap previous) {
if (this == previous) return Collections.emptySet();
Map<String, ShadowConfig> invalidated = new HashMap<>();
invalidated.putAll(map);
for (Map.Entry<String, ShadowConfig> entry : previous.map.entrySet()) {
String className = entry.getKey();
ShadowConfig previousConfig = entry.getValue();
ShadowConfig currentConfig = invalidated.get(className);
if (currentConfig == null) {
invalidated.put(className, previousConfig);
} else if (previousConfig.equals(currentConfig)) {
invalidated.remove(className);
}
}
return invalidated.keySet();
}
public static String convertToShadowName(String className) {
String shadowClassName =
"org.robolectric.shadows.Shadow" + className.substring(className.lastIndexOf(".") + 1);
shadowClassName = shadowClassName.replaceAll("\\$", "\\$Shadow");
return shadowClassName;
}
public Builder newBuilder() {
return new Builder(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShadowMap shadowMap = (ShadowMap) o;
if (!map.equals(shadowMap.map)) return false;
return true;
}
@Override
public int hashCode() {
return map.hashCode();
}
public static class Builder {
private final Map<String, ShadowConfig> map;
public Builder() {
map = new HashMap<>();
}
public Builder(ShadowMap shadowMap) {
this.map = new HashMap<>(shadowMap.map);
}
public Builder addShadowClasses(Class<?>... shadowClasses) {
for (Class<?> shadowClass : shadowClasses) {
addShadowClass(shadowClass);
}
return this;
}
public Builder addShadowClasses(Collection<Class<?>> shadowClasses) {
for (Class<?> shadowClass : shadowClasses) {
addShadowClass(shadowClass);
}
return this;
}
public Builder addShadowClass(Class<?> shadowClass) {
ShadowInfo shadowInfo = getShadowInfo(shadowClass);
if (shadowInfo != null) {
addShadowConfig(shadowInfo.getShadowedClassName(), shadowInfo.getShadowConfig());
}
return this;
}
public Builder addShadowClass(String realClassName, Class<?> shadowClass, boolean callThroughByDefault, boolean inheritImplementationMethods, boolean looseSignatures) {
addShadowClass(realClassName, shadowClass.getName(), callThroughByDefault, inheritImplementationMethods, looseSignatures);
return this;
}
public Builder addShadowClass(Class<?> realClass, Class<?> shadowClass, boolean callThroughByDefault, boolean inheritImplementationMethods, boolean looseSignatures) {
addShadowClass(realClass.getName(), shadowClass.getName(), callThroughByDefault, inheritImplementationMethods, looseSignatures);
return this;
}
public Builder addShadowClass(String realClassName, String shadowClassName, boolean callThroughByDefault, boolean inheritImplementationMethods, boolean looseSignatures) {
addShadowConfig(realClassName, new ShadowConfig(shadowClassName, callThroughByDefault, inheritImplementationMethods, looseSignatures, -1, -1));
return this;
}
private void addShadowConfig(String realClassName, ShadowConfig shadowConfig) {
map.put(realClassName, shadowConfig);
}
public ShadowMap build() {
return new ShadowMap(map);
}
}
public static class ShadowInfo {
private final String shadowedClassName;
private final ShadowConfig shadowConfig;
ShadowInfo(String shadowedClassName, ShadowConfig shadowConfig) {
this.shadowConfig = shadowConfig;
this.shadowedClassName = shadowedClassName;
}
public String getShadowedClassName() {
return shadowedClassName;
}
public ShadowConfig getShadowConfig() {
return shadowConfig;
}
}
}