All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nhaarman.mockito_kotlin.createinstance.InstanceCreator.kt Maven / Gradle / Ivy

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
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy