Skip to content

Commit

Permalink
Improvements to configuration overriding (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmook committed Oct 6, 2019
1 parent 377e08f commit 674634b
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 176 deletions.
13 changes: 13 additions & 0 deletions fixture/src/main/kotlin/com/appmattus/kotlinfixture/ContextImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.appmattus.kotlinfixture

import com.appmattus.kotlinfixture.config.Configuration
import com.appmattus.kotlinfixture.resolver.CompositeResolver
import com.appmattus.kotlinfixture.resolver.Resolver

internal data class ContextImpl(override val configuration: Configuration) : Context {
private val baseResolver = CompositeResolver(configuration.resolvers) as Resolver

override val resolver = configuration.decorators.fold(baseResolver) { resolver, decorator ->
decorator.decorate(resolver)
}
}
105 changes: 9 additions & 96 deletions fixture/src/main/kotlin/com/appmattus/kotlinfixture/KotlinFixture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,15 @@ package com.appmattus.kotlinfixture

import com.appmattus.kotlinfixture.config.Configuration
import com.appmattus.kotlinfixture.config.ConfigurationBuilder
import com.appmattus.kotlinfixture.decorator.Decorator
import com.appmattus.kotlinfixture.decorator.logging.LoggingDecorator
import com.appmattus.kotlinfixture.decorator.logging.SysOutLoggingStrategy
import com.appmattus.kotlinfixture.decorator.recursion.NullRecursionStrategy
import com.appmattus.kotlinfixture.decorator.recursion.RecursionDecorator
import com.appmattus.kotlinfixture.decorator.recursion.ThrowingRecursionStrategy
import com.appmattus.kotlinfixture.resolver.AbstractClassResolver
import com.appmattus.kotlinfixture.resolver.ArrayResolver
import com.appmattus.kotlinfixture.resolver.BigDecimalResolver
import com.appmattus.kotlinfixture.resolver.BigIntegerResolver
import com.appmattus.kotlinfixture.resolver.CalendarResolver
import com.appmattus.kotlinfixture.resolver.CharResolver
import com.appmattus.kotlinfixture.resolver.ClassResolver
import com.appmattus.kotlinfixture.resolver.CompositeResolver
import com.appmattus.kotlinfixture.resolver.DateResolver
import com.appmattus.kotlinfixture.resolver.EnumMapResolver
import com.appmattus.kotlinfixture.resolver.EnumResolver
import com.appmattus.kotlinfixture.resolver.EnumSetResolver
import com.appmattus.kotlinfixture.resolver.HashtableKTypeResolver
import com.appmattus.kotlinfixture.resolver.IterableKTypeResolver
import com.appmattus.kotlinfixture.resolver.KFunctionResolver
import com.appmattus.kotlinfixture.resolver.KTypeResolver
import com.appmattus.kotlinfixture.resolver.MapKTypeResolver
import com.appmattus.kotlinfixture.resolver.ObjectResolver
import com.appmattus.kotlinfixture.resolver.PrimitiveArrayResolver
import com.appmattus.kotlinfixture.resolver.PrimitiveResolver
import com.appmattus.kotlinfixture.resolver.Resolver
import com.appmattus.kotlinfixture.resolver.SealedClassResolver
import com.appmattus.kotlinfixture.resolver.StringResolver
import com.appmattus.kotlinfixture.resolver.SubTypeResolver
import com.appmattus.kotlinfixture.resolver.UriResolver
import com.appmattus.kotlinfixture.resolver.UrlResolver
import com.appmattus.kotlinfixture.resolver.UuidResolver
import kotlin.random.Random
import kotlin.reflect.KType
import kotlin.reflect.typeOf

class Fixture(private val baseConfiguration: Configuration) {

private val baseResolver = CompositeResolver(
CharResolver(),
StringResolver(),
PrimitiveResolver(),
UrlResolver(),
UriResolver(),
BigDecimalResolver(),
BigIntegerResolver(),
UuidResolver(),
EnumResolver(),
CalendarResolver(),
DateResolver(),

ObjectResolver(),
SealedClassResolver(),

ArrayResolver(),

PrimitiveArrayResolver(),
HashtableKTypeResolver(),
IterableKTypeResolver(),
EnumSetResolver(),
EnumMapResolver(),
MapKTypeResolver(),
KTypeResolver(),
KFunctionResolver(),

SubTypeResolver(),

AbstractClassResolver(),

ClassResolver()
)

private val baseDecorators: List<Decorator> = listOf(
RecursionDecorator(ThrowingRecursionStrategy())
)
class Fixture(val fixtureConfiguration: Configuration) {

inline operator fun <reified T : Any?> invoke(
range: Iterable<T> = emptyList(),
Expand All @@ -88,7 +21,7 @@ class Fixture(private val baseConfiguration: Configuration) {
rangeShuffled.first()
} else {
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
val result = create(typeOf<T>(), ConfigurationBuilder().apply(configuration).build())
val result = create(typeOf<T>(), ConfigurationBuilder(fixtureConfiguration).apply(configuration).build())
if (result is T) {
result
} else {
Expand All @@ -99,34 +32,12 @@ class Fixture(private val baseConfiguration: Configuration) {
}

fun create(type: KType, configuration: Configuration): Any? {
val resolver = combineDecorators(configuration).fold(baseResolver as Resolver) { resolver, decorator ->
decorator.decorate(resolver)
}

val context = object : Context {
override val configuration = baseConfiguration + configuration
override val resolver = resolver
}
return context.resolve(type)
}

private fun combineDecorators(configuration: Configuration): List<Decorator> {
val base = baseDecorators.filterClassNotIn(baseConfiguration).filterClassNotIn(configuration)
val baseConfigStart = baseConfiguration.decoratorsAtStart.filterClassNotIn(configuration)
val baseConfigEnd = baseConfiguration.decoratorsAtEnd.filterClassNotIn(configuration)

return configuration.decoratorsAtStart + baseConfigStart + base + baseConfigEnd + configuration.decoratorsAtEnd
}

private fun List<Decorator>.filterClassNotIn(configuration: Configuration): List<Decorator> {
return this.filterNot { decorator ->
configuration.decoratorsAtStart.map { it::class }.contains(decorator::class) ||
configuration.decoratorsAtEnd.map { it::class }.contains(decorator::class)
}
return ContextImpl(configuration).resolve(type)
}
}

fun kotlinFixture(init: ConfigurationBuilder.() -> Unit = {}) = Fixture(ConfigurationBuilder().apply(init).build())
fun kotlinFixture(init: ConfigurationBuilder.() -> Unit = {}) =
Fixture(ConfigurationBuilder().apply(init).build())

class TestClass(val bob: String) {
override fun toString() = "TestClass [bob=$bob]"
Expand Down Expand Up @@ -163,7 +74,7 @@ fun main() {
fixture<List<String>> {
repeatCount { 2 }

addDecorator(LoggingDecorator(SysOutLoggingStrategy()))
decorators.add(LoggingDecorator(SysOutLoggingStrategy()))
}

println(fixture(listOf(1, 2, 3)))
Expand All @@ -178,7 +89,9 @@ fun main() {
})

println(fixture<A> {
addDecorator(RecursionDecorator(NullRecursionStrategy()))
decorators.removeIf { it is RecursionDecorator }
decorators.add(RecursionDecorator(NullRecursionStrategy()))
decorators.add(LoggingDecorator(SysOutLoggingStrategy()))
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.appmattus.kotlinfixture

import java.util.Collections

internal fun <T> List<T>.circularIterator() = object : Iterator<T> {
private var position = 0

Expand All @@ -12,3 +14,7 @@ internal fun <T> List<T>.circularIterator() = object : Iterator<T> {
}
}
}

fun <K, V> Map<K, V>.toUnmodifiableMap(): Map<K, V> = Collections.unmodifiableMap(this)

fun <T> List<T>.toUnmodifiableList(): List<T> = Collections.unmodifiableList(this)
4 changes: 2 additions & 2 deletions fixture/src/main/kotlin/com/appmattus/kotlinfixture/ToDo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ private object ToDo {
// - https://blog.kotlin-academy.com/creating-a-random-instance-of-any-class-in-kotlin-b6168655b64a

// TODO Add tests:
// TODO - Test configuration overloading in Configuration
// TODO - Test ConfigurationBuilder
// TODO - Test DateSpecification

// TODO Code coverage; Add badge to readme

// TODO Write readme/documentation

// TODO Remove println statements
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
package com.appmattus.kotlinfixture.config

import com.appmattus.kotlinfixture.decorator.Decorator
import com.appmattus.kotlinfixture.decorator.recursion.RecursionDecorator
import com.appmattus.kotlinfixture.decorator.recursion.ThrowingRecursionStrategy
import com.appmattus.kotlinfixture.resolver.AbstractClassResolver
import com.appmattus.kotlinfixture.resolver.ArrayResolver
import com.appmattus.kotlinfixture.resolver.BigDecimalResolver
import com.appmattus.kotlinfixture.resolver.BigIntegerResolver
import com.appmattus.kotlinfixture.resolver.CalendarResolver
import com.appmattus.kotlinfixture.resolver.CharResolver
import com.appmattus.kotlinfixture.resolver.ClassResolver
import com.appmattus.kotlinfixture.resolver.DateResolver
import com.appmattus.kotlinfixture.resolver.EnumMapResolver
import com.appmattus.kotlinfixture.resolver.EnumResolver
import com.appmattus.kotlinfixture.resolver.EnumSetResolver
import com.appmattus.kotlinfixture.resolver.HashtableKTypeResolver
import com.appmattus.kotlinfixture.resolver.IterableKTypeResolver
import com.appmattus.kotlinfixture.resolver.KFunctionResolver
import com.appmattus.kotlinfixture.resolver.KTypeResolver
import com.appmattus.kotlinfixture.resolver.MapKTypeResolver
import com.appmattus.kotlinfixture.resolver.ObjectResolver
import com.appmattus.kotlinfixture.resolver.PrimitiveArrayResolver
import com.appmattus.kotlinfixture.resolver.PrimitiveResolver
import com.appmattus.kotlinfixture.resolver.Resolver
import com.appmattus.kotlinfixture.resolver.SealedClassResolver
import com.appmattus.kotlinfixture.resolver.StringResolver
import com.appmattus.kotlinfixture.resolver.SubTypeResolver
import com.appmattus.kotlinfixture.resolver.UriResolver
import com.appmattus.kotlinfixture.resolver.UrlResolver
import com.appmattus.kotlinfixture.resolver.UuidResolver
import com.appmattus.kotlinfixture.toUnmodifiableList
import com.appmattus.kotlinfixture.toUnmodifiableMap
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
Expand All @@ -9,45 +39,56 @@ import kotlin.reflect.KType
data class Configuration(
val dateSpecification: DateSpecification = defaultDateSpecification,
val repeatCount: () -> Int = defaultRepeatCount,
val properties: Map<KClass<*>, Map<String, Any?>> = emptyMap(),
val instances: Map<KType, () -> Any?> = emptyMap(),
val subTypes: Map<KClass<*>, KClass<*>> = emptyMap(),
internal val decoratorsAtStart: List<Decorator> = emptyList(),
internal val decoratorsAtEnd: List<Decorator> = emptyList()
val properties: Map<KClass<*>, Map<String, () -> Any?>> =
emptyMap<KClass<*>, Map<String, () -> Any?>>().toUnmodifiableMap(),
val instances: Map<KType, () -> Any?> = emptyMap<KType, () -> Any?>().toUnmodifiableMap(),
val subTypes: Map<KClass<*>, KClass<*>> = emptyMap<KClass<*>, KClass<*>>().toUnmodifiableMap(),
val decorators: List<Decorator> = defaultDecorators.toUnmodifiableList(),
val resolvers: List<Resolver> = defaultResolvers.toUnmodifiableList()
) {
operator fun plus(other: Configuration): Configuration {
val newDateSpecification = if (other.dateSpecification !== defaultDateSpecification) {
other.dateSpecification
} else {
dateSpecification
}

val newRepeatCount = if (other.repeatCount !== defaultRepeatCount) {
other.repeatCount
} else {
repeatCount
}

val newProperties = mutableMapOf<KClass<*>, Map<String, Any?>>()
newProperties.putAll(properties)
other.properties.forEach { (clazz, properties) ->
newProperties.compute(clazz) { _, origProperties ->
origProperties?.let { it + properties } ?: properties
}
}

val newInstances = instances + other.instances
val newSubTypes = subTypes + other.subTypes

return Configuration(newDateSpecification, newRepeatCount, newProperties, newInstances, newSubTypes)
}

companion object {
internal val defaultRepeatCount: () -> Int = { 5 }
private companion object {
private val defaultRepeatCount: () -> Int = { 5 }

internal val defaultDateSpecification: DateSpecification = DateSpecification.Between(
private val defaultDateSpecification: DateSpecification = DateSpecification.Between(
Date(Date().time - TimeUnit.DAYS.toMillis(365)),
Date(Date().time + TimeUnit.DAYS.toMillis(365))
)

private val defaultDecorators = listOf(RecursionDecorator(ThrowingRecursionStrategy()))

private val defaultResolvers = listOf(
CharResolver(),
StringResolver(),
PrimitiveResolver(),
UrlResolver(),
UriResolver(),
BigDecimalResolver(),
BigIntegerResolver(),
UuidResolver(),
EnumResolver(),
CalendarResolver(),
DateResolver(),

ObjectResolver(),
SealedClassResolver(),

ArrayResolver(),

PrimitiveArrayResolver(),
HashtableKTypeResolver(),
IterableKTypeResolver(),
EnumSetResolver(),
EnumMapResolver(),
MapKTypeResolver(),
KTypeResolver(),
KFunctionResolver(),

SubTypeResolver(),

AbstractClassResolver(),

ClassResolver()
)
}
}

0 comments on commit 674634b

Please sign in to comment.