-
-
Notifications
You must be signed in to change notification settings - Fork 332
/
AndroidSubclassInstrumentation.kt
77 lines (65 loc) · 2.85 KB
/
AndroidSubclassInstrumentation.kt
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
package io.mockk.proxy.android.transformation
import android.os.Build
import com.android.dx.stock.ProxyBuilder
import com.android.dx.stock.ProxyBuilder.MethodSetEntry
import io.mockk.proxy.MockKAgentException
import io.mockk.proxy.MockKInvocationHandler
import io.mockk.proxy.common.ProxyInvocationHandler
import io.mockk.proxy.common.transformation.SubclassInstrumentation
import java.lang.reflect.Method
import java.lang.reflect.Modifier
internal class AndroidSubclassInstrumentation(
val inlineInstrumentationApplied: Boolean
) : SubclassInstrumentation {
@Suppress("UNCHECKED_CAST")
override fun <T> subclass(clazz: Class<T>, interfaces: Array<Class<*>>): Class<T> =
try {
ProxyBuilder.forClass(clazz)
.implementing(*interfaces)
.apply {
if (inlineInstrumentationApplied) {
onlyMethods(getMethodsToProxy(clazz, interfaces))
}
}
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// markTrusted();
}
}
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
withSharedClassLoader()
}
}
.buildProxyClass() as Class<T>
} catch (e: Exception) {
throw MockKAgentException("Failed to mock $clazz", e)
}
private fun <T> getMethodsToProxy(clazz: Class<T>, interfaces: Array<Class<*>>): Array<Method> {
val abstractMethods = mutableSetOf<MethodSetEntry>()
val nonAbstractMethods = mutableSetOf<MethodSetEntry>()
tailrec fun fillInAbstractAndNonAbstract(clazz: Class<*>) {
clazz.declaredMethods
.filter { Modifier.isAbstract(it.modifiers) }
.map { MethodSetEntry(it) }
.filterNotTo(abstractMethods) { it in nonAbstractMethods }
clazz.declaredMethods
.filterNot { Modifier.isAbstract(it.modifiers) }
.mapTo(nonAbstractMethods) { MethodSetEntry(it) }
fillInAbstractAndNonAbstract(clazz.superclass ?: return)
}
fillInAbstractAndNonAbstract(clazz)
fun Class<*>.allSuperInterfaces(): Set<Class<*>> {
val setOfInterfaces = this.interfaces.toSet()
return setOfInterfaces + setOfInterfaces.flatMap { it.allSuperInterfaces() }
}
(clazz.interfaces + interfaces)
.asSequence()
.flatMap { it.allSuperInterfaces().asSequence() }
.flatMap { it.methods.asSequence() }
.map { MethodSetEntry(it) }
.filterNot { it in nonAbstractMethods }
.mapTo(abstractMethods) { it }
return abstractMethods.map { it.originalMethod }.toTypedArray()
}
}