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.expectations.mocking;
7   
8   import static mockit.internal.reflection.FieldReflection.getFieldValue;
9   
10  import edu.umd.cs.findbugs.annotations.NonNull;
11  import edu.umd.cs.findbugs.annotations.Nullable;
12  
13  import java.lang.instrument.ClassDefinition;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  
20  import mockit.asm.classes.ClassReader;
21  import mockit.asm.types.JavaType;
22  import mockit.internal.BaseClassModifier;
23  import mockit.internal.capturing.CaptureOfImplementations;
24  import mockit.internal.startup.Startup;
25  import mockit.internal.state.MockFixture;
26  import mockit.internal.state.TestRun;
27  import mockit.internal.util.Utilities;
28  
29  public class CaptureOfNewInstances extends CaptureOfImplementations<MockedType> {
30      protected static final class Capture {
31          @NonNull
32          final MockedType typeMetadata;
33          @Nullable
34          private Object originalMockInstance;
35          @NonNull
36          private final List<Object> instancesCaptured;
37  
38          private Capture(@NonNull MockedType typeMetadata, @Nullable Object originalMockInstance) {
39              this.typeMetadata = typeMetadata;
40              this.originalMockInstance = originalMockInstance;
41              instancesCaptured = new ArrayList<>(4);
42          }
43  
44          private boolean isInstanceAlreadyCaptured(@NonNull Object mock) {
45              return Utilities.containsReference(instancesCaptured, mock);
46          }
47  
48          private boolean captureInstance(@Nullable Object fieldOwner, @NonNull Object instance) {
49              if (instancesCaptured.size() < typeMetadata.getMaxInstancesToCapture()) {
50                  if (fieldOwner != null && typeMetadata.field != null && originalMockInstance == null) {
51                      originalMockInstance = getFieldValue(typeMetadata.field, fieldOwner);
52                  }
53  
54                  instancesCaptured.add(instance);
55                  return true;
56              }
57  
58              return false;
59          }
60  
61          void reset() {
62              originalMockInstance = null;
63              instancesCaptured.clear();
64          }
65      }
66  
67      @NonNull
68      private final Map<Class<?>, List<Capture>> baseTypeToCaptures;
69  
70      CaptureOfNewInstances() {
71          baseTypeToCaptures = new HashMap<>();
72      }
73  
74      @NonNull
75      protected final Collection<List<Capture>> getCapturesForAllBaseTypes() {
76          return baseTypeToCaptures.values();
77      }
78  
79      @NonNull
80      @Override
81      protected BaseClassModifier createModifier(@Nullable ClassLoader cl, @NonNull ClassReader cr,
82              @NonNull Class<?> baseType, @Nullable MockedType typeMetadata) {
83          MockedClassModifier modifier = new MockedClassModifier(cl, cr, typeMetadata);
84          String baseTypeDesc = JavaType.getInternalName(baseType);
85          modifier.setClassNameForCapturedInstanceMethods(baseTypeDesc);
86          return modifier;
87      }
88  
89      @Override
90      protected void redefineClass(@NonNull Class<?> realClass, @NonNull byte[] modifiedClass) {
91          ClassDefinition newClassDefinition = new ClassDefinition(realClass, modifiedClass);
92          Startup.redefineMethods(newClassDefinition);
93  
94          MockFixture mockFixture = TestRun.mockFixture();
95          mockFixture.addRedefinedClass(newClassDefinition);
96          mockFixture.registerMockedClass(realClass);
97      }
98  
99      void registerCaptureOfNewInstances(@NonNull MockedType typeMetadata, @Nullable Object mockInstance) {
100         Class<?> baseType = typeMetadata.getClassType();
101 
102         if (!typeMetadata.isFinalFieldOrParameter()) {
103             makeSureAllSubtypesAreModified(typeMetadata);
104         }
105 
106         List<Capture> captures = baseTypeToCaptures.computeIfAbsent(baseType, k -> new ArrayList<>());
107         captures.add(new Capture(typeMetadata, mockInstance));
108     }
109 
110     void makeSureAllSubtypesAreModified(@NonNull MockedType typeMetadata) {
111         Class<?> baseType = typeMetadata.getClassType();
112         makeSureAllSubtypesAreModified(baseType, typeMetadata.fieldFromTestClass, typeMetadata);
113     }
114 
115     public boolean captureNewInstance(@Nullable Object fieldOwner, @NonNull Object mock) {
116         Class<?> mockedClass = mock.getClass();
117         List<Capture> captures = baseTypeToCaptures.get(mockedClass);
118         boolean constructorModifiedForCaptureOnly = captures == null;
119 
120         if (constructorModifiedForCaptureOnly) {
121             captures = findCaptures(mockedClass);
122 
123             if (captures == null) {
124                 return false;
125             }
126         }
127 
128         Capture captureFound = findCapture(fieldOwner, mock, captures);
129 
130         if (captureFound != null) {
131             if (captureFound.typeMetadata.injectable) {
132                 TestRun.getExecutingTest().addCapturedInstanceForInjectableMock(captureFound.originalMockInstance,
133                         mock);
134                 constructorModifiedForCaptureOnly = true;
135             } else {
136                 TestRun.getExecutingTest().addCapturedInstance(captureFound.originalMockInstance, mock);
137             }
138         }
139 
140         return constructorModifiedForCaptureOnly;
141     }
142 
143     @Nullable
144     private List<Capture> findCaptures(@NonNull Class<?> mockedClass) {
145         Class<?>[] interfaces = mockedClass.getInterfaces();
146 
147         for (Class<?> anInterface : interfaces) {
148             List<Capture> found = baseTypeToCaptures.get(anInterface);
149 
150             if (found != null) {
151                 return found;
152             }
153         }
154 
155         Class<?> superclass = mockedClass.getSuperclass();
156 
157         if (superclass == Object.class) {
158             return null;
159         }
160 
161         List<Capture> found = baseTypeToCaptures.get(superclass);
162 
163         return found != null ? found : findCaptures(superclass);
164     }
165 
166     @Nullable
167     private static Capture findCapture(@Nullable Object fieldOwner, @NonNull Object mock,
168             @NonNull List<Capture> captures) {
169         for (Capture capture : captures) {
170             if (capture.isInstanceAlreadyCaptured(mock)) {
171                 break;
172             } else if (capture.captureInstance(fieldOwner, mock)) {
173                 return capture;
174             }
175         }
176 
177         return null;
178     }
179 
180     public void cleanUp() {
181         baseTypeToCaptures.clear();
182     }
183 }