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