1
2
3
4
5
6 package mockit.internal.faking;
7
8 import edu.umd.cs.findbugs.annotations.NonNull;
9 import edu.umd.cs.findbugs.annotations.Nullable;
10
11 import java.lang.reflect.Field;
12 import java.lang.reflect.InvocationTargetException;
13 import java.lang.reflect.Method;
14 import java.util.IdentityHashMap;
15 import java.util.Map;
16 import java.util.Map.Entry;
17
18 import mockit.MockUp;
19 import mockit.internal.util.ClassLoad;
20
21 public final class FakeClasses {
22 private static final Field INVOKED_INSTANCE_FIELD;
23 private static final Method ON_TEAR_DOWN_METHOD;
24
25 static {
26 try {
27 INVOKED_INSTANCE_FIELD = MockUp.class.getDeclaredField("invokedInstance");
28 INVOKED_INSTANCE_FIELD.setAccessible(true);
29
30 ON_TEAR_DOWN_METHOD = MockUp.class.getDeclaredMethod("onTearDown");
31 ON_TEAR_DOWN_METHOD.setAccessible(true);
32 } catch (NoSuchFieldException | NoSuchMethodException e) {
33 throw new RuntimeException(e);
34 }
35 }
36
37 private static void notifyOfTearDown(@NonNull MockUp<?> mockUp) {
38 try {
39 ON_TEAR_DOWN_METHOD.invoke(mockUp);
40 } catch (IllegalAccessException ignore) {
41 } catch (InvocationTargetException e) {
42 e.getCause().printStackTrace();
43 }
44 }
45
46 public static final class MockUpInstances {
47 @NonNull
48 public final MockUp<?> initialMockUp;
49 boolean hasMockupsForSingleInstances;
50
51 MockUpInstances(@NonNull MockUp<?> initialMockUp) {
52 this.initialMockUp = initialMockUp;
53 hasMockupsForSingleInstances = false;
54 }
55
56 public boolean hasMockUpsForSingleInstances() {
57 return hasMockupsForSingleInstances;
58 }
59
60 void notifyMockUpOfTearDown() {
61 notifyOfTearDown(initialMockUp);
62 }
63 }
64
65 @NonNull
66 private final Map<String, MockUp<?>> startupMocks;
67 @NonNull
68 private final Map<Class<?>, MockUpInstances> mockupClassesToMockupInstances;
69 @NonNull
70 private final Map<Object, MockUp<?>> mockedToMockupInstances;
71 @NonNull
72 public final FakeStates fakeStates;
73
74 public FakeClasses() {
75 startupMocks = new IdentityHashMap<>(8);
76 mockupClassesToMockupInstances = new IdentityHashMap<>();
77 mockedToMockupInstances = new IdentityHashMap<>();
78 fakeStates = new FakeStates();
79 }
80
81 public void addFake(@NonNull String mockClassDesc, @NonNull MockUp<?> mockUp) {
82 startupMocks.put(mockClassDesc, mockUp);
83 }
84
85 public void addFake(@NonNull MockUp<?> mockUp) {
86 Class<?> mockUpClass = mockUp.getClass();
87 MockUpInstances newData = new MockUpInstances(mockUp);
88 mockupClassesToMockupInstances.put(mockUpClass, newData);
89 }
90
91 public void addFake(@NonNull MockUp<?> mockUp, @NonNull Object mockedInstance) {
92 MockUp<?> previousMockup = mockedToMockupInstances.put(mockedInstance, mockUp);
93 assert previousMockup == null;
94
95 MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUp.getClass());
96 mockUpInstances.hasMockupsForSingleInstances = true;
97 }
98
99 @Nullable
100 public MockUp<?> getFake(@NonNull String mockUpClassDesc, @Nullable Object mockedInstance) {
101 if (mockedInstance != null) {
102 MockUp<?> mockUpForSingleInstance = mockedToMockupInstances.get(mockedInstance);
103
104 if (mockUpForSingleInstance != null) {
105 return mockUpForSingleInstance;
106 }
107 }
108
109 MockUp<?> startupMock = startupMocks.get(mockUpClassDesc);
110
111 if (startupMock != null) {
112 return startupMock;
113 }
114
115 Class<?> mockUpClass = ClassLoad.loadByInternalName(mockUpClassDesc);
116 MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUpClass);
117 Object invokedInstance = mockedInstance;
118
119 if (mockedInstance == null) {
120 invokedInstance = Void.class;
121 } else if (mockUpInstances.hasMockUpsForSingleInstances()) {
122 return null;
123 }
124
125 try {
126 INVOKED_INSTANCE_FIELD.set(mockUpInstances.initialMockUp, invokedInstance);
127 } catch (IllegalAccessException ignore) {
128 }
129
130 return mockUpInstances.initialMockUp;
131 }
132
133 @Nullable
134 public MockUpInstances findPreviouslyAppliedMockUps(@NonNull MockUp<?> newMockUp) {
135 Class<?> mockUpClass = newMockUp.getClass();
136 MockUpInstances mockUpInstances = mockupClassesToMockupInstances.get(mockUpClass);
137
138 if (mockUpInstances != null && mockUpInstances.hasMockupsForSingleInstances) {
139 fakeStates.copyFakeStates(mockUpInstances.initialMockUp, newMockUp);
140 }
141
142 return mockUpInstances;
143 }
144
145 private void discardMockupInstances(@NonNull Map<Object, MockUp<?>> previousMockInstances) {
146 if (!previousMockInstances.isEmpty()) {
147 mockedToMockupInstances.entrySet().retainAll(previousMockInstances.entrySet());
148 } else if (!mockedToMockupInstances.isEmpty()) {
149 mockedToMockupInstances.clear();
150 }
151 }
152
153 private void discardMockupInstancesExceptPreviousOnes(@NonNull Map<Class<?>, Boolean> previousMockupClasses) {
154 updatePreviousMockups(previousMockupClasses);
155
156 for (Entry<Class<?>, MockUpInstances> mockupClassAndInstances : mockupClassesToMockupInstances.entrySet()) {
157 Class<?> mockupClass = mockupClassAndInstances.getKey();
158
159 if (!previousMockupClasses.containsKey(mockupClass)) {
160 MockUpInstances mockUpInstances = mockupClassAndInstances.getValue();
161 mockUpInstances.notifyMockUpOfTearDown();
162 }
163 }
164
165 mockupClassesToMockupInstances.keySet().retainAll(previousMockupClasses.keySet());
166 }
167
168 private void updatePreviousMockups(@NonNull Map<Class<?>, Boolean> previousMockupClasses) {
169 for (Entry<Class<?>, Boolean> mockupClassAndData : previousMockupClasses.entrySet()) {
170 Class<?> mockupClass = mockupClassAndData.getKey();
171 MockUpInstances mockUpData = mockupClassesToMockupInstances.get(mockupClass);
172 mockUpData.hasMockupsForSingleInstances = mockupClassAndData.getValue();
173 }
174 }
175
176 private void discardAllMockupInstances() {
177 if (!mockupClassesToMockupInstances.isEmpty()) {
178 for (MockUpInstances mockUpInstances : mockupClassesToMockupInstances.values()) {
179 mockUpInstances.notifyMockUpOfTearDown();
180 }
181
182 mockupClassesToMockupInstances.clear();
183 }
184 }
185
186 public void discardStartupFakes() {
187 for (MockUp<?> startupMockup : startupMocks.values()) {
188 notifyOfTearDown(startupMockup);
189 }
190 }
191
192 public final class SavePoint {
193 @NonNull
194 private final Map<Object, MockUp<?>> previousMockInstances;
195 @NonNull
196 private final Map<Class<?>, Boolean> previousMockupClasses;
197
198 public SavePoint() {
199 previousMockInstances = new IdentityHashMap<>(mockedToMockupInstances);
200 previousMockupClasses = new IdentityHashMap<>();
201
202 for (Entry<Class<?>, MockUpInstances> mockUpClassAndData : mockupClassesToMockupInstances.entrySet()) {
203 Class<?> mockUpClass = mockUpClassAndData.getKey();
204 MockUpInstances mockUpData = mockUpClassAndData.getValue();
205 previousMockupClasses.put(mockUpClass, mockUpData.hasMockupsForSingleInstances);
206 }
207 }
208
209 public void rollback() {
210 discardMockupInstances(previousMockInstances);
211
212 if (!previousMockupClasses.isEmpty()) {
213 discardMockupInstancesExceptPreviousOnes(previousMockupClasses);
214 } else {
215 discardAllMockupInstances();
216 }
217 }
218 }
219 }