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

io.github.serpro69.kfaker.provider.RandomProvider.kt Maven / Gradle / Ivy

Go to download

Generate realistically looking fake data such as names, addresses, banking details, and many more, that can be used for testing and data anonymization purposes.

There is a newer version: 2.0.0-rc.7
Show newest version
package io.github.serpro69.kfaker.provider

import io.github.serpro69.kfaker.RandomService
import java.util.*
import kotlin.Boolean
import kotlin.Char
import kotlin.Double
import kotlin.Float
import kotlin.Int
import kotlin.Long
import kotlin.NoSuchElementException
import kotlin.Short
import kotlin.String
import kotlin.reflect.KClass
import kotlin.reflect.KVisibility

/**
 * Provider for functionality not covered by the standard dictionary files.
 *
 * Inspired by [Creating a random instance of any class in Kotlin blog post](https://blog.kotlin-academy.com/creating-a-random-instance-of-any-class-in-kotlin-b6168655b64a).
 */
@Suppress("unused")
class RandomProvider internal constructor(random: Random) {
    private val randomService = RandomService(random)

    /**
     * Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
     * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
     *
     * @throws NoSuchElementException if [T] has no public constructor.
     */
    inline fun  randomClassInstance() = T::class.randomClassInstance(RandomProviderConfig())

    /**
     * Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
     * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
     *
     * @param configurator configure instance creation.
     *
     * @throws NoSuchElementException if [T] has no public constructor.
     */
    inline fun  randomClassInstance(configurator: RandomProviderConfig.() -> Unit): T {
        val config = RandomProviderConfig().apply(configurator)
        return T::class.randomClassInstance(config)
    }

    @JvmSynthetic
    @PublishedApi
    internal fun  KClass.randomClassInstance(config: RandomProviderConfig): T {
        val defaultInstance: T? = if (
            config.constructorParamSize == -1
            && config.constructorFilterStrategy == ConstructorFilterStrategy.NO_ARGS
        ) {
            constructors.firstOrNull { it.parameters.isEmpty() && it.visibility == KVisibility.PUBLIC }?.call()
        } else null

        return defaultInstance ?: objectInstance ?: run {
            val constructors = constructors
                .filter { it.visibility == KVisibility.PUBLIC }

            val constructor = constructors.firstOrNull {
                it.parameters.size == config.constructorParamSize
            } ?: when (config.constructorFilterStrategy) {
                ConstructorFilterStrategy.MIN_NUM_OF_ARGS -> constructors.minByOrNull { it.parameters.size }
                ConstructorFilterStrategy.MAX_NUM_OF_ARGS -> constructors.maxByOrNull { it.parameters.size }
                else -> {
                    when (config.fallbackStrategy) {
                        FallbackStrategy.FAIL_IF_NOT_FOUND -> {
                            throw NoSuchElementException("Constructor with 'parameters.size == ${config.constructorParamSize}' not found for $this")
                        }
                        FallbackStrategy.USE_MIN_NUM_OF_ARGS -> constructors.minByOrNull { it.parameters.size }
                        FallbackStrategy.USE_MAX_NUM_OF_ARGS -> constructors.maxByOrNull { it.parameters.size }
                    }
                }
            } ?: throw NoSuchElementException("No suitable constructor found for $this")

            val params = constructor.parameters
                .map { it.type }
                .map {
                    val klass = it.classifier as KClass<*>
                    if (it.isMarkedNullable && config.nullableGenerators.containsKey(klass)) {
                        config.nullableGenerators[klass]?.invoke()
                    } else {
                        klass.predefinedTypeOrNull(config)
                            ?: klass.randomPrimitiveOrNull()
                            ?: klass.objectInstance
                            ?: klass.randomEnumOrNull()
                            ?: klass.randomSealedClassOrNull(config)
                            ?: klass.randomClassInstance(config)
                    }
                }
                .toTypedArray()

            constructor.call(*params)
        }
    }

    private fun  KClass.predefinedTypeOrNull(config: RandomProviderConfig): Any? {
        return config.predefinedGenerators[this]?.invoke()
    }

    /**
     * Handles generation of primitive types since they do not have a public constructor.
     */
    private fun KClass<*>.randomPrimitiveOrNull(): Any? = when (this) {
        Double::class -> randomService.nextDouble()
        Float::class -> randomService.nextFloat()
        Long::class -> randomService.nextLong()
        Int::class -> randomService.nextInt()
        Short::class -> randomService.nextInt().toShort()
        Byte::class -> randomService.nextInt().toByte()
        String::class -> randomService.nextString()
        Char::class -> randomService.nextChar()
        Boolean::class -> randomService.nextBoolean()
        // TODO: 16.06.19 Arrays
        else -> null
    }

    /**
     * Handles generation of enums types since they do not have a public constructor.
     */
    private fun KClass<*>.randomEnumOrNull(): Any? {
        return if (this.java.isEnum) randomService.randomValue(this.java.enumConstants) else null
    }

    private fun KClass<*>.randomSealedClassOrNull(config: RandomProviderConfig): Any? {
        return if (isSealed) randomService.randomValue(sealedSubclasses).randomClassInstance(config) else null
    }
}

/**
 * Configuration for [RandomProvider.randomClassInstance].
 *
 * @property constructorParamSize will try to look up the constructor with specified number of arguments,
 * and use that to create the instance of the class.
 * Defaults to `-1`, which ignores this configuration property.
 * This takes precedence over [constructorFilterStrategy] configuration.
 *
 * @property constructorFilterStrategy default strategy for looking up a constructor
 * that is used to create the instance of a class.
 * By default a zero-args constructor will be used.
 *
 * @property fallbackStrategy fallback strategy that is used to look up a constructor
 * if no constructor with [constructorParamSize] or [constructorFilterStrategy] was found.
 */
class RandomProviderConfig @PublishedApi internal constructor() {
    var constructorParamSize: Int = -1
    var constructorFilterStrategy: ConstructorFilterStrategy = ConstructorFilterStrategy.NO_ARGS
    var fallbackStrategy: FallbackStrategy = FallbackStrategy.USE_MIN_NUM_OF_ARGS

    @PublishedApi
    internal val predefinedGenerators = mutableMapOf, () -> Any>()

    @PublishedApi
    internal val nullableGenerators = mutableMapOf, () -> Any?>()

    /**
     * Configures generation for a specific type. It can override internal generators (for primitives, for example)
     */
    inline fun  typeGenerator(noinline generator: () -> K) {
        predefinedGenerators[K::class] = generator
    }

    /**
     * Configures generation for a specific nullable type. It can override internal generators (for primitives, for example)
     */
    inline fun  nullableTypeGenerator(noinline generator: () -> K?) {
        nullableGenerators[K::class] = generator
    }
}

enum class FallbackStrategy {
    USE_MIN_NUM_OF_ARGS,
    USE_MAX_NUM_OF_ARGS,
    FAIL_IF_NOT_FOUND
}

enum class ConstructorFilterStrategy {
    NO_ARGS,
    MIN_NUM_OF_ARGS,
    MAX_NUM_OF_ARGS
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy