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