Skip to content

Avoid whens by applicationContext by introducing UTBotComponentFactory #2358

Closed
@IlyaMuravjov

Description

@IlyaMuravjov

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

Labels

comp-springIssue is related to Spring projects supportctg-refactoringIssue related to refactoring process

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions