Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make RandomClassProvider::createRandomInstance operating on KClass publicly accessible #257

Open
ajax-levashov-m opened this issue Jan 21, 2025 · 2 comments

Comments

@ajax-levashov-m
Copy link

ajax-levashov-m commented Jan 21, 2025

Hello, is there any chance that randomClassInstance operating on KClass could be made publicly accessible? At the moment, the only way to generate a fake instance is through interaction with inline methods which is unsuitable if we don't have concrete type at hand, but only KClass. Which is somewhat unfortunate as there is already an appropriate method with internal visibility modifier and calling which from Kotlin requires some additional hoops via reflection:

    private fun <T : Any> createRandomInstance(
        randomProvider: RandomClassProvider,
        klass: KClass<T>,
    ): T {
        val configClass = RandomProviderConfig::class

        val configConstructor = configClass.constructors.first { it.parameters.isEmpty() }
        val config = configConstructor.call()

        val randomClassInstanceMethod = RandomClassProvider::class.java
            .getDeclaredMethod(
                "randomClassInstance",
                KClass::class.java,
                configClass.java
            )

        @Suppress("UNCHECKED_CAST")
        return randomClassInstanceMethod.invoke(randomProvider, klass, config) as T
    }

For context: my use case is generating some arbitrary json for a KClass<*> whose underlying type is not necessary known in advance.

@serpro69
Copy link
Owner

Hi @ajax-levashov-m ,
Can you provide an example of what you're trying to achieve that doesn't work with existing functionality?
I think your use case is valid, so opening up the function to public visibility would be OK.

@ajax-levashov-m
Copy link
Author

@serpro69
In my code I attempt to provide a json representation of some entity that could be passed from outside. In spirit, it something akeen to how swagger-ui generates a json template of a request body of an endpoint for your convenience.

object ExampleProviderService {
    private val faker = Faker()

    fun tryGenerateJsonExample(klass: KClass<*>): String? =
        runCatching { generateExample(klass) }
            .map { JsonService.writeValueAsString(it) }
            .getOrNull()

    private fun <T: Any> generateExample(klass: KClass<T>): T {
        @Suppress("UNCHECKED_CAST")
        return when (klass) {
            String::class -> Defaults.STRING as T
            UUID::class -> Defaults.Uuid as T
            Int::class -> Defaults.INT as T
            Double::class -> Defaults.DOUBLE as T
            Long::class -> Defaults.LONG as T
            Boolean::class -> Defaults.BOOLEAN as T
            Float::class -> Defaults.FLOAT as T
            Short::class -> Defaults.SHORT as T
            Byte::class -> Defaults.BYTE as T
            Char::class -> Defaults.CHAR as T
            else -> createRandomInstance(faker.randomProvider, klass) {
                configureDefaultTypeGenerators()
            }
        }
    }

    private fun RandomProviderConfig.configureDefaultTypeGenerators() {
        typeGenerator<String> { Defaults.STRING }
        typeGenerator<UUID> { Defaults.Uuid }
        typeGenerator<Int> { Defaults.INT }
        typeGenerator<Double> { Defaults.DOUBLE }
        typeGenerator<Long> { Defaults.LONG }
        typeGenerator<Boolean> { Defaults.BOOLEAN }
        typeGenerator<Float> { Defaults.FLOAT }
        typeGenerator<Short> { Defaults.SHORT }
        typeGenerator<Byte> { Defaults.BYTE }
        typeGenerator<Char> { Defaults.CHAR }
    }

    // Going through hoops to access internal* randomClassInstance function from the RandomClassProvider
    private fun <T : Any> createRandomInstance(
        randomProvider: RandomClassProvider,
        klass: KClass<T>,
        builder: RandomProviderConfig.() -> Unit
    ): T {
        val configClass = RandomProviderConfig::class

        val configConstructor = configClass.constructors.first { it.parameters.isEmpty() }
        val config = configConstructor.call()
        builder.invoke(config)

        val randomClassInstanceMethod = RandomClassProvider::class.java
            .getDeclaredMethod(
                "randomClassInstance",
                KClass::class.java,
                configClass.java
            )

        @Suppress("UNCHECKED_CAST")
        return randomClassInstanceMethod.invoke(randomProvider, klass, config) as T
    }

    private object Defaults {
        const val STRING = ""
        const val INT = 0
        const val DOUBLE = 0.0
        const val LONG = 0L
        const val BOOLEAN = false
        const val FLOAT = 0.0f
        const val SHORT: Short = 0
        const val BYTE: Byte = 0
        const val CHAR = 'a'
        val Uuid: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000")
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants