Skip to content

Commit 300fc74

Browse files
authored
[Modernize] Use *.class files as sources instead of *.java (#47)
This is a smaller change that has a few big benefits: 1. Removes all deprecation warnings 🎉 2. Inputs are now correctly handled, if there is any change in classes we will always regenerate everything -- previously it would only regenerate changed files which is misleading due to the reflected data potentially being incorrect 3. Excluding files via `sourceSet.exclude` works again -- restoring the ignored test - part of a fix for #16 5. This enables us to add support for other JVM languages: Groovy, Kotlin, etc. 🚀 Closes #5 Closes #17
1 parent 566c52a commit 300fc74

File tree

6 files changed

+120
-176
lines changed

6 files changed

+120
-176
lines changed

src/main/kotlin/org/assertj/generator/gradle/AssertJGeneratorGradlePlugin.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ open class AssertJGeneratorGradlePlugin @Inject internal constructor(
6767

6868
// Create a new task for the source set
6969
val generationTask = project.tasks.register<AssertJGenerationTask>(generateTaskName, objects, this)
70+
generationTask.configure { task ->
71+
task.classDirectories.srcDir(this.java.classesDirectory)
72+
}
7073

7174
javaPlugin.sourceSets.named("test").configure { sourceSet ->
7275
sourceSet.java.srcDir(generationTask.flatMap { it.outputDir })

src/main/kotlin/org/assertj/generator/gradle/internal/tasks/AssertionsGeneratorReport.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ internal class AssertionsGeneratorReport(
3333
private val inputClassesNotFound = TreeSet<String>()
3434
private val userTemplates = mutableListOf<String>()
3535

36-
fun addGeneratedAssertionFiles(vararg generatedCustomAssertionFiles: File) {
37-
generatedCustomAssertionFileNames += generatedCustomAssertionFiles.map { it.canonicalPath }
36+
fun addGeneratedAssertionFiles(vararg files: File) {
37+
generatedCustomAssertionFileNames += files.map { it.canonicalPath }
3838
}
3939

4040
fun getReportContent(): String = buildString {

src/main/kotlin/org/assertj/generator/gradle/tasks/AssertJGenerationTask.kt

Lines changed: 63 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
*/
1313
package org.assertj.generator.gradle.tasks
1414

15-
import com.google.common.collect.Sets
1615
import com.google.common.reflect.TypeToken
1716
import org.assertj.assertions.generator.AssertionsEntryPointType
1817
import org.assertj.assertions.generator.BaseAssertionGenerator
@@ -22,29 +21,29 @@ import org.assertj.assertions.generator.util.ClassUtil
2221
import org.assertj.generator.gradle.internal.tasks.AssertionsGeneratorReport
2322
import org.assertj.generator.gradle.tasks.config.AssertJGeneratorExtension
2423
import org.assertj.generator.gradle.tasks.config.SerializedTemplate
24+
import org.gradle.api.DefaultTask
2525
import org.gradle.api.file.ConfigurableFileCollection
2626
import org.gradle.api.file.DirectoryProperty
2727
import 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
3029
import org.gradle.api.model.ObjectFactory
3130
import org.gradle.api.provider.ListProperty
3231
import org.gradle.api.provider.Property
3332
import org.gradle.api.provider.SetProperty
3433
import org.gradle.api.tasks.CacheableTask
3534
import org.gradle.api.tasks.Classpath
35+
import org.gradle.api.tasks.IgnoreEmptyDirectories
3636
import org.gradle.api.tasks.Input
3737
import org.gradle.api.tasks.InputFiles
3838
import org.gradle.api.tasks.Optional
3939
import org.gradle.api.tasks.OutputDirectory
4040
import org.gradle.api.tasks.SkipWhenEmpty
4141
import org.gradle.api.tasks.SourceSet
42-
import org.gradle.api.tasks.SourceTask
4342
import org.gradle.api.tasks.TaskAction
44-
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
4543
import org.gradle.kotlin.dsl.getByType
4644
import org.gradle.kotlin.dsl.property
4745
import org.gradle.kotlin.dsl.setProperty
46+
import org.gradle.work.InputChanges
4847
import java.io.File
4948
import java.net.URLClassLoader
5049
import java.nio.file.Path
@@ -53,7 +52,7 @@ import javax.inject.Inject
5352
import kotlin.io.path.extension
5453
import kotlin.io.path.isDirectory
5554
import 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
6261
open 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

279226
private 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
}

src/main/kotlin/org/assertj/generator/gradle/tasks/config/AssertJGeneratorExtension.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.assertj.assertions.generator.AssertionsEntryPointType
1616
import org.gradle.api.Action
1717
import org.gradle.api.Project
1818
import org.gradle.api.file.DirectoryProperty
19+
import org.gradle.api.file.SourceDirectorySet
1920
import org.gradle.api.model.ObjectFactory
2021
import org.gradle.api.tasks.SourceSet
2122
import org.gradle.kotlin.dsl.newInstance
@@ -30,6 +31,9 @@ open class AssertJGeneratorExtension @Inject internal constructor(
3031
project: Project,
3132
sourceSet: SourceSet
3233
) {
34+
val classDirectories: SourceDirectorySet =
35+
objects.sourceDirectorySet("assertJClasses", "Classes to generate AssertJ assertions from")
36+
3337
/**
3438
* Generate generating Soft Assertions entry point class.
3539
* @return templates value, never {@code null}

0 commit comments

Comments
 (0)