Skip to content

Commit bb0c67a

Browse files
committed
Extract all value class handling (type deduction, boxing, unboxing, etc.) to ValueClassSupport.kt
1 parent f6023c0 commit bb0c67a

File tree

5 files changed

+306
-74
lines changed

5 files changed

+306
-74
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/ArgumentCaptor.kt

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
package org.mockito.kotlin
2727

2828
import org.mockito.ArgumentCaptor
29+
import org.mockito.kotlin.internal.toKotlinType
2930
import org.mockito.kotlin.internal.createInstance
31+
import org.mockito.kotlin.internal.valueClassInnerClass
3032
import java.lang.reflect.Array
3133
import kotlin.reflect.KClass
3234
import kotlin.reflect.KType
@@ -164,53 +166,50 @@ class KArgumentCaptor<out T : Any?>(private val kType: KType) {
164166

165167
private val captor: ArgumentCaptor<Any?> =
166168
if (clazz.isValue && !kType.isMarkedNullable) {
167-
val boxImpl =
168-
clazz.java.declaredMethods
169-
.single { it.name == "box-impl" && it.parameterCount == 1 }
170-
boxImpl.parameters[0].type // is the boxed type of the value type
169+
clazz.valueClassInnerClass()
171170
} else {
172-
clazz.java
171+
clazz
173172
}.let {
174-
ArgumentCaptor.forClass(it)
173+
ArgumentCaptor.forClass(it.java)
175174
}
176175

177176
/**
178177
* The first captured value of the argument.
179178
* @throws IndexOutOfBoundsException if the value is not available.
180179
*/
181180
val firstValue: T
182-
get() = toKotlinType(captor.firstValue)
181+
get() = captor.firstValue.toKotlinType(clazz)
183182

184183
/**
185184
* The second captured value of the argument.
186185
* @throws IndexOutOfBoundsException if the value is not available.
187186
*/
188187
val secondValue: T
189-
get() = toKotlinType(captor.secondValue)
188+
get() = captor.secondValue.toKotlinType(clazz)
190189

191190
/**
192191
* The third captured value of the argument.
193192
* @throws IndexOutOfBoundsException if the value is not available.
194193
*/
195194
val thirdValue: T
196-
get() = toKotlinType(captor.thirdValue)
195+
get() = captor.thirdValue.toKotlinType(clazz)
197196

198197
/**
199198
* The last captured value of the argument.
200199
* @throws IndexOutOfBoundsException if the value is not available.
201200
*/
202201
val lastValue: T
203-
get() = toKotlinType(captor.lastValue)
202+
get() = captor.lastValue.toKotlinType(clazz)
204203

205204
/**
206205
* The *only* captured value of the argument,
207206
* or throws an exception if no value or more than one value was captured.
208207
*/
209208
val singleValue: T
210-
get() = toKotlinType(captor.singleValue)
209+
get() = captor.singleValue.toKotlinType(clazz)
211210

212211
val allValues: List<T>
213-
get() = captor.allValues.map(::toKotlinType)
212+
get() = captor.allValues.map { it.toKotlinType(clazz) }
214213

215214
@Suppress("UNCHECKED_CAST")
216215
fun capture(): T {
@@ -221,30 +220,14 @@ class KArgumentCaptor<out T : Any?>(private val kType: KType) {
221220
// In Java, `captor.capture` returns null and so the method is called with `[null]`
222221
// In Kotlin, we have to create `[null]` explicitly.
223222
// This code-path is applied for non-vararg array arguments as well, but it seems to work fine.
224-
return toKotlinType(captor.capture()) ?: if (clazz.java.isArray) {
223+
return captor.capture().toKotlinType(clazz) ?: if (clazz.java.isArray) {
225224
singleElementArray()
226225
} else {
227226
createInstance(clazz)
228227
} as T
229228
}
230229

231230
private fun singleElementArray(): Any? = Array.newInstance(clazz.java.componentType, 1)
232-
233-
@Suppress("UNCHECKED_CAST")
234-
private fun toKotlinType(rawCapturedValue: Any?): T {
235-
if (rawCapturedValue == null) return null as T
236-
237-
if (clazz.isValue && rawCapturedValue::class != clazz) {
238-
return rawCapturedValue
239-
.let {
240-
val boxImpl =
241-
clazz.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
242-
boxImpl.invoke(null, it)
243-
} as T
244-
}
245-
246-
return rawCapturedValue as T
247-
}
248231
}
249232

250233
val <T> ArgumentCaptor<T>.firstValue: T

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ package org.mockito.kotlin
2727

2828
import org.mockito.ArgumentMatcher
2929
import org.mockito.ArgumentMatchers
30+
import org.mockito.kotlin.internal.boxAsValueClass
3031
import org.mockito.kotlin.internal.createInstance
32+
import org.mockito.kotlin.internal.toJavaType
33+
import org.mockito.kotlin.internal.toKotlinType
34+
import org.mockito.kotlin.internal.valueClassInnerClass
3135
import kotlin.reflect.KClass
32-
import kotlin.reflect.typeOf
3336

3437
/** Object argument that is equal to the given value. */
3538
inline fun <reified T : Any?> eq(value: T): T {
@@ -79,39 +82,15 @@ inline fun <reified T : Any?> anyArray(): Array<T> {
7982
}
8083

8184
/** Matches any Kotlin value class with the same boxed type by taking its boxed type. */
82-
inline fun <reified T > anyValueClass(): T {
83-
require(T::class.isValue) {
84-
"${T::class.qualifiedName} is not a value class."
85-
}
86-
87-
val boxImpl =
88-
T::class.java.declaredMethods
89-
.single { it.name == "box-impl" && it.parameterCount == 1 }
90-
val boxedType = boxImpl.parameters[0].type
91-
92-
return boxImpl.invoke(null, ArgumentMatchers.any(boxedType)) as T
85+
inline fun <reified T> anyValueClass(): T {
86+
val clazz = T::class
87+
return ArgumentMatchers.any(clazz.valueClassInnerClass().java).boxAsValueClass(clazz)
9388
}
9489

9590
inline fun <reified T> eqValueClass(value: T): T {
96-
require(T::class.isValue) {
97-
"${T::class.qualifiedName} is not a value class."
98-
}
99-
100-
if (typeOf<T>().isMarkedNullable) {
101-
// if the value is both value class and nullable, then Kotlin passes the value class boxed
102-
// towards Mockito java code.
103-
return ArgumentMatchers.eq(value)
91+
return value.toJavaType().let {
92+
(ArgumentMatchers.eq(it) ?: it).toKotlinType(T::class)
10493
}
105-
106-
val unboxImpl =
107-
T::class.java.declaredMethods
108-
.single { it.name == "unbox-impl" && it.parameterCount == 0 }
109-
val unboxed = unboxImpl.invoke(value)
110-
111-
val boxImpl =
112-
T::class.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
113-
114-
return boxImpl.invoke(null, ArgumentMatchers.eq(unboxed) ?: unboxed) as T
11594
}
11695

11796
/**

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/CreateInstance.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
package org.mockito.kotlin.internal
2727

2828
import kotlin.reflect.KClass
29-
import kotlin.reflect.KProperty1
30-
import kotlin.reflect.full.primaryConstructor
3129

3230
inline fun <reified T : Any> createInstance(): T {
3331
return createInstance(T::class)
@@ -48,13 +46,10 @@ fun <T : Any> createInstance(kClass: KClass<T>): T {
4846
}
4947
}
5048

51-
@Suppress("UNCHECKED_CAST")
5249
private fun <T : Any> createInstanceNonPrimitive(kClass: KClass<T>): T {
5350
return if (kClass.isValue) {
54-
val boxImpl =
55-
kClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
56-
val wrappedType = getValueClassWrappedType(kClass)
57-
boxImpl.invoke(null, createInstance(wrappedType)) as T
51+
createInstance(kClass.valueClassInnerClass())
52+
.boxAsValueClass(kClass)
5853
} else {
5954
castNull()
6055
}
@@ -68,11 +63,3 @@ private fun <T : Any> createInstanceNonPrimitive(kClass: KClass<T>): T {
6863
*/
6964
@Suppress("UNCHECKED_CAST")
7065
private fun <T> castNull(): T = null as T
71-
72-
private fun getValueClassWrappedType(kClass: KClass<*>): KClass<*> {
73-
require(kClass.isValue)
74-
75-
val primaryConstructor = checkNotNull(kClass.primaryConstructor)
76-
val wrappedType = primaryConstructor.parameters.single().type
77-
return wrappedType.classifier as KClass<*>
78-
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2018 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
package org.mockito.kotlin.internal
27+
28+
import java.lang.reflect.Method
29+
import kotlin.reflect.KClass
30+
import kotlin.reflect.typeOf
31+
32+
@Suppress("UNCHECKED_CAST")
33+
fun <T : Any?> Any?.toKotlinType(clazz: KClass<*>): T {
34+
if (this == null) return null as T
35+
36+
return if (clazz.isValue && this::class != clazz) {
37+
this.boxAsValueClass(clazz) as T
38+
} else {
39+
this as T
40+
}
41+
}
42+
43+
inline fun <reified T> T.toJavaType(): Any? {
44+
if (this == null) return null
45+
46+
return if (this::class.isValue && !typeOf<T>().isMarkedNullable) {
47+
this.unboxValueClass()
48+
} else {
49+
// if the value is both value class and nullable, then Kotlin passes the value class boxed
50+
// towards Mockito java code.
51+
this
52+
}
53+
}
54+
55+
@Suppress("UNCHECKED_CAST")
56+
fun <T : Any?> Any?.boxAsValueClass(clazz: KClass<*>): T {
57+
require(clazz.isValue) { "${clazz.qualifiedName} is not a value class." }
58+
59+
val boxImpl = clazz.boxImpl()
60+
return boxImpl.invoke(null, this) as T
61+
}
62+
63+
fun Any.unboxValueClass(): Any {
64+
val clazz = this::class
65+
require(clazz.isValue) { "${clazz.qualifiedName} is not a value class." }
66+
67+
val unboxImpl =
68+
clazz.java.declaredMethods
69+
.single { it.name == "unbox-impl" && it.parameterCount == 0 }
70+
71+
return unboxImpl.invoke(this)
72+
}
73+
74+
fun KClass<*>.valueClassInnerClass(): KClass<*> {
75+
require(isValue) { "$qualifiedName is not a value class." }
76+
77+
return boxImpl().parameters[0].type.kotlin
78+
}
79+
80+
private fun KClass<*>.boxImpl(): Method =
81+
java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }

0 commit comments

Comments
 (0)