Closed
Description
Description
Right now codebase contains a lot of code frgaments like this:
when (applicationContext) {
is SpringApplicationContext -> doSomethingSpringSpecific()
else -> doSomethingDefault()
}
Action plan
Utilize abstract factory design pattern by making ApplicationContext
an abstract factory responsible for creating different components of UTBot (possible renaming it to UTBotComponentFactory
).
Note: it's not as simple as it may look, since many interfaces need to be extracted and moved to utbot-framework-api
and constructor and method parameters of both SimpleUtBotComponentFactory
and SpringAwareUtBotComponentFactory
need to be carefully chosen.
interface UtBotComponentFactory {
val utExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult>
fun createJavaValueProvider(idGenerator: IdentityPreservingIdGenerator<Int>, methodUnderTest: MethodId) : JavaValueProvider
fun createCodeGenerator(...): CodeGenerator
...
}
class SimpleUtBotComponentFactory : UtBotComponentFactory {
override val utExecutionInstrumentation = UtExecutionInstrumentation
override fun createJavaValueProvider(idGenerator: IdentityPreservingIdGenerator<Int>, methodUnderTest: MethodId) = ValueProvider.of(
defaultValueProviders(idGenerator)
)
override fun createCodeGenerator(...) = SimpleGenerator(...)
...
}
class SpringAwareUtBotComponentFactory(
private val delegateFactory: UtBotComponentFactory,
private val instantiationSettings: InstantiationSettings,
private val beanDefinitions: List<BeanDefinitionData>
) : UtBotComponentFactory {
override val utExecutionInstrumentation = SpringUtExecutionInstrumentation(
delegateFactory.utExecutionInstrumentation,
instantiationSettings,
beanDefinitions
)
override fun createJavaValueProvider(idGenerator: IdentityPreservingIdGenerator<Int>, methodUnderTest: MethodId) =
delegateFactory.createJavaValueProvider(idGenerator).let {
provider ->
val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(methodUnderTest.classId)
logger.info { "Detected relevant repositories for class ${methodUnderTest.classId}: $relevantRepositories" }
val generatedValueAnnotationClasses = SpringModelUtils.generatedValueClassIds.mapNotNull {
@Suppress("UNCHECKED_CAST") // type system fails to understand that GeneratedValue is indeed an annotation
utContext.classLoader.tryLoadClass(it.name) as Class<out Annotation>?
}
val generatedValueFieldIds =
relevantRepositories
.map { it.entityClassId.jClass }
.flatMap { entityClass -> generateSequence(entityClass) { it.superclass } }
.flatMap { it.declaredFields.toList() }
.filter { field -> generatedValueAnnotationClasses.any { field.isAnnotationPresent(it) } }
.map { it.fieldId }
logger.info { "Detected @GeneratedValue fields: $generatedValueFieldIds" }
// spring should try to generate bean values, but if it fails, then object value provider is used for it
val springBeanValueProvider = SpringBeanValueProvider(
idGenerator,
beanNameProvider = { classId ->
beanDefinitions
.filter { it.beanTypeFqn == classId.name }
.map { it.beanName }
},
relevantRepositories = relevantRepositories
).withFallback(ObjectValueProvider(idGenerator))
provider
.except { p -> p is ObjectValueProvider }
.with(springBeanValueProvider)
.with(ValueProvider.of(relevantRepositories.map { SavedEntityValueProvider(defaultIdGenerator, idGenerator) }))
.with(ValueProvider.of(generatedValueFieldIds.map { FieldValueProvider(defaultIdGenerator, idGenerator) }))
}
override fun createCodeGenerator(...): CodeGenerator = SpringCodeGenerator(delegateFactory.createCodeGenerator(...), ...)
...
}
Metadata
Metadata
Assignees
Type
Projects
Status
Done