@@ -35,6 +35,15 @@ import kotlin.streams.asSequence
3535val PsiClass .packageName: String get() = this .containingFile.containingDirectory.getPackage()?.qualifiedName ? : " "
3636const val HISTORY_LIMIT = 10
3737
38+ const val SPRINGBOOT_APPLICATION_FQN = " org.springframework.boot.autoconfigure.SpringBootApplication"
39+ const val SPRINGBOOT_CONFIGURATION_FQN = " org.springframework.boot.SpringBootConfiguration"
40+ const val SPRING_CONFIGURATION_ANNOTATION = " org.springframework.context.annotation.Configuration"
41+ const val SPRING_TESTCONFIGURATION_ANNOTATION = " org.springframework.boot.test.context.TestConfiguration"
42+
43+ const val SPRING_BEANS_SCHEMA_URL = " http://www.springframework.org/schema/beans"
44+ const val SPRING_LOAD_DTD_GRAMMAR_PROPERTY = " http://apache.org/xml/features/nonvalidating/load-dtd-grammar"
45+ const val SPRING_LOAD_EXTERNAL_DTD_PROPERTY = " http://apache.org/xml/features/nonvalidating/load-external-dtd"
46+
3847open class BaseTestsModel (
3948 val project : Project ,
4049 val srcModule : Module ,
@@ -84,46 +93,52 @@ open class BaseTestsModel(
8493 )
8594
8695 /* *
87- * Searches configuration classes in Spring application.
96+ * Finds @SpringBootApplication classes in Spring application.
8897 *
89- * Classes are selected and sorted in the following order:
90- * - Classes marked with `@TestConfiguration` annotation
91- * - Classes marked with `@Configuration` annotation
92- * - firstly, from test source roots (in the order provided by [getSortedTestRoots])
93- * - after that, from source roots
98+ * @see [getSortedAnnotatedClasses]
9499 */
95- fun getSortedSpringConfigurationClasses (): Set <String > {
96- val testRootToIndex = getSortedTestRoots().withIndex().associate { (i, root) -> root.dir to i }
100+ fun getSortedSpringBootApplicationClasses (): Set <String > =
101+ getSortedAnnotatedClasses( SPRINGBOOT_CONFIGURATION_FQN ) + getSortedAnnotatedClasses( SPRINGBOOT_APPLICATION_FQN )
97102
98- // Not using `srcModule.testModules(project)` here because it returns
99- // test modules for dependent modules if no test roots are found in the source module itself.
100- // We don't want to search configurations there because they seem useless.
101- val testModules = ModuleManager .getInstance(project)
102- .modules
103- .filter { module -> TestModuleProperties .getInstance(module).productionModule == srcModule }
103+ /* *
104+ * Finds @TestConfiguration and @Configuration classes in Spring application.
105+ *
106+ * @see [getSortedAnnotatedClasses]
107+ */
108+ fun getSortedSpringConfigurationClasses (): Set <String > =
109+ getSortedAnnotatedClasses(SPRING_TESTCONFIGURATION_ANNOTATION ) + getSortedAnnotatedClasses(SPRING_CONFIGURATION_ANNOTATION )
104110
105- val searchScope = testModules.fold(GlobalSearchScope .moduleScope(srcModule)) { accScope, module ->
111+ /* *
112+ * Finds classes annotated with given annotation in [srcModule] and [potentialTestModules].
113+ *
114+ * Sorting order:
115+ * - classes from test source roots (in the order provided by [getSortedTestRoots])
116+ * - classes from production source roots
117+ */
118+ private fun getSortedAnnotatedClasses (annotationFqn : String ): Set <String > {
119+ val searchScope = potentialTestModules.fold(GlobalSearchScope .moduleScope(srcModule)) { accScope, module ->
106120 accScope.union(GlobalSearchScope .moduleScope(module))
107121 }
108122
109- val annotationClasses = listOf (
110- " org.springframework.boot.test.context.TestConfiguration" ,
111- " org.springframework.context.annotation.Configuration"
112- ).mapNotNull {
113- JavaPsiFacade .getInstance(project).findClass(it, GlobalSearchScope .allScope(project))
114- }
123+ val annotationClass = JavaPsiFacade
124+ .getInstance(project)
125+ .findClass(annotationFqn, GlobalSearchScope .allScope(project)) ? : return emptySet()
126+
127+ val testRootToIndex = getSortedTestRoots().withIndex().associate { (i, root) -> root.dir to i }
115128
116- return annotationClasses.flatMap { annotation ->
117- AnnotatedElementsSearch
118- .searchPsiClasses(annotation, searchScope)
119- .findAll()
120- .sortedBy { testRootToIndex[it.containingFile.sourceRoot] ? : Int .MAX_VALUE }
121- }.mapNotNullTo(mutableSetOf ()) { it.qualifiedName }
129+ return AnnotatedElementsSearch
130+ .searchPsiClasses(annotationClass, searchScope)
131+ .findAll()
132+ .sortedBy { testRootToIndex[it.containingFile.sourceRoot] ? : Int .MAX_VALUE }
133+ .mapNotNullTo(mutableSetOf ()) { it.qualifiedName }
122134 }
123135
124136 fun getSpringXMLConfigurationFiles (): Set <String > {
125137 val resourcesPaths =
126- setOf (testModule, srcModule).flatMapTo(mutableSetOf ()) { it.getResourcesPaths() }
138+ buildList {
139+ addAll(potentialTestModules)
140+ add(srcModule)
141+ }.distinct().flatMapTo(mutableSetOf ()) { it.getResourcesPaths() }
127142 val xmlFilePaths = resourcesPaths.flatMapTo(mutableListOf ()) { path ->
128143 Files .walk(path)
129144 .asSequence()
@@ -136,7 +151,7 @@ open class BaseTestsModel(
136151 val doc = builder.parse(path.toFile())
137152
138153 val hasBeanTagName = doc.documentElement.tagName == " beans"
139- val hasAttribute = doc.documentElement.getAttribute(" xmlns" ) == " http://www.springframework.org/schema/beans "
154+ val hasAttribute = doc.documentElement.getAttribute(" xmlns" ) == SPRING_BEANS_SCHEMA_URL
140155 when {
141156 hasBeanTagName && hasAttribute -> path.toString()
142157 else -> null
@@ -162,8 +177,8 @@ open class BaseTestsModel(
162177 builderFactory.isNamespaceAware = true
163178
164179 // See documentation https://xerces.apache.org/xerces2-j/features.html
165- builderFactory.setFeature(" http://apache.org/xml/features/nonvalidating/load-dtd-grammar " , false )
166- builderFactory.setFeature(" http://apache.org/xml/features/nonvalidating/load-external-dtd " , false )
180+ builderFactory.setFeature(SPRING_LOAD_DTD_GRAMMAR_PROPERTY , false )
181+ builderFactory.setFeature(SPRING_LOAD_EXTERNAL_DTD_PROPERTY , false )
167182
168183 return builderFactory.newDocumentBuilder()
169184 }
0 commit comments