/
SubclassByteBuddyMockMaker.java
193 lines (173 loc) · 7.95 KB
/
SubclassByteBuddyMockMaker.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
* Copyright (c) 2016 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.creation.bytebuddy;
import static org.mockito.internal.util.StringUtil.join;
import java.lang.reflect.Modifier;
import org.mockito.creation.instance.Instantiator;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Platform;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
/**
* Subclass based mock maker.
*
* This mock maker tries to create a subclass to represent a mock. It uses the given mock settings, that contains
* the type to mock, extra interfaces, and serialization support.
*
* <p>
* The type to mock has to be not final and not part of the JDK. THe created mock will implement extra interfaces
* if any. And will implement <code>Serializable</code> if this settings is explicitly set.
*/
public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
private final BytecodeGenerator cachingMockBytecodeGenerator;
public SubclassByteBuddyMockMaker() {
this(new SubclassInjectionLoader());
}
public SubclassByteBuddyMockMaker(SubclassLoader loader) {
cachingMockBytecodeGenerator =
new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(loader), false);
}
@Override
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
Class<? extends T> mockedProxyType = createMockType(settings);
Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
T mockInstance = null;
try {
mockInstance = instantiator.newInstance(mockedProxyType);
MockAccess mockAccess = (MockAccess) mockInstance;
mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
return ensureMockIsAssignableToMockedType(settings, mockInstance);
} catch (ClassCastException cce) {
throw new MockitoException(
join(
"ClassCastException occurred while creating the mockito mock :",
" class to mock : " + describeClass(settings.getTypeToMock()),
" created class : " + describeClass(mockedProxyType),
" proxy instance class : " + describeClass(mockInstance),
" instance creation by : " + instantiator.getClass().getSimpleName(),
"",
"You might experience classloading issues, please ask the mockito mailing-list.",
""),
cce);
} catch (org.mockito.creation.instance.InstantiationException e) {
throw new MockitoException(
"Unable to create mock instance of type '"
+ mockedProxyType.getSuperclass().getSimpleName()
+ "'",
e);
}
}
@Override
public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
try {
return cachingMockBytecodeGenerator.mockClass(
MockFeatures.withMockFeatures(
settings.getTypeToMock(),
settings.getExtraInterfaces(),
settings.getSerializableMode(),
settings.isStripAnnotations(),
settings.getDefaultAnswer()));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
}
}
private static <T> T ensureMockIsAssignableToMockedType(
MockCreationSettings<T> settings, T mock) {
// Force explicit cast to mocked type here, instead of
// relying on the JVM to implicitly cast on the client call site.
// This allows us to catch earlier the ClassCastException earlier
Class<T> typeToMock = settings.getTypeToMock();
return typeToMock.cast(mock);
}
private <T> RuntimeException prettifyFailure(
MockCreationSettings<T> mockFeatures, Exception generationFailed) {
if (mockFeatures.getTypeToMock().isArray()) {
throw new MockitoException(
join("Mockito cannot mock arrays: " + mockFeatures.getTypeToMock() + ".", ""),
generationFailed);
}
if (Modifier.isPrivate(mockFeatures.getTypeToMock().getModifiers())) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
"Most likely it is due to mocking a private class that is not visible to Mockito",
""),
generationFailed);
}
throw new MockitoException(
join(
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
"",
"Mockito can only mock non-private & non-final classes.",
"If you're not sure why you're getting this error, please report to the mailing list.",
"",
Platform.warnForVM(
"IBM J9 VM",
"Early IBM virtual machine are known to have issues with Mockito, please upgrade to an up-to-date version.\n",
"Hotspot",
Platform.isJava8BelowUpdate45()
? "Java 8 early builds have bugs that were addressed in Java 1.8.0_45, please update your JDK!\n"
: ""),
Platform.describe(),
"",
"Underlying exception : " + generationFailed),
generationFailed);
}
private static String describeClass(Class<?> type) {
return type == null
? "null"
: "'"
+ type.getCanonicalName()
+ "', loaded by classloader : '"
+ type.getClassLoader()
+ "'";
}
private static String describeClass(Object instance) {
return instance == null ? "null" : describeClass(instance.getClass());
}
@Override
public MockHandler getHandler(Object mock) {
if (!(mock instanceof MockAccess)) {
return null;
}
return ((MockAccess) mock).getMockitoInterceptor().getMockHandler();
}
@Override
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
((MockAccess) mock).setMockitoInterceptor(new MockMethodInterceptor(newHandler, settings));
}
@Override
public TypeMockability isTypeMockable(final Class<?> type) {
return new TypeMockability() {
@Override
public boolean mockable() {
return !type.isPrimitive()
&& !Modifier.isFinal(type.getModifiers())
&& !TypeSupport.INSTANCE.isSealed(type);
}
@Override
public String nonMockableReason() {
if (mockable()) {
return "";
}
if (type.isPrimitive()) {
return "primitive type";
}
if (Modifier.isFinal(type.getModifiers())) {
return "final class";
}
if (TypeSupport.INSTANCE.isSealed(type)) {
return "sealed class";
}
return join("not handled type");
}
};
}
@Override
public void clearAllCaches() {
cachingMockBytecodeGenerator.clearAllCaches();
}
}