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

commonMain.com.episode6.mockspresso2.internal.MxoInstance.kt Maven / Gradle / Ivy

package com.episode6.mockspresso2.internal

import com.episode6.mockspresso2.InvalidTypeReturnedFromMakerError
import com.episode6.mockspresso2.MockspressoInstance
import com.episode6.mockspresso2.api.Dependencies
import com.episode6.mockspresso2.api.DynamicObjectMaker
import com.episode6.mockspresso2.api.FallbackObjectMaker
import com.episode6.mockspresso2.api.RealObjectMaker
import com.episode6.mockspresso2.internal.util.DependencyCacheBuilder
import com.episode6.mockspresso2.internal.util.DependencyValidator
import com.episode6.mockspresso2.internal.util.RealObjectRequestsList
import com.episode6.mockspresso2.internal.util.runOnce
import com.episode6.mockspresso2.reflect.DependencyKey
import com.episode6.mockspresso2.reflect.asKClass

internal class MxoInstance(
  private val parent: MxoInstance? = null,
  val realMaker: RealObjectMaker,
  val fallbackMaker: FallbackObjectMaker,
  private val dynamicMakers: List,
  setupCallbacks: MutableList<(MockspressoInstance) -> Unit>,
  private val teardownCallbacks: List<() -> Unit>,
  dependenciesBuilder: DependencyCacheBuilder,
  private val realObjectRequests: RealObjectRequestsList,
) {

  private val dependencies = dependenciesBuilder.build { asDependencies() }

  val ensureInit: () -> Unit by runOnce {
    parent?.ensureInit?.invoke()

    // warm real objects cache
    realObjectRequests.forEach { key ->
      if (!dependencies.containsKey(key)) {
        createInternal(key, DependencyValidator(key), cache = true)
      }
    }

    // fire setup callbacks
    val container = MockspressoInstanceContainer(this)
    setupCallbacks.forEach { it.invoke(container) }
  }

  fun  get(key: DependencyKey): T {
    ensureInit()
    return getInternal(key, DependencyValidator(key))
  }

  fun  create(key: DependencyKey): T {
    ensureInit()
    return createInternal(key, DependencyValidator(key), cache = false)
  }

  fun teardown() {
    ensureInit()
    teardownCallbacks.forEach { it.invoke() }
    parent?.teardown()
  }

  private fun  canGetInternal(key: DependencyKey, validator: DependencyValidator): TypedObjectAnswer {
    if (dependencies.containsKey(key)) return Yes(dependencies.get(key, validator))
    if (realObjectRequests.containsKey(key)) return Yes(createInternal(key, validator, cache = true))

    val isSpecial = dynamicMakers.canMake(key, validator.asDependencies())
    if (isSpecial is DynamicObjectMaker.Answer.Yes) return isSpecial.castAndCache(key, validator)

    return parent?.canGetInternal(key, validator) ?: No
  }

  private fun  getInternal(key: DependencyKey, validator: DependencyValidator): T {
    return when (val got = canGetInternal(key, validator)) {
      is Yes -> got.value
      is No  -> fallbackMaker.makeObject(key).cacheWith(key, validator)
    }
  }

  private fun  createInternal(key: DependencyKey, validator: DependencyValidator, cache: Boolean): T =
    realMaker
      .makeRealObject(realObjectRequests.getImplFor(key), validator.asDependencies())
      .checkedCast(key)
      .let { realObjectRequests.intercept(key, it) }
      .also { if (cache) it.cacheWith(key, validator) }

  private fun DependencyValidator.asDependencies(): Dependencies = object : Dependencies {
    override fun  get(key: DependencyKey): T = getInternal(key, childValidator(key))
  }

  private fun  T.cacheWith(key: DependencyKey, validator: DependencyValidator): T =
    also { dependencies.put(key, validator) { it } }

  private fun  DynamicObjectMaker.Answer.Yes.castAndCache(
    key: DependencyKey,
    validator: DependencyValidator
  ): Yes =
    Yes(value.checkedCast(key).cacheWith(key, validator))
}

private fun List.canMake(
  key: DependencyKey<*>,
  dependencies: Dependencies
): DynamicObjectMaker.Answer = firstNotNullOfOrNull { maker ->
  maker.canMakeObject(key, dependencies).takeIf { it is DynamicObjectMaker.Answer.Yes }
} ?: DynamicObjectMaker.Answer.No

private sealed class TypedObjectAnswer
private data class Yes(val value: T) : TypedObjectAnswer()
private object No : TypedObjectAnswer()

@Suppress("UNCHECKED_CAST")
private fun  Any?.checkedCast(key: DependencyKey): T {
  if (!key.token.asKClass().isInstance(this)) {
    throw InvalidTypeReturnedFromMakerError("Tried to create $key and got un-castable result: $this")
  }
  return this as T
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy