1
2
3
4
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 }