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

io.github.serpro69.kfaker.provider.AbstractFakeDataProvider.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.Faker
import io.github.serpro69.kfaker.FakerService
import io.github.serpro69.kfaker.dictionary.Category
import io.github.serpro69.kfaker.dictionary.CategoryName
import io.github.serpro69.kfaker.exception.RetryLimitException
import io.github.serpro69.kfaker.provider.unique.LocalUniqueDataProvider

/**
 * Abstract class for all concrete [FakeDataProvider]'s.
 *
 * All data providers should implement this class.
 *
 * @param T type of data provider (i.e. [Address])
 */
abstract class AbstractFakeDataProvider internal constructor(
    internal val fakerService: FakerService
) : FakeDataProvider {

    /**
     * Name of the category for `this` fake data provider class.
     *
     * This is the key entry after the `faker` key in `.yml` locale file.
     *
     * For example `address.yml` file has the following structure - `en: faker: address:`,
     * then the category name would be [CategoryName.ADDRESS]
     */
    internal abstract val categoryName: CategoryName

    /**
     * A [LocalUniqueDataProvider] instance that is used with this [unique] provider.
     */
    internal abstract val localUniqueDataProvider: LocalUniqueDataProvider

    /**
     * An instance of [T] for generating unique values
     */
    abstract val unique: T

    /**
     * Clears used unique values for the function [name] of this provider.
     */
    fun clear(name: String) = localUniqueDataProvider.clear(name)

    /**
     * Clears all used unique values of this provider.
     */
    fun clearAll() = localUniqueDataProvider.clearAll()

    /**
     * Higher-order function that resolves the [block] expression for this [categoryName].
     *
     * @return parameterless function that returns a [String]: `() -> String`
     */
    private fun resolve(block: (Category) -> String): () -> String = {
        block(fakerService.fetchCategory(categoryName))
    }

    /**
     * Returns resolved (unique) value for the parameter with the specified [key].
     *
     * Will return a unique value if the call to the function is prefixed with `unique` property.
     * Example:
     * ```
     * faker.address.unique.city() => will return a unique value for the `city` parameter
     * ```
     */
    protected fun resolve(key: String): String {
        return returnOrResolveUnique(key)
    }

    /**
     * Returns resolved (unique) value for the parameter with the specified [primaryKey] and [secondaryKey].
     *
     * Will return a unique value if the call to the function is prefixed with `unique` property.
     * Example:
     * ```
     * faker.address.unique.countryByCode(countryCode) => will return a unique value for the `city` parameter
     * ```
     */
    protected fun resolve(primaryKey: String, secondaryKey: String): String {
        return returnOrResolveUnique(primaryKey, secondaryKey)
    }

    /**
     * Returns resolved (unique) value for the parameter with the specified [primaryKey], [secondaryKey] and [thirdKey]
     *
     * Will return a unique value if the call to the function is prefixed with `unique` property.
     * Example:
     * ```
     * faker.educator.tertiaryDegree(type) => will return a unique value for the `tertiaryDegree` parameter
     * ```
     */
    protected fun resolve(primaryKey: String, secondaryKey: String, thirdKey: String): String {
        return returnOrResolveUnique(primaryKey, secondaryKey, thirdKey)
    }

    /**
     * Returns the result of this [resolve] function.
     *
     * IF [Faker.unique] is enabled for this [T] provider type
     * OR this [unique] is used
     * THEN will attempt to return a unique value.
     *
     * @throws RetryLimitException if exceeds number of retries to generate a unique value.
     */
    private tailrec fun returnOrResolveUnique(
        primaryKey: String,
        secondaryKey: String? = null,
        thirdKey: String? = null,
        counter: Int = 0
    ): String {
        val result = if (secondaryKey != null && thirdKey != null) {
            resolve { fakerService.resolve(it, primaryKey, secondaryKey, thirdKey) }.invoke()
        } else if (secondaryKey != null) {
            resolve { fakerService.resolve(it, primaryKey, secondaryKey) }.invoke()
        } else {
            resolve { fakerService.resolve(it, primaryKey) }.invoke()
        }

        val globalUniqueProvider = fakerService.faker.unique
        val fakerConfig = fakerService.faker.fakerConfig

        val key = listOfNotNull(primaryKey, secondaryKey, thirdKey).joinToString("$")

        return if (localUniqueDataProvider.markedUnique.contains(this)) {
            // if function is prefixed with `unique` -> try to resolve a unique value
            when (val set = localUniqueDataProvider.usedValues[key]) {
                null -> {
                    localUniqueDataProvider.usedValues[key] = mutableSetOf(result)
                    result
                }
                else -> {
                    if (counter >= fakerConfig.uniqueGeneratorRetryLimit) {
                        throw RetryLimitException("Retry limit of $counter exceeded")
                    } else if (!set.contains(result)) result.also {
                        localUniqueDataProvider.usedValues[key] = mutableSetOf(result).also { it.addAll(set) }
                    } else returnOrResolveUnique(
                        primaryKey = primaryKey,
                        secondaryKey = secondaryKey,
                        thirdKey = thirdKey,
                        counter = counter + 1
                    )
                }
            }
        } else if (!globalUniqueProvider.config.markedUnique.contains(this::class)) {
            // if global unique provider is not enabled for this category -> return result
            result
        } else {
            val exclusionValues = globalUniqueProvider.config.excludedValues
            val exclusionPatterns = globalUniqueProvider.config.excludedPatterns
            val usedProviderValues = requireNotNull(globalUniqueProvider.config.usedProviderValues[this::class])
            val providerExclusionPatterns = requireNotNull(globalUniqueProvider.config.providerExclusionPatterns[this::class])
            val providerFunctionExclusionPatternsMap = requireNotNull(globalUniqueProvider.config.providerFunctionExclusionPatterns[this::class])
            val usedProviderFunctionsValuesMap = requireNotNull(globalUniqueProvider.config.usedProviderFunctionValues[this::class])

            when {
                // Globally excluded values
                exclusionValues.isNotEmpty() && exclusionValues.contains(result) -> {
                    returnOrResolveUnique(
                        primaryKey = primaryKey,
                        secondaryKey = secondaryKey,
                        thirdKey = thirdKey,
                        counter = counter + 1
                    )
                }
                // Global exclusion patterns
                exclusionPatterns.isNotEmpty() && exclusionPatterns.any { r -> r.containsMatchIn(result) } -> {
                    returnOrResolveUnique(
                        primaryKey = primaryKey,
                        secondaryKey = secondaryKey,
                        thirdKey = thirdKey,
                        counter = counter + 1
                    )
                }
                // Provider-based excluded values for all functions
                usedProviderValues.isNotEmpty() && usedProviderValues.contains(result) -> {
                    returnOrResolveUnique(
                        primaryKey = primaryKey,
                        secondaryKey = secondaryKey,
                        thirdKey = thirdKey,
                        counter = counter + 1
                    )
                }
                // Provider-based exclusion patterns for all functions
                providerExclusionPatterns.isNotEmpty() && providerExclusionPatterns.any { it.containsMatchIn(result) } -> {
                    returnOrResolveUnique(
                        primaryKey = primaryKey,
                        secondaryKey = secondaryKey,
                        thirdKey = thirdKey,
                        counter = counter + 1
                    )
                }
                else -> {
                    val patterns = providerFunctionExclusionPatternsMap[key]
                    val usedValues = usedProviderFunctionsValuesMap[key]

                    when {
                        patterns != null && patterns.isNotEmpty() && patterns.any { r -> r.containsMatchIn(result) } -> {
                            returnOrResolveUnique(
                                primaryKey = primaryKey,
                                secondaryKey = secondaryKey,
                                thirdKey = thirdKey,
                                counter = counter + 1
                            )
                        }
                        usedValues == null -> {
                            usedProviderFunctionsValuesMap[key] = mutableSetOf(result)
                            result
                        }
                        else -> {
                            if (counter >= fakerConfig.uniqueGeneratorRetryLimit) {
                                throw RetryLimitException("Retry limit of $counter exceeded")
                            } else if (!usedValues.contains(result)) result.also {
                                usedProviderFunctionsValuesMap[key] = mutableSetOf(result).also { it.addAll(usedValues) }
                            } else returnOrResolveUnique(
                                primaryKey = primaryKey,
                                secondaryKey = secondaryKey,
                                thirdKey = thirdKey,
                                counter = counter + 1
                            )
                        }
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy