|  | 
|  | 1 | +/* | 
|  | 2 | + * The MIT License | 
|  | 3 | + * | 
|  | 4 | + * Copyright (c) 2016 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 com.nhaarman.mockito_kotlin | 
|  | 27 | + | 
|  | 28 | +import org.mockito.Answers | 
|  | 29 | +import org.mockito.internal.creation.MockSettingsImpl | 
|  | 30 | +import org.mockito.internal.util.MockUtil | 
|  | 31 | +import java.lang.reflect.Modifier | 
|  | 32 | +import java.lang.reflect.ParameterizedType | 
|  | 33 | +import java.lang.reflect.Type | 
|  | 34 | +import kotlin.reflect.KClass | 
|  | 35 | +import kotlin.reflect.KFunction | 
|  | 36 | +import kotlin.reflect.KType | 
|  | 37 | +import kotlin.reflect.defaultType | 
|  | 38 | +import kotlin.reflect.jvm.isAccessible | 
|  | 39 | +import kotlin.reflect.jvm.javaType | 
|  | 40 | + | 
|  | 41 | +/** | 
|  | 42 | + * A collection of functions that tries to create an instance of | 
|  | 43 | + * classes to avoid NPE's when using Mockito with Kotlin. | 
|  | 44 | + */ | 
|  | 45 | + | 
|  | 46 | +inline fun <reified T> createArrayInstance() = arrayOf<T>() | 
|  | 47 | + | 
|  | 48 | +inline fun <reified T : Any> createInstance() = createInstance(T::class) | 
|  | 49 | + | 
|  | 50 | +fun <T : Any> createInstance(kClass: KClass<T>): T { | 
|  | 51 | +    return when { | 
|  | 52 | +        kClass.hasObjectInstance() -> kClass.objectInstance!! | 
|  | 53 | +        kClass.isMockable() -> kClass.java.uncheckedMock() | 
|  | 54 | +        kClass.isPrimitive() -> kClass.toDefaultPrimitiveValue() | 
|  | 55 | +        kClass.isEnum() -> kClass.java.enumConstants.first() | 
|  | 56 | +        kClass.isArray() -> kClass.toArrayInstance() | 
|  | 57 | +        else -> kClass.constructors.first().newInstance() | 
|  | 58 | +    } | 
|  | 59 | +} | 
|  | 60 | + | 
|  | 61 | +@Suppress("SENSELESS_COMPARISON") | 
|  | 62 | +private fun KClass<*>.hasObjectInstance() = objectInstance != null | 
|  | 63 | + | 
|  | 64 | +private fun KClass<*>.isMockable() = !Modifier.isFinal(java.modifiers) | 
|  | 65 | +private fun KClass<*>.isEnum() = java.isEnum | 
|  | 66 | +private fun KClass<*>.isArray() = java.isArray | 
|  | 67 | +private fun KClass<*>.isPrimitive() = | 
|  | 68 | +        java.isPrimitive || !defaultType.isMarkedNullable && simpleName in arrayOf( | 
|  | 69 | +                "Byte", | 
|  | 70 | +                "Short", | 
|  | 71 | +                "Int", | 
|  | 72 | +                "Double", | 
|  | 73 | +                "Float", | 
|  | 74 | +                "Long", | 
|  | 75 | +                "String" | 
|  | 76 | +        ) | 
|  | 77 | + | 
|  | 78 | +@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_UNIT_OR_ANY") | 
|  | 79 | +private fun <T : Any> KClass<T>.toDefaultPrimitiveValue(): T { | 
|  | 80 | +    return when (simpleName) { | 
|  | 81 | +        "Byte" -> 0.toByte() | 
|  | 82 | +        "Short" -> 0.toShort() | 
|  | 83 | +        "Int" -> 0 | 
|  | 84 | +        "Double" -> 0.0 | 
|  | 85 | +        "Float" -> 0f | 
|  | 86 | +        "Long" -> 0 | 
|  | 87 | +        "String" -> "" | 
|  | 88 | +        else -> throw UnsupportedOperationException("Cannot create default primitive for $simpleName.") | 
|  | 89 | +    } as T | 
|  | 90 | +} | 
|  | 91 | + | 
|  | 92 | +@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_UNIT_OR_ANY") | 
|  | 93 | +private fun <T : Any> KClass<T>.toArrayInstance(): T { | 
|  | 94 | +    return when (simpleName) { | 
|  | 95 | +        "ByteArray" -> byteArrayOf() | 
|  | 96 | +        "ShortArray" -> shortArrayOf() | 
|  | 97 | +        "IntArray" -> intArrayOf() | 
|  | 98 | +        "LongArray" -> longArrayOf() | 
|  | 99 | +        "DoubleArray" -> doubleArrayOf() | 
|  | 100 | +        "FloatArray" -> floatArrayOf() | 
|  | 101 | +        else -> throw UnsupportedOperationException("Cannot create a generic array for $simpleName. Use createArrayInstance() instead.") | 
|  | 102 | +    } as T | 
|  | 103 | +} | 
|  | 104 | + | 
|  | 105 | +private fun <T : Any> KFunction<T>.newInstance(): T { | 
|  | 106 | +    isAccessible = true | 
|  | 107 | +    return callBy(parameters.toMap { | 
|  | 108 | +        it to it.type.createNullableInstance<T>() | 
|  | 109 | +    }) | 
|  | 110 | +} | 
|  | 111 | + | 
|  | 112 | +@Suppress("UNCHECKED_CAST") | 
|  | 113 | +private fun <T : Any> KType.createNullableInstance(): T? { | 
|  | 114 | +    if (isMarkedNullable) { | 
|  | 115 | +        return null | 
|  | 116 | +    } | 
|  | 117 | + | 
|  | 118 | +    val javaType: Type = javaType | 
|  | 119 | +    return when (javaType) { | 
|  | 120 | +        is ParameterizedType -> (javaType.rawType as Class<T>).uncheckedMock() | 
|  | 121 | +        is Class<*> -> createInstance((javaType as Class<T>).kotlin) | 
|  | 122 | +        else -> null | 
|  | 123 | +    } | 
|  | 124 | +} | 
|  | 125 | + | 
|  | 126 | +/** | 
|  | 127 | + * Creates a mock instance of given class, without modifying or checking any internal Mockito state. | 
|  | 128 | + */ | 
|  | 129 | +@Suppress("UNCHECKED_CAST") | 
|  | 130 | +private fun <T> Class<T>.uncheckedMock(): T { | 
|  | 131 | +    val impl = MockSettingsImpl<T>().defaultAnswer(Answers.RETURNS_DEFAULTS) as MockSettingsImpl<*> | 
|  | 132 | +    val creationSettings = impl.confirm(this) | 
|  | 133 | +    return MockUtil().createMock(creationSettings) as T | 
|  | 134 | +} | 
0 commit comments