com.nhaarman.mockito_kotlin.createinstance.InstanceCreator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mockito-kotlin Show documentation
Show all versions of mockito-kotlin Show documentation
Using Mockito with Kotlin.
The newest version!
package com.nhaarman.mockito_kotlin.createinstance
import com.nhaarman.mockito_kotlin.MockitoKotlin
import com.nhaarman.mockito_kotlin.MockitoKotlinException
import org.mockito.Answers
import org.mockito.internal.creation.MockSettingsImpl
import org.mockito.internal.creation.bytebuddy.MockAccess
import org.mockito.internal.util.MockUtil
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmName
import java.lang.reflect.Array as JavaArray
internal class InstanceCreator() : NonNullProvider {
override fun createInstance(kClass: KClass): T {
var cause: Throwable? = null
@Suppress("UNCHECKED_CAST")
return MockitoKotlin.instanceCreator(kClass)?.invoke() as T? ?:
try {
when {
kClass.hasObjectInstance() -> kClass.objectInstance!!
kClass.isPrimitive() -> kClass.toDefaultPrimitiveValue()
kClass.isEnum() -> kClass.java.enumConstants.first()
kClass.isArray() -> kClass.toArrayInstance()
kClass.isClassObject() -> kClass.toClassObject()
kClass.isMockable() -> try {
kClass.java.uncheckedMock()
} catch(e: Throwable) {
cause = e
kClass.easiestConstructor().newInstance()
}
else -> kClass.easiestConstructor().newInstance()
}
} catch(e: Exception) {
if (e is MockitoKotlinException) throw e
cause?.let {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
(e as java.lang.Throwable).initCause(it)
}
throw MockitoKotlinException("Could not create an instance for $kClass.", e)
}
}
/**
* Tries to find the easiest constructor which it can instantiate.
*/
private fun KClass.easiestConstructor(): KFunction {
return constructors
.sortedBy { it.parameters.withoutOptionalParameters().size }
.withoutParametersOfType(this.defaultType)
.withoutArrayParameters()
.firstOrNull() ?: constructors.sortedBy { it.parameters.withoutOptionalParameters().size }
.withoutParametersOfType(this.defaultType)
.first()
}
private fun List>.withoutArrayParameters() = filter {
it.parameters.filter { parameter -> parameter.type.toString().toLowerCase().contains("array") }.isEmpty()
}
/**
* Filters out functions with the given type.
* This is especially useful to avoid infinite loops where constructors
* accepting a parameter of their own type, e.g. 'copy constructors'.
*/
private fun List>.withoutParametersOfType(type: KType) = filter {
it.parameters.filter { it.type == type }.isEmpty()
}
private fun List.withoutOptionalParameters() = filterNot { it.isOptional }
@Suppress("SENSELESS_COMPARISON")
private fun KClass<*>.hasObjectInstance() = objectInstance != null
private fun KClass<*>.isMockable(): Boolean {
return !Modifier.isFinal(java.modifiers) || mockMakerInlineEnabled()
}
private fun KClass<*>.isEnum() = java.isEnum
private fun KClass<*>.isArray() = java.isArray
private fun KClass<*>.isClassObject() = jvmName.equals("java.lang.Class")
private fun KClass<*>.isPrimitive() =
java.isPrimitive || !defaultType.isMarkedNullable && simpleName in arrayOf(
"Boolean",
"Byte",
"Short",
"Int",
"Double",
"Float",
"Long",
"String"
)
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
private fun KClass.toDefaultPrimitiveValue(): T {
return when (simpleName) {
"Boolean" -> true
"Byte" -> 0.toByte()
"Short" -> 0.toShort()
"Int" -> 0
"Double" -> 0.0
"Float" -> 0f
"Long" -> 0
"String" -> ""
else -> throw UnsupportedOperationException("Cannot create default primitive for $simpleName.")
} as T
}
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
private fun KClass.toArrayInstance(): T {
return when (simpleName) {
"ByteArray" -> byteArrayOf()
"ShortArray" -> shortArrayOf()
"IntArray" -> intArrayOf()
"LongArray" -> longArrayOf()
"DoubleArray" -> doubleArrayOf()
"FloatArray" -> floatArrayOf()
else -> {
val name = java.name.drop(2).dropLast(1)
return JavaArray.newInstance(Class.forName(name), 0) as T
}
} as T
}
@Suppress("UNCHECKED_CAST")
private fun KClass.toClassObject(): T {
return Class.forName("java.lang.Object") as T
}
private fun KFunction.newInstance(): T {
try {
isAccessible = true
return callBy(parameters.withoutOptionalParameters().associate {
it to it.type.createNullableInstance()
})
} catch(e: InvocationTargetException) {
throw MockitoKotlinException(
"""
Could not create an instance of class ${this.returnType}, because of an error with the following message:
"${e.cause?.message}"
Try registering an instance creator yourself, using MockitoKotlin.registerInstanceCreator<${this.returnType}> {...}.""",
e.cause
)
}
}
@Suppress("UNCHECKED_CAST")
private fun KType.createNullableInstance(): T? {
if (isMarkedNullable) {
return null
}
val javaType: Type = javaType
return when (javaType) {
is ParameterizedType -> (javaType.rawType as Class).uncheckedMock()
is Class<*> -> createInstance((javaType as Class).kotlin)
else -> null
}
}
/**
* Creates a mock instance of given class, without modifying or checking any internal Mockito state.
*/
@Suppress("UNCHECKED_CAST")
fun Class.uncheckedMock(): T {
val impl = MockSettingsImpl().defaultAnswer(Answers.RETURNS_DEFAULTS) as MockSettingsImpl
val creationSettings = impl.setTypeToMock(this)
return MockUtil.createMock(creationSettings).apply {
(this as? MockAccess)?.mockitoInterceptor = null
}
}
}