View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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 }