Skip to content

Commit 58e947d

Browse files
refactor: extract zip/classes dir specific code from JarExploder
for better encapsulation and less duplication
1 parent 02cfa6b commit 58e947d

File tree

8 files changed

+136
-124
lines changed

8 files changed

+136
-124
lines changed

src/main/kotlin/com/autonomousapps/internal/JarExploder.kt

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
package com.autonomousapps.internal
44

55
import com.autonomousapps.internal.asm.ClassReader
6-
import com.autonomousapps.internal.utils.asSequenceOfClassFiles
76
import com.autonomousapps.internal.utils.getLogger
87
import com.autonomousapps.model.internal.KtFile
98
import com.autonomousapps.model.internal.PhysicalArtifact
10-
import com.autonomousapps.model.internal.PhysicalArtifact.Mode
119
import com.autonomousapps.model.internal.intermediates.producer.AndroidLinterDependency
1210
import com.autonomousapps.model.internal.intermediates.ExplodingJar
1311
import com.autonomousapps.model.internal.intermediates.producer.ExplodedJar
1412
import com.autonomousapps.services.InMemoryCache
1513
import com.autonomousapps.tasks.ExplodeJarTask
16-
import java.util.zip.ZipFile
1714

1815
internal class JarExploder(
1916
private val artifacts: List<PhysicalArtifact>,
@@ -34,15 +31,9 @@ internal class JarExploder(
3431

3532
private fun Sequence<PhysicalArtifact>.toExplodedJars(): Set<ExplodedJar> =
3633
map { artifact ->
37-
val explodedJar = if (artifact.isJar()) {
38-
explode(artifact, Mode.ZIP)
39-
} else {
40-
explode(artifact, Mode.CLASSES)
41-
}
42-
4334
ExplodedJar(
4435
artifact = artifact,
45-
exploding = explodedJar
36+
exploding = explode(artifact)
4637
)
4738
}.toSortedSet()
4839

@@ -54,52 +45,34 @@ internal class JarExploder(
5445
* jars. It seems that the behavior when requesting the "android-classes" artifact view has changed (previously we'd
5546
* get jars, but now we get class files).
5647
*/
57-
private fun explode(artifact: PhysicalArtifact, mode: Mode): ExplodingJar {
48+
private fun explode(artifact: PhysicalArtifact): ExplodingJar {
5849
val entry = findInCache(artifact)
5950
if (entry != null) return entry
6051

61-
val ktFiles: Set<KtFile>
52+
return artifact.withContent { content ->
6253

63-
val visitors = when (mode) {
64-
Mode.ZIP -> {
65-
ZipFile(artifact.file).use { zip ->
66-
ktFiles = KtFile.fromZip(zip)
54+
val ktFiles: Set<KtFile> = content.ktFiles()
6755

68-
zip.asSequenceOfClassFiles()
69-
.map { classEntry ->
70-
ClassNameAndAnnotationsVisitor(logger).apply {
71-
val reader = zip.getInputStream(classEntry).use { ClassReader(it.readBytes()) }
72-
reader.accept(this, 0)
73-
}
74-
}.toList()
56+
val visitors = content.asSequenceOfClassFiles().map { classFile ->
57+
ClassNameAndAnnotationsVisitor(logger).apply {
58+
val reader = ClassReader(classFile.readBytes())
59+
reader.accept(this, 0)
7560
}
7661
}
7762

78-
Mode.CLASSES -> {
79-
ktFiles = KtFile.fromDirectory(artifact.file)
63+
val analyzedClasses = visitors.map { it.getAnalyzedClass() }
64+
.filterNot {
65+
// Filter out `java` packages, but not `javax`
66+
it.className.startsWith("java.")
67+
}
68+
.toSortedSet()
8069

81-
artifact.file.asSequenceOfClassFiles()
82-
.map { classFile ->
83-
ClassNameAndAnnotationsVisitor(logger).apply {
84-
val reader = classFile.inputStream().use { ClassReader(it.readBytes()) }
85-
reader.accept(this, 0)
86-
}
87-
}.toList()
88-
}
70+
ExplodingJar(
71+
analyzedClasses = analyzedClasses,
72+
ktFiles = ktFiles,
73+
androidLintRegistry = findAndroidLinter(artifact)
74+
).also { putInCache(artifact, it) }
8975
}
90-
91-
val analyzedClasses = visitors.map { it.getAnalyzedClass() }
92-
.filterNot {
93-
// Filter out `java` packages, but not `javax`
94-
it.className.startsWith("java.")
95-
}
96-
.toSortedSet()
97-
98-
return ExplodingJar(
99-
analyzedClasses = analyzedClasses,
100-
ktFiles = ktFiles,
101-
androidLintRegistry = findAndroidLinter(artifact)
102-
).also { putInCache(artifact, it) }
10376
}
10477

10578
private fun findInCache(artifact: PhysicalArtifact): ExplodingJar? {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.internal.artifacts.processing
4+
5+
import com.autonomousapps.model.internal.KtFile
6+
7+
internal interface Artifact {
8+
fun hasKotlinClasses(): Boolean
9+
10+
fun ktFiles(): Set<KtFile>
11+
12+
fun asSequenceOfClassFiles(): Sequence<ClassFile>
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.internal.artifacts.processing
4+
5+
import java.io.InputStream
6+
7+
internal class ClassFile(private val inputStreamSupplier: () -> InputStream, val packagePath: String) {
8+
9+
fun readBytes(): ByteArray = inputStreamSupplier().use { it.readBytes() }
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.internal.artifacts.processing
4+
5+
import com.autonomousapps.internal.utils.Files
6+
import com.autonomousapps.internal.utils.asSequenceOfClassFiles
7+
import com.autonomousapps.model.internal.KtFile
8+
import java.io.File
9+
10+
internal class DirectoryArtifact(val dir: File) : Artifact {
11+
12+
init {
13+
check(dir.isDirectory) { "Expected directory. Was '${dir.absolutePath}'" }
14+
}
15+
16+
override fun hasKotlinClasses(): Boolean = ktFiles().isNotEmpty()
17+
18+
override fun ktFiles(): Set<KtFile> = KtFile.fromDirectory(dir)
19+
20+
override fun asSequenceOfClassFiles(): Sequence<ClassFile> =
21+
dir.asSequenceOfClassFiles().map { classFile ->
22+
ClassFile(
23+
inputStreamSupplier = { classFile.inputStream() },
24+
packagePath = Files.asPackagePath(classFile)
25+
)
26+
}
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.internal.artifacts.processing
4+
5+
import com.autonomousapps.internal.utils.asSequenceOfClassFiles
6+
import com.autonomousapps.model.internal.KtFile
7+
import java.io.Closeable
8+
import java.io.File
9+
import java.util.zip.ZipFile
10+
11+
internal class ZipArtifact private constructor(private val zipFile: ZipFile) : Artifact, Closeable by zipFile {
12+
13+
constructor(file: File) : this(ZipFile(file))
14+
15+
override fun hasKotlinClasses(): Boolean = KtFile.hasKotlinClasses(zipFile)
16+
17+
override fun ktFiles(): Set<KtFile> = KtFile.fromZip(zipFile)
18+
19+
override fun asSequenceOfClassFiles(): Sequence<ClassFile> =
20+
zipFile.asSequenceOfClassFiles().map { entry ->
21+
ClassFile(
22+
inputStreamSupplier = { zipFile.getInputStream(entry) },
23+
packagePath = entry.name
24+
)
25+
}
26+
}

src/main/kotlin/com/autonomousapps/model/internal/KtFile.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ internal data class KtFile(
2929
internal companion object {
3030
private const val KOTLIN_MODULE = ".kotlin_module"
3131

32+
fun hasKotlinClasses(zipFile: ZipFile): Boolean = zipFile.entries().toList().any { it.name.endsWith(KOTLIN_MODULE) }
33+
3234
fun fromDirectory(dir: File): Set<KtFile> {
3335
check(dir.isDirectory) { "Expected directory. Was '${dir.absolutePath}'" }
3436

src/main/kotlin/com/autonomousapps/model/internal/PhysicalArtifact.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ package com.autonomousapps.model.internal
55
import com.autonomousapps.PROJECT_LOGGER
66
import com.autonomousapps.internal.utils.toCoordinates
77
import com.autonomousapps.model.Coordinates
8+
import com.autonomousapps.internal.artifacts.processing.Artifact
9+
import com.autonomousapps.internal.artifacts.processing.DirectoryArtifact
10+
import com.autonomousapps.internal.artifacts.processing.ZipArtifact
811
import com.squareup.moshi.JsonClass
912
import org.gradle.api.artifacts.result.ResolvedArtifactResult
1013
import java.io.File
@@ -16,18 +19,18 @@ internal data class PhysicalArtifact(
1619
val file: File,
1720
) : Comparable<PhysicalArtifact> {
1821

19-
enum class Mode {
20-
ZIP,
21-
CLASSES
22-
}
23-
2422
init {
2523
check(isJar() || containsClassFiles()) {
2624
"'file' must either be a jar or a directory that contains class files. Was '$file'"
2725
}
2826
}
2927

30-
val mode: Mode = if (isJar()) Mode.ZIP else Mode.CLASSES
28+
fun <R> withContent(action: (Artifact) -> R): R =
29+
if (isJar()) {
30+
ZipArtifact(file).use { action(it) }
31+
} else {
32+
action(DirectoryArtifact(file))
33+
}
3134

3235
fun isJar(): Boolean = isJar(file)
3336
fun containsClassFiles(): Boolean = containsClassFiles(file)

src/main/kotlin/com/autonomousapps/tasks/FindKotlinMagicTask.kt

Lines changed: 30 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import com.autonomousapps.internal.KotlinMetadataVisitor
88
import com.autonomousapps.internal.asm.ClassReader
99
import com.autonomousapps.internal.utils.*
1010
import com.autonomousapps.model.internal.InlineMemberCapability
11-
import com.autonomousapps.model.internal.KtFile
1211
import com.autonomousapps.model.internal.PhysicalArtifact
13-
import com.autonomousapps.model.internal.PhysicalArtifact.Mode
1412
import com.autonomousapps.model.internal.TypealiasCapability
1513
import com.autonomousapps.model.internal.intermediates.producer.InlineMemberDependency
1614
import com.autonomousapps.model.internal.intermediates.producer.TypealiasDependency
@@ -26,7 +24,6 @@ import org.gradle.workers.WorkAction
2624
import org.gradle.workers.WorkParameters
2725
import org.gradle.workers.WorkerExecutor
2826
import java.io.File
29-
import java.util.zip.ZipFile
3027
import javax.inject.Inject
3128

3229
@CacheableTask
@@ -135,9 +132,9 @@ internal class KotlinMagicFinder(
135132
artifacts.asSequence()
136133
.filter {
137134
it.isJar() || it.containsClassFiles()
138-
}.map { artifact ->
139-
artifact to findKotlinMagic(artifact, artifact.mode)
140-
}.forEach { (artifact, capabilities) ->
135+
}
136+
.associateWith(::findKotlinMagic)
137+
.forEach { (artifact, capabilities) ->
141138
if (capabilities.inlineMembers.isNotEmpty()) {
142139
inlineMembersMut += InlineMemberDependency.newInstance(artifact.coordinates, capabilities.inlineMembers)
143140
}
@@ -162,10 +159,14 @@ internal class KotlinMagicFinder(
162159
* An import statement with either of those would import the `kotlin.jdk7.use()` inline function, contributed by the
163160
* "org.jetbrains.kotlin:kotlin-stdlib-jdk7" module.
164161
*/
165-
private fun findKotlinMagic(artifact: PhysicalArtifact, mode: Mode): KotlinCapabilities {
162+
private fun findKotlinMagic(artifact: PhysicalArtifact): KotlinCapabilities {
166163
val cached = findInCache(artifact)
167164
if (cached != null) return cached
168165

166+
if (!artifact.withContent { it.hasKotlinClasses() }) {
167+
return KotlinCapabilities.EMPTY
168+
}
169+
169170
fun packageName(fileLike: String): String {
170171
return if (fileLike.contains('/')) {
171172
// entry is in a package
@@ -179,75 +180,32 @@ internal class KotlinMagicFinder(
179180
val inlineMembers = mutableSetOf<InlineMemberCapability.InlineMember>()
180181
val typealiases = mutableSetOf<TypealiasCapability.Typealias>()
181182

182-
when (mode) {
183-
Mode.ZIP -> {
184-
ZipFile(artifact.file).use { zipFile ->
185-
val entries = zipFile.entries().toList()
186-
// Only look at jars that have actual Kotlin classes in them
187-
if (entries.none { it.name.endsWith(".kotlin_module") }) {
188-
return KotlinCapabilities.EMPTY
189-
}
183+
artifact.withContent { content ->
184+
content.asSequenceOfClassFiles().mapNotNull { classFile ->
185+
// TODO an entry with `META-INF/proguard/androidx-annotations.pro`
186+
val kotlinMagic = readClass(
187+
ClassReader(classFile.readBytes()),
188+
classFile.packagePath
189+
) ?: return@mapNotNull null
190190

191-
entries.asSequenceOfClassFiles()
192-
.mapNotNull { entry ->
193-
// TODO an entry with `META-INF/proguard/androidx-annotations.pro`
194-
val kotlinMagic = readClass(
195-
zipFile.getInputStream(entry).use { ClassReader(it.readBytes()) },
196-
entry.name
197-
) ?: return@mapNotNull null
198-
199-
entry to kotlinMagic
200-
}
201-
.forEach { (entry, kotlinMagic) ->
202-
if (kotlinMagic.inlineMembers != null) {
203-
inlineMembers += InlineMemberCapability.InlineMember.newInstance(
204-
packageName = packageName(entry.name),
205-
// Guaranteed to be non-empty
206-
inlineMembers = kotlinMagic.inlineMembers
207-
)
208-
}
209-
210-
if (kotlinMagic.typealiases != null) {
211-
typealiases += TypealiasCapability.Typealias.newInstance(
212-
packageName = packageName(entry.name),
213-
typealiases = kotlinMagic.typealiases
214-
)
215-
}
216-
}
217-
}
191+
classFile.packagePath to kotlinMagic
218192
}
219-
220-
Mode.CLASSES -> {
221-
if (KtFile.fromDirectory(artifact.file).isEmpty()) {
222-
return KotlinCapabilities.EMPTY
223-
}
224-
225-
artifact.file.asSequenceOfClassFiles()
226-
.mapNotNull { classFile ->
227-
val kotlinMagic = readClass(
228-
classFile.inputStream().use { ClassReader(it.readBytes()) },
229-
Files.asPackagePath(classFile)
230-
) ?: return@mapNotNull null
231-
232-
classFile to kotlinMagic
193+
.forEach { (packagePath, kotlinMagic) ->
194+
if (kotlinMagic.inlineMembers != null) {
195+
inlineMembers += InlineMemberCapability.InlineMember.newInstance(
196+
packageName = packageName(packagePath),
197+
// Guaranteed to be non-empty
198+
inlineMembers = kotlinMagic.inlineMembers
199+
)
233200
}
234-
.forEach { (classFile, kotlinMagic) ->
235-
if (kotlinMagic.inlineMembers != null) {
236-
inlineMembers += InlineMemberCapability.InlineMember.newInstance(
237-
packageName = packageName(Files.asPackagePath(classFile)),
238-
// Guaranteed to be non-empty
239-
inlineMembers = kotlinMagic.inlineMembers
240-
)
241-
}
242-
243-
if (kotlinMagic.typealiases != null) {
244-
typealiases += TypealiasCapability.Typealias.newInstance(
245-
packageName = packageName(Files.asPackagePath(classFile)),
246-
typealiases = kotlinMagic.typealiases
247-
)
248-
}
201+
202+
if (kotlinMagic.typealiases != null) {
203+
typealiases += TypealiasCapability.Typealias.newInstance(
204+
packageName = packageName(packagePath),
205+
typealiases = kotlinMagic.typealiases
206+
)
249207
}
250-
}
208+
}
251209
}
252210

253211
val kotlinCapabilities = KotlinCapabilities(inlineMembers, typealiases)

0 commit comments

Comments
 (0)