1212 */
1313package org.assertj.generator.gradle.tasks
1414
15- import com.google.common.collect.Sets
1615import com.google.common.reflect.TypeToken
1716import org.assertj.assertions.generator.AssertionsEntryPointType
1817import org.assertj.assertions.generator.BaseAssertionGenerator
@@ -22,29 +21,29 @@ import org.assertj.assertions.generator.util.ClassUtil
2221import org.assertj.generator.gradle.internal.tasks.AssertionsGeneratorReport
2322import org.assertj.generator.gradle.tasks.config.AssertJGeneratorExtension
2423import org.assertj.generator.gradle.tasks.config.SerializedTemplate
24+ import org.gradle.api.DefaultTask
2525import org.gradle.api.file.ConfigurableFileCollection
2626import org.gradle.api.file.DirectoryProperty
2727import org.gradle.api.file.FileCollection
28- import org.gradle.api.file.FileTree
29- import org.gradle.api.file.FileVisitDetails
28+ import org.gradle.api.file.SourceDirectorySet
3029import org.gradle.api.model.ObjectFactory
3130import org.gradle.api.provider.ListProperty
3231import org.gradle.api.provider.Property
3332import org.gradle.api.provider.SetProperty
3433import org.gradle.api.tasks.CacheableTask
3534import org.gradle.api.tasks.Classpath
35+ import org.gradle.api.tasks.IgnoreEmptyDirectories
3636import org.gradle.api.tasks.Input
3737import org.gradle.api.tasks.InputFiles
3838import org.gradle.api.tasks.Optional
3939import org.gradle.api.tasks.OutputDirectory
4040import org.gradle.api.tasks.SkipWhenEmpty
4141import org.gradle.api.tasks.SourceSet
42- import org.gradle.api.tasks.SourceTask
4342import org.gradle.api.tasks.TaskAction
44- import org.gradle.api.tasks.incremental.IncrementalTaskInputs
4543import org.gradle.kotlin.dsl.getByType
4644import org.gradle.kotlin.dsl.property
4745import org.gradle.kotlin.dsl.setProperty
46+ import org.gradle.work.InputChanges
4847import java.io.File
4948import java.net.URLClassLoader
5049import java.nio.file.Path
@@ -53,7 +52,7 @@ import javax.inject.Inject
5352import kotlin.io.path.extension
5453import kotlin.io.path.isDirectory
5554import kotlin.io.path.nameWithoutExtension
56- import kotlin.io.path.useLines
55+ import kotlin.io.path.relativeTo
5756
5857/* *
5958 * Executes AssertJ generation against provided sources using the configured templates.
@@ -62,13 +61,19 @@ import kotlin.io.path.useLines
6261open class AssertJGenerationTask @Inject internal constructor(
6362 objects : ObjectFactory ,
6463 sourceSet : SourceSet ,
65- ) : SourceTask () {
64+ ) : DefaultTask () {
6665
6766 @get:InputFiles
6867 @get:Classpath
6968 val generationClasspath: ConfigurableFileCollection = objects.fileCollection()
7069 .from(sourceSet.runtimeClasspath)
7170
71+ @get:Classpath
72+ @get:InputFiles
73+ @get:SkipWhenEmpty
74+ @get:IgnoreEmptyDirectories
75+ val classDirectories: SourceDirectorySet
76+
7277 @OutputDirectory
7378 val outputDir: DirectoryProperty
7479
@@ -95,14 +100,14 @@ open class AssertJGenerationTask @Inject internal constructor(
95100 init {
96101 description = " Generates AssertJ assertions for the ${sourceSet.name} sources."
97102
98- source(sourceSet.allJava)
99103 dependsOn(sourceSet.compileJavaTaskName)
100104
101105 val assertJOptions = sourceSet.extensions.getByType<AssertJGeneratorExtension >()
102106
103107 outputDir = assertJOptions.outputDir
104108 templateFiles = assertJOptions.templates.templateFiles
105109 generatorTemplates = assertJOptions.templates.generatorTemplates
110+ classDirectories = assertJOptions.classDirectories
106111
107112 skip.set(project.provider { assertJOptions.skip })
108113 hierarchical.set(project.provider { assertJOptions.hierarchical })
@@ -111,66 +116,36 @@ open class AssertJGenerationTask @Inject internal constructor(
111116 }
112117
113118 @TaskAction
114- fun execute (inputs : IncrementalTaskInputs ) {
115- if (skip.get( )) {
119+ fun execute (inputs : InputChanges ) {
120+ if (skip.getOrElse( false )) {
116121 return
117122 }
118123
119- val sourceFiles = source.files
120-
121- var classesToGenerate = mutableSetOf<File >()
122- var fullRegenRequired = false
123- inputs.outOfDate { change ->
124- if (generationClasspath.contains(change.file)) {
125- // file is part of classpath
126- fullRegenRequired = true
127- } else if (sourceFiles.contains(change.file)) {
128- // source file changed
129- classesToGenerate + = change.file
130- } else if (templateFiles.contains(change.file)) {
131- fullRegenRequired = true
132- }
133- }
124+ // We always regen every time
125+ project.delete(outputDir)
134126
135- inputs.removed { change ->
136- // TODO Handle deleted file
137- // def targetFile = project.file("$outputDir/${change.file.name}")
138- // if (targetFile.exists()) {
139- // targetFile.delete()
140- // }
141- }
127+ val classLoader = URLClassLoader ((generationClasspath + classDirectories).map { it.toURI().toURL() }.toTypedArray())
142128
143- if (fullRegenRequired || ! inputs.isIncremental) {
144- project.delete(outputDir.asFileTree.files)
145- classesToGenerate = sourceFiles
146- }
147-
148- val classLoader = URLClassLoader (generationClasspath.map { it.toURI().toURL() }.toTypedArray())
149-
150- val inputClassNames = getClassNames()
129+ val allClassNames = getClassNames(classDirectories)
151130
152131 @Suppress(" SpreadOperator" ) // Java interop
153- val classes = ClassUtil .collectClasses(
132+ val allClasses = ClassUtil .collectClasses(
154133 classLoader,
155- * inputClassNames.values.flatten() .toTypedArray(),
134+ * allClassNames .toTypedArray(),
156135 )
157136
158- val classesByTypeName = classes.associateBy { it.type.typeName }
159-
160- val inputClassesToFile = inputClassNames.asSequence()
161- .flatMap { (file, classDefs) ->
162- classDefs.map { classesByTypeName.getValue(it) to file }
163- }
164- .filter { (_, file) -> file in classesToGenerate }
165- .toMap()
137+ val changedFiles = if (inputs.isIncremental) {
138+ inputs.getFileChanges(classDirectories).asSequence().map { it.file }.filter { it.isFile }.toSet()
139+ } else {
140+ classDirectories.files
141+ }
166142
167- runGeneration(classes, inputClassNames, inputClassesToFile )
143+ runGeneration(allClasses, changedFiles )
168144 }
169145
170146 private fun runGeneration (
171147 allClasses : Set <TypeToken <* >>,
172- inputClassNames : Map <File , Set <String >>,
173- inputClassesToFile : Map <TypeToken <* >, File >
148+ changedFiles : Set <File >,
174149 ) {
175150 val generator = BaseAssertionGenerator ()
176151 val converter = ClassToClassDescriptionConverter ()
@@ -180,7 +155,7 @@ open class AssertJGenerationTask @Inject internal constructor(
180155 val filteredClasses = removeAssertClasses(allClasses)
181156 val report = AssertionsGeneratorReport (
182157 directoryPathWhereAssertionFilesAreGenerated = absOutputDir,
183- inputClasses = inputClassNames.values.flatten() ,
158+ inputClasses = changedFiles.map { it.absolutePath } ,
184159 excludedClassesFromAssertionGeneration = allClasses - filteredClasses,
185160 )
186161
@@ -195,19 +170,17 @@ open class AssertJGenerationTask @Inject internal constructor(
195170 val classDescriptions = if (hierarchical.get()) {
196171 generateHierarchical(converter, generator, report, filteredClasses)
197172 } else {
198- generateFlat(generator, converter, report, filteredClasses, inputClassesToFile)
199- }.toSet()
200-
201- if (inputClassesToFile.isNotEmpty()) {
202- // only generate the entry points if there are classes that have changed (or exist..)
203- for (assertionsEntryPointType in entryPoints.get()) {
204- val assertionsEntryPointFile = generator.generateAssertionsEntryPointClassFor(
205- classDescriptions,
206- assertionsEntryPointType,
207- entryPointsClassPackage.orNull,
208- )
209- report.reportEntryPointGeneration(assertionsEntryPointType, assertionsEntryPointFile)
210- }
173+ generateFlat(generator, converter, report, filteredClasses)
174+ }
175+
176+ // only generate the entry points if there are classes that have changed (or exist..)
177+ for (assertionsEntryPointType in entryPoints.get()) {
178+ val assertionsEntryPointFile = generator.generateAssertionsEntryPointClassFor(
179+ classDescriptions.toSet(),
180+ assertionsEntryPointType,
181+ entryPointsClassPackage.orNull,
182+ )
183+ report.reportEntryPointGeneration(assertionsEntryPointType, assertionsEntryPointFile)
211184 }
212185 } catch (@Suppress(" TooGenericExceptionCaught" ) e: Exception ) {
213186 report.exception = e
@@ -221,14 +194,14 @@ open class AssertJGenerationTask @Inject internal constructor(
221194 generator : BaseAssertionGenerator ,
222195 report : AssertionsGeneratorReport ,
223196 classes : Set <TypeToken <* >>,
224- ): Collection <ClassDescription > {
197+ ): List <ClassDescription > {
225198 return classes.map { clazz ->
226199 val classDescription = converter.convertToClassDescription(clazz)
227200 val generatedCustomAssertionFiles = generator.generateHierarchicalCustomAssertionFor(
228201 classDescription,
229202 classes,
230203 )
231- report.addGeneratedAssertionFiles(generatedCustomAssertionFiles = generatedCustomAssertionFiles)
204+ report.addGeneratedAssertionFiles(files = generatedCustomAssertionFiles)
232205 classDescription
233206 }
234207 }
@@ -238,42 +211,16 @@ open class AssertJGenerationTask @Inject internal constructor(
238211 converter : ClassToClassDescriptionConverter ,
239212 report : AssertionsGeneratorReport ,
240213 classes : Set <TypeToken <* >>,
241- inputClassesToFile : Map <TypeToken <* >, File >,
242- ): Collection <ClassDescription > {
214+ ): List <ClassDescription > {
243215 return classes.map { clazz ->
244216 val classDescription = converter.convertToClassDescription(clazz)
245217
246- if (clazz in inputClassesToFile) {
247- val generatedCustomAssertionFile = generator.generateCustomAssertionFor(classDescription)
248- report.addGeneratedAssertionFiles(generatedCustomAssertionFile)
249- }
218+ val generatedCustomAssertionFile = generator.generateCustomAssertionFor(classDescription)
219+ report.addGeneratedAssertionFiles(generatedCustomAssertionFile)
250220
251221 classDescription
252222 }
253223 }
254-
255- /* *
256- * Returns the source for this task, after the include and exclude patterns have been applied. Ignores source files
257- * which do not exist.
258- *
259- * @return The source.
260- */
261- // This method is here as the Gradle DSL generation can't handle properties with setters and getters in different
262- // classes.
263- @InputFiles
264- @SkipWhenEmpty
265- override fun getSource (): FileTree = super .getSource()
266-
267- private fun getClassNames (): Map <File , Set <String >> {
268- val fullyQualifiedNames = mutableMapOf<File , Set <String >>()
269-
270- source.visit { fileVisitDetails: FileVisitDetails ->
271- val file = fileVisitDetails.file.toPath()
272- fullyQualifiedNames[fileVisitDetails.file] = getClassesInFile(file)
273- }
274-
275- return fullyQualifiedNames.filterValues { it.isNotEmpty() }
276- }
277224}
278225
279226private fun removeAssertClasses (classList : Set <TypeToken <* >>): Set <TypeToken <* >> {
@@ -287,30 +234,28 @@ private fun removeAssertClasses(classList: Set<TypeToken<*>>): Set<TypeToken<*>>
287234 return filteredClassList
288235}
289236
290- private val PACKAGE_JAVA_PATH = Paths .get(" package-info.java" )
291-
292- private fun getClassesInFile (path : Path ): Set <String > {
293- if (path.isDirectory()) return setOf ()
237+ private fun getClassNames (source : SourceDirectorySet ): Set <String > {
238+ val srcDirs = source.sourceDirectories.map { it.toPath() }
294239
295- // Ignore package-info.java, it's not supposed to be included.
296- if (path.fileName == PACKAGE_JAVA_PATH ) return setOf ()
240+ val classFiles = source.asFileTree.filter { it.isFile && it.extension == " class" }
241+ return classFiles.asSequence()
242+ .map { it.toPath() }
243+ .mapNotNull { getFullyQualifiedClassForFile(srcDirs, it) }
244+ .toSet()
245+ }
297246
298- val extension = path.extension
299- val fileNameWithoutExtension = path.fileName.nameWithoutExtension
247+ private val PACKAGE_INFO_PATH = Paths .get(" package-info.class" )
300248
301- val (packageName, classNames) = when (extension) {
302- " java" -> {
303- path.useLines { lines ->
304- val packageLine = lines.first { it.startsWith(" package " ) }
249+ private fun getFullyQualifiedClassForFile (srcDirs : List <Path >, path : Path ): String? {
250+ if (path.isDirectory() || path.extension != " class" ) return null
305251
306- val packageName = packageLine.removePrefix(" package " ).trimEnd(' ;' )
307- val classNames = Sets .newHashSet(fileNameWithoutExtension)
308- Pair (packageName, classNames)
309- }
310- }
252+ // Ignore package-info.java, it's not supposed to be included.
253+ if (path.fileName == PACKAGE_INFO_PATH ) return null
254+ val className = path.nameWithoutExtension
311255
312- else -> error(" Unsupported extension: $extension " )
313- }
256+ val srcDir = srcDirs.single { path.startsWith(it) }
257+ val relativePath = path.relativeTo(srcDir).parent.toString()
258+ .replace(File .separatorChar, ' .' )
314259
315- return classNames.asSequence().map { " $packageName . $it " }.toSet()
260+ return " $relativePath . $className "
316261}
0 commit comments