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

Getting rid of reflection for... many reasons #23

Open
serpro69 opened this issue Feb 1, 2020 · 1 comment
Open

Getting rid of reflection for... many reasons #23

serpro69 opened this issue Feb 1, 2020 · 1 comment
Labels
core 🧬 Issue related to :core module enhancement 🚀 New feature or request help wanted 🆘 Extra attention is needed

Comments

@serpro69
Copy link
Owner

serpro69 commented Feb 1, 2020

The main problem is that reflection affects all the linked issues, so using e.g. codegen (via ksp ?) instead would be a preferable solution at this point.

@serpro69 serpro69 added the enhancement 🚀 New feature or request label Feb 1, 2020
@serpro69 serpro69 added the core 🧬 Issue related to :core module label Jun 7, 2020
@serpro69 serpro69 changed the title Consider using AOT instead (addition to) reflection for better runtime performance Getting rid of reflection for... many reasons Oct 26, 2024
@serpro69 serpro69 added the help wanted 🆘 Extra attention is needed label Oct 26, 2024
@serpro69
Copy link
Owner Author

serpro69 commented Oct 26, 2024

Currently, reflection is mostly used to call other providers' functions for expressions like :

street_name:
- "#{Name.first_name} #{street_suffix}"
- "#{Name.last_name} #{street_suffix}"

But what if we update the FakerService like this:

diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
index 25c69297..431476b4 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
@@ -12,19 +12,13 @@ import io.github.serpro69.kfaker.dictionary.YamlCategory.SEPARATOR
 import io.github.serpro69.kfaker.dictionary.YamlCategoryData
 import io.github.serpro69.kfaker.dictionary.lowercase
 import io.github.serpro69.kfaker.exception.DictionaryKeyNotFoundException
-import io.github.serpro69.kfaker.provider.Address
-import io.github.serpro69.kfaker.provider.FakeDataProvider
 import io.github.serpro69.kfaker.provider.Name
-import io.github.serpro69.kfaker.provider.YamlFakeDataProvider
 import java.io.InputStream
 import java.util.*
 import java.util.regex.Matcher
 import kotlin.collections.component1
 import kotlin.collections.component2
 import kotlin.collections.set
-import kotlin.reflect.KFunction
-import kotlin.reflect.full.declaredMemberFunctions
-import kotlin.reflect.full.declaredMemberProperties
 
 /**
  * Internal class used for resolving yaml expressions into values.
@@ -307,7 +301,8 @@ class FakerService {
 
         return when (paramValue) {
             is List<*> -> {
-                if (paramValue.isEmpty()) RawExpression("") else when (val value = randomService.randomValue(paramValue)) {
+                if (paramValue.isEmpty()) RawExpression("") else when (val value =
+                    randomService.randomValue(paramValue)) {
                     is List<*> -> {
                         if (value.isEmpty()) RawExpression("") else RawExpression(randomService.randomValue(value) as String)
                     }
@@ -490,13 +485,9 @@ class FakerService {
         val resolvedExpression = when {
             curlyBraceRegex.containsMatchIn(rawExpression.value) -> {
                 findMatchesAndAppendTail(rawExpression.value, sb, curlyBraceRegex) {
-                    val simpleClassName = it.group(1)?.trimEnd('.')
-
-                    val replacement = when (simpleClassName != null) {
-                        true -> {
-                            val (providerType, propertyName) = getProvider(simpleClassName).getFunctionName(it.group(2))
-                            providerType.callFunction(propertyName)
-                        }
+                    val providerName = it.group(1)?.trimEnd('.')
+                    val replacement = when (providerName != null) {
+                        true -> getRawValue(category, "$providerName.${it.group(2)}").value
                         false -> getRawValue(category, it.group(2)).value
                     }
 
@@ -549,68 +540,6 @@ class FakerService {
     val String.regexify: () -> String
         get() = { RgxGen.parse(this).generate(faker.config.random) }
 
-    /**
-     * Calls the property of this [FakeDataProvider] receiver and returns the result as [String].
-     *
-     * @param T instance of [FakeDataProvider]
-     * @param kFunction the [KFunction] of [T]
-     */
-    private fun <T : FakeDataProvider> T.callFunction(kFunction: KFunction<*>): String {
-        return kFunction.call(this) as String
-    }
-
-    /**
-     * Gets the [KFunction] of this [FakeDataProvider] receiver from the [rawString].
-     *
-     * Examples:
-     *
-     * - Yaml expression in the form of `Name.first_name` would return the [Name.firstName] function.
-     * - Yaml expression in the form of `Address.country` would return the [Address.country] function.
-     * - Yaml expression in the form of `Educator.tertiary.degree.course_number` would return the [Educator.tertiary.degree.courseNumber] function.
-     *
-     * @param T instance of [FakeDataProvider]
-     */
-    @Suppress("KDocUnresolvedReference")
-    private fun <T : FakeDataProvider> T.getFunctionName(rawString: String): Pair<FakeDataProvider, KFunction<*>> {
-        val funcName = rawString.split("_").mapIndexed { i: Int, s: String ->
-            if (i == 0) s else s.substring(0, 1).uppercase() + s.substring(1)
-        }.joinToString("")
-
-        return this::class.declaredMemberFunctions.firstOrNull { it.name == funcName }
-            ?.let { this to it }
-            ?: run {
-                this::class.declaredMemberProperties.firstOrNull { it.name == funcName.substringBefore(".") }?.let {
-                    (it.getter.call(this) as YamlFakeDataProvider<*>)
-                        .getFunctionName(funcName.substringAfter("."))
-                }
-            }
-            ?: throw NoSuchElementException("Function $funcName not found in $this")
-    }
-
-    /**
-     * Returns an instance of [FakeDataProvider] fetched by its [simpleClassName] (case-insensitive).
-     *
-     * The function will attempt a [FakeDataProvider] in this [faker]'s declared member properties.
-     * Failing that, the core [Faker] implementation will be used to do the same.
-     *
-     * @throws NoSuchElementException if neither this [faker] nor the core [Faker] implementation
-     * has declared a provider that matches the [simpleClassName] parameter.
-     */
-    private fun getProvider(simpleClassName: String): FakeDataProvider {
-        val kProp = faker::class.declaredMemberProperties.firstOrNull {
-            it.name.lowercase() == simpleClassName.lowercase()
-        }
-
-        return kProp?.let { it.call(faker) as FakeDataProvider } ?: run {
-            val core = Faker(faker.config)
-            val prop = core::class.declaredMemberProperties.firstOrNull { p ->
-                p.name.lowercase() == simpleClassName.lowercase()
-            }
-            prop?.let { p -> p.call(core) as FakeDataProvider }
-                ?: throw NoSuchElementException("Faker provider '$simpleClassName' not found in $core or $faker")
-        }
-    }
-
     private fun findMatchesAndAppendTail(
         string: String,
         stringBuffer: StringBuffer,

And instead of "massaging" code to account for yaml files, we "massage" yaml files by appending the necessary data from other yaml files, e.g. :

diff --git a/core/src/main/resources/locales/en/address.yml b/core/src/main/resources/locales/en/address.yml
index 55e9a687..888549f1 100644
--- a/core/src/main/resources/locales/en/address.yml
+++ b/core/src/main/resources/locales/en/address.yml
@@ -1839,6 +1839,12 @@ en:
       street_name:
         - "#{Name.first_name} #{street_suffix}"
         - "#{Name.last_name} #{street_suffix}"
+      Name.first_name:
+        - first name foo
+        - first name bar
+      Name.last_name:
+        - last name foo
+        - last name bar
       street_address:
         - "#{building_number} #{street_name}"
       full_address:

Maybe I've been looking at the problem all wrong....


...or need to refactor the bits that read data and just read the values directly from the dictionary

serpro69 added a commit that referenced this issue Oct 28, 2024
Partially does what #23 wants. Over-engineering is evil...

There are still some parts in :core that user reflection, e.g. unique data generation bits. This PR doesn't attempt to address those.

At the very least, this should fix most issues with #250
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core 🧬 Issue related to :core module enhancement 🚀 New feature or request help wanted 🆘 Extra attention is needed
Projects
None yet
Development

No branches or pull requests

1 participant