blob: b48e9ed910659a2b2d8250557b21d4480ecad94b [file] [log] [blame]
Kiran Ryali + Christian Williams7be4d2b2013-03-01 18:10:16 -08001package org.robolectric.bytecode;
2
3import org.robolectric.internal.Implements;
4
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.Map;
10import java.util.Set;
11
12public class ShadowMap {
13 public static final ShadowMap EMPTY = new ShadowMap(Collections.<String, ShadowConfig>emptyMap());
14
15 private final Map<String, ShadowConfig> map;
16
Christian Williams36d87d72013-03-13 18:25:54 -070017 ShadowMap(Map<String, ShadowConfig> map) {
Kiran Ryali + Christian Williams7be4d2b2013-03-01 18:10:16 -080018 this.map = new HashMap<String, ShadowConfig>(map);
19 }
20
Christian Williams36d87d72013-03-13 18:25:54 -070021 public ShadowConfig get(String className) {
22 return map.get(className);
23 }
24
25 public ShadowConfig get(Class<?> clazz) {
26 return get(clazz.getName());
Kiran Ryali + Christian Williams7be4d2b2013-03-01 18:10:16 -080027 }
28
29 public Builder newBuilder() {
30 return new Builder(this);
31 }
32
Christian Williams36d87d72013-03-13 18:25:54 -070033 String getShadowClassName(Class clazz) {
34 ShadowConfig shadowConfig = null;
35 while (shadowConfig == null && clazz != null) {
36 shadowConfig = get(clazz.getName());
37 clazz = clazz.getSuperclass();
38 }
39 return shadowConfig == null ? null : shadowConfig.shadowClassName;
40 }
41
Christian Williamsa1c48302013-03-18 14:44:04 -070042 @Override
43 public boolean equals(Object o) {
44 if (this == o) return true;
45 if (o == null || getClass() != o.getClass()) return false;
46
47 ShadowMap shadowMap = (ShadowMap) o;
48
49 if (!map.equals(shadowMap.map)) return false;
50
51 return true;
52 }
53
54 @Override
55 public int hashCode() {
56 return map.hashCode();
57 }
58
Kiran Ryali + Christian Williams7be4d2b2013-03-01 18:10:16 -080059 public static class Builder {
60 private static final Set<String> unloadableClassNames = new HashSet<String>();
61
62 private static void warnAbout(String unloadableClassName) {
63 boolean alreadyReported;
64 synchronized (unloadableClassNames) {
65 alreadyReported = unloadableClassNames.add(unloadableClassName);
66 }
67 if (alreadyReported) {
68 System.out.println("Warning: an error occurred while binding shadow class: " + unloadableClassName);
69 }
70 }
71
72 private final Map<String, ShadowConfig> map;
73
74 public Builder() {
75 map = new HashMap<String, ShadowConfig>();
76 }
77
78 public Builder(ShadowMap shadowMap) {
79 this.map = new HashMap<String, ShadowConfig>(shadowMap.map);
80 }
81
82 public Builder addShadowClasses(Class<?>... shadowClasses) {
83 for (Class<?> shadowClass : shadowClasses) {
84 addShadowClass(shadowClass);
85 }
86 return this;
87 }
88
89 public Builder addShadowClasses(Collection<Class<?>> shadowClasses) {
90 for (Class<?> shadowClass : shadowClasses) {
91 addShadowClass(shadowClass);
92 }
93 return this;
94 }
95
96 public Builder addShadowClass(Class<?> shadowClass) {
97 Implements implementsAnnotation = shadowClass.getAnnotation(Implements.class);
98 if (implementsAnnotation == null) {
99 throw new IllegalArgumentException(shadowClass + " is not annotated with @Implements");
100 }
101
102 try {
103 String className = implementsAnnotation.value().getName();
104 if (!implementsAnnotation.className().isEmpty()) {
105 className = implementsAnnotation.className();
106 }
107 addShadowClass(className, shadowClass, implementsAnnotation.callThroughByDefault());
108 } catch (TypeNotPresentException typeLoadingException) {
109 String unloadableClassName = shadowClass.getSimpleName();
110 if (typeLoadingException.typeName().startsWith("com.google.android.maps")) {
111 warnAbout(unloadableClassName);
112 return this;
113 } else if (isIgnorableClassLoadingException(typeLoadingException)) {
114 //this allows users of the robolectric.jar file to use the non-Google APIs version of the api
115 warnAbout(unloadableClassName);
116 } else {
117 throw typeLoadingException;
118 }
119 }
120 return this;
121 }
122
123 public Builder addShadowClass(String realClassName, Class<?> shadowClass, boolean callThroughByDefault) {
124 addShadowClass(realClassName, shadowClass.getName(), callThroughByDefault);
125 return this;
126 }
127
128 public Builder addShadowClass(Class<?> realClass, Class<?> shadowClass, boolean callThroughByDefault) {
129 addShadowClass(realClass.getName(), shadowClass.getName(), callThroughByDefault);
130 return this;
131 }
132
133 public Builder addShadowClass(String realClassName, String shadowClassName, boolean callThroughByDefault) {
134 map.put(realClassName, new ShadowConfig(shadowClassName, callThroughByDefault));
135 return this;
136 }
137
138 public ShadowMap build() {
139 return new ShadowMap(map);
140 }
141
142 private static boolean isIgnorableClassLoadingException(Throwable typeLoadingException) {
143 if (typeLoadingException != null) {
144 // instanceof doesn't work here. Are we in different classloaders?
145 if (typeLoadingException.getClass().getName().equals(IgnorableClassNotFoundException.class.getName())) {
146 return true;
147 }
148
149 if (typeLoadingException instanceof NoClassDefFoundError
150 || typeLoadingException instanceof ClassNotFoundException
151 || typeLoadingException instanceof TypeNotPresentException) {
152 return isIgnorableClassLoadingException(typeLoadingException.getCause());
153 }
154 }
155 return false;
156 }
157 }
158}