View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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 }