11package org.utbot.framework.plugin.api.util
22
33import org.utbot.common.tryLoadClass
4+ import org.utbot.common.withToStringThreadLocalReentrancyGuard
5+ import org.utbot.framework.plugin.api.isNotNull
46import org.utbot.framework.plugin.api.ClassId
57import org.utbot.framework.plugin.api.MethodId
68import org.utbot.framework.plugin.api.UtArrayModel
@@ -10,6 +12,7 @@ import org.utbot.framework.plugin.api.UtModel
1012import org.utbot.framework.plugin.api.UtNullModel
1113import org.utbot.framework.plugin.api.UtPrimitiveModel
1214import org.utbot.framework.plugin.api.UtSpringContextModel
15+ import java.util.Optional
1316
1417object SpringModelUtils {
1518 val autowiredClassId = ClassId (" org.springframework.beans.factory.annotation.Autowired" )
@@ -121,6 +124,7 @@ object SpringModelUtils {
121124 // /region spring-web
122125 private val requestMappingClassId = ClassId (" org.springframework.web.bind.annotation.RequestMapping" )
123126 private val pathVariableClassId = ClassId (" org.springframework.web.bind.annotation.PathVariable" )
127+ private val requestHeaderClassId = ClassId (" org.springframework.web.bind.annotation.RequestHeader" )
124128 private val requestBodyClassId = ClassId (" org.springframework.web.bind.annotation.RequestBody" )
125129 private val requestParamClassId = ClassId (" org.springframework.web.bind.annotation.RequestParam" )
126130 private val uriComponentsBuilderClassId = ClassId (" org.springframework.web.util.UriComponentsBuilder" )
@@ -141,6 +145,7 @@ object SpringModelUtils {
141145 private val viewResultMatchersClassId = ClassId (" org.springframework.test.web.servlet.result.ViewResultMatchers" )
142146 private val mockHttpServletRequestBuilderClassId = ClassId (" org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder" )
143147 private val modelAndViewClassId = ClassId (" org.springframework.web.servlet.ModelAndView" )
148+ private val httpHeaderClassId = ClassId (" org.springframework.http.HttpHeaders" )
144149
145150 private val objectMapperClassId = ClassId (" com.fasterxml.jackson.databind.ObjectMapper" )
146151
@@ -314,10 +319,70 @@ object SpringModelUtils {
314319 )
315320 )
316321
317- // TODO #2462 (support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute, ...)
322+ val headersContentModel = createHeadersContentModel(methodId, arguments, idGenerator)
323+
324+ requestBuilderModel = addHeadersToRequestBuilderModel(headersContentModel, requestBuilderModel, idGenerator)
325+
326+ // TODO #2462 (support @CookieValue, @RequestAttribute, ...)
318327 return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator)
319328 }
320329
330+ private fun addHeadersToRequestBuilderModel (
331+ headersContentModel : UtAssembleModel ,
332+ requestBuilderModel : UtAssembleModel ,
333+ idGenerator : () -> Int
334+ ): UtAssembleModel {
335+ @Suppress(" NAME_SHADOWING" )
336+ var requestBuilderModel = requestBuilderModel
337+
338+ if (headersContentModel.modificationsChain.isEmpty()) {
339+ return requestBuilderModel
340+ }
341+
342+ val headers = UtAssembleModel (
343+ id = idGenerator(),
344+ classId = httpHeaderClassId,
345+ modelName = " headers" ,
346+ instantiationCall = UtExecutableCallModel (
347+ instance = null ,
348+ executable = constructorId(httpHeaderClassId),
349+ params = emptyList(),
350+ ),
351+ modificationsChainProvider = {
352+ listOf (
353+ UtExecutableCallModel (
354+ instance = this ,
355+ executable = methodId(
356+ classId = httpHeaderClassId,
357+ name = " setAll" ,
358+ returnType = voidClassId,
359+ arguments = arrayOf(Map ::class .java.id),
360+ ),
361+ params = listOf (headersContentModel)
362+ )
363+ )
364+ }
365+ )
366+
367+ requestBuilderModel = UtAssembleModel (
368+ id = idGenerator(),
369+ classId = mockHttpServletRequestBuilderClassId,
370+ modelName = " requestBuilder" ,
371+ instantiationCall = UtExecutableCallModel (
372+ instance = requestBuilderModel,
373+ executable = MethodId (
374+ classId = mockHttpServletRequestBuilderClassId,
375+ name = " headers" ,
376+ returnType = mockHttpServletRequestBuilderClassId,
377+ parameters = listOf (httpHeaderClassId)
378+ ),
379+ params = listOf (headers)
380+ )
381+ )
382+
383+ return requestBuilderModel
384+ }
385+
321386 private fun addContentToRequestBuilderModel (
322387 methodId : MethodId ,
323388 arguments : List <UtModel >,
@@ -330,7 +395,6 @@ object SpringModelUtils {
330395 @Suppress(" UNCHECKED_CAST" )
331396 param.getAnnotation(requestBodyClassId.jClass as Class <out Annotation >) ? : return @forEach
332397
333- // TODO filter out `null` and `Optional.empty()` values of `arg`
334398 val mediaTypeModel = UtAssembleModel (
335399 id = idGenerator(),
336400 classId = mediaTypeClassId,
@@ -403,14 +467,48 @@ object SpringModelUtils {
403467 return requestBuilderModel
404468 }
405469
470+ private fun createHeadersContentModel (
471+ methodId : MethodId ,
472+ arguments : List <UtModel >,
473+ idGenerator : () -> Int ,
474+ ): UtAssembleModel {
475+ // Converts Map models values to String because `HttpHeaders.setAll(...)` method takes `Map<String, String>`
476+ val headersContent = collectArgumentsWithAnnotationModels(methodId, requestHeaderClassId, arguments)
477+ .mapValues { (_, model) -> convertModelValueToString(model) }
478+
479+ return UtAssembleModel (
480+ id = idGenerator(),
481+ classId = Map ::class .java.id,
482+ modelName = " headersContent" ,
483+ instantiationCall = UtExecutableCallModel (
484+ instance = null ,
485+ executable = HashMap ::class .java.getConstructor().executableId,
486+ params = emptyList()
487+ ),
488+ modificationsChainProvider = {
489+ headersContent.map { (name, value) ->
490+ UtExecutableCallModel (
491+ instance = this ,
492+ // Actually it is a `Map<String, String>`, but we use `Object::class.java` to avoid concrete failures
493+ executable = Map ::class .java.getMethod(
494+ " put" ,
495+ Object ::class .java,
496+ Object ::class .java
497+ ).executableId,
498+ params = listOf (UtPrimitiveModel (name), value)
499+ )
500+ }
501+ }
502+ )
503+ }
504+
406505 private fun createPathVariablesModel (
407506 methodId : MethodId ,
408507 arguments : List <UtModel >,
409508 idGenerator : () -> Int
410509 ): UtAssembleModel {
411510 val pathVariables = collectArgumentsWithAnnotationModels(methodId, pathVariableClassId, arguments)
412511
413- // TODO filter out `null` and `Optional.empty()` values of `arg`
414512 return UtAssembleModel (
415513 id = idGenerator(),
416514 classId = Map ::class .java.id,
@@ -443,7 +541,6 @@ object SpringModelUtils {
443541 ): List < Pair <UtPrimitiveModel , UtAssembleModel > > {
444542 val requestParams = collectArgumentsWithAnnotationModels(methodId, requestParamClassId, arguments)
445543
446- // TODO filter out `null` and `Optional.empty()` values of `arg`
447544 return requestParams.map { (name, value) ->
448545 Pair (UtPrimitiveModel (name),
449546 UtAssembleModel (
@@ -479,20 +576,46 @@ object SpringModelUtils {
479576 annotationClassId : ClassId ,
480577 arguments : List <UtModel >
481578 ): MutableMap <String , UtModel > {
482- val argumentsModels = mutableMapOf<String , UtModel >()
579+ fun UtModel.isEmptyOptional (): Boolean {
580+ return classId == Optional ::class .java.id && this is UtAssembleModel &&
581+ instantiationCall is UtExecutableCallModel && instantiationCall.executable.name == " empty"
582+ }
483583
584+ val argumentsModels = mutableMapOf<String , UtModel >()
484585 methodId.method.parameters.zip(arguments).forEach { (param, arg) ->
485586 @Suppress(" UNCHECKED_CAST" ) val paramAnnotation =
486587 param.getAnnotation(annotationClassId.jClass as Class <out Annotation >) ? : return @forEach
487588 val name = (annotationClassId.jClass.getMethod(" name" ).invoke(paramAnnotation) as ? String ).orEmpty()
488589 .ifEmpty { annotationClassId.jClass.getMethod(" value" ).invoke(paramAnnotation) as ? String }.orEmpty()
489590 .ifEmpty { param.name }
490- argumentsModels[name] = arg
591+
592+ if (arg.isNotNull() && ! arg.isEmptyOptional()) {
593+ argumentsModels[name] = arg
594+ }
491595 }
492596
493597 return argumentsModels
494598 }
495599
600+ /* *
601+ * Converts the model into a form that is understandable for annotations.
602+ * Example: UtArrayModel([UtPrimitiveModel("a"), UtPrimitiveModel("b"), UtPrimitiveModel("c")]) -> UtPrimitiveModel("a, b, c")
603+ *
604+ * There is known issue when using `model.toString()` is not reliable:
605+ * https://github.com/UnitTestBot/UTBotJava/issues/2505
606+ * This issue may be improved in the future.
607+ */
608+ private fun convertModelValueToString (model : UtModel ): UtModel {
609+ return UtPrimitiveModel (
610+ when (model){
611+ is UtArrayModel -> withToStringThreadLocalReentrancyGuard {
612+ (0 until model.length).map { model.stores[it] ? : model.constModel }.joinToString(" , " )
613+ }
614+ else -> model.toString()
615+ }
616+ )
617+ }
618+
496619 private fun createUrlTemplateModel (
497620 pathVariablesModel : UtAssembleModel ,
498621 requestParamModel : List <Pair <UtPrimitiveModel , UtAssembleModel >>,
0 commit comments