-
-
Notifications
You must be signed in to change notification settings - Fork 45
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
Comments
Currently, reflection is mostly used to call other providers' functions for expressions like : kotlin-faker/core/src/main/resources/locales/en/address.yml Lines 1839 to 1841 in caf10e6
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 |
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.
The text was updated successfully, but these errors were encountered: