com.autonomousapps.internal.parse.explodeAndroidResSource.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dependency-analysis-gradle-plugin Show documentation
Show all versions of dependency-analysis-gradle-plugin Show documentation
Analyzes dependency usage in Android and JVM projects
package com.autonomousapps.internal.parse
import com.autonomousapps.internal.ManifestParser
import com.autonomousapps.internal.ManifestParser.ManifestParseException
import com.autonomousapps.internal.ManifestParser.ParseResult
import com.autonomousapps.internal.utils.*
import com.autonomousapps.model.AndroidResSource
import org.w3c.dom.Document
import java.io.File
internal class AndroidLayoutParser(
private val projectDir: File,
private val layouts: Set
) {
val explodedLayouts: Set = parseLayouts()
private fun parseLayouts(): Set {
return layouts.asSequence()
.map { layoutFile ->
layoutFile to buildDocument(layoutFile).getElementsByTagName("*")
.map { it.nodeName }
.filterToOrderedSet { JAVA_FQCN_REGEX_DOTTY.matches(it) }
}.map { (file, classes) ->
ExplodedLayout(
relativePath = file.toRelativeString(projectDir),
usedClasses = classes
)
}
.toSet()
}
}
internal class AndroidResParser(
projectDir: File,
resources: Iterable
) {
val androidResSource: Set = resources
.map { it to buildDocument(it) }
.mapToSet { (file, doc) ->
ExplodedRes(
relativePath = file.toRelativeString(projectDir),
styleParentRefs = extractStyleParentsFromResourceXml(doc),
attrRefs = extractAttrsFromResourceXml(doc) + extractContentReferencesFromResourceXml(doc)
)
}
// e.g., "Theme.AppCompat.Light.DarkActionBar"
private fun extractStyleParentsFromResourceXml(doc: Document) =
doc.getElementsByTagName("style").mapNotNull {
it.attributes.getNamedItem("parent")?.nodeValue
}.mapToSet {
// Transform Theme.AppCompat.Light.DarkActionBar to Theme_AppCompat_Light_DarkActionBar
it.replace('.', '_')
}.mapToSet {
AndroidResSource.StyleParentRef(it)
}
private fun extractAttrsFromResourceXml(doc: Document): Set {
return doc.attrs().mapNotNullToSet { AndroidResSource.AttrRef.from(it) }
}
private fun extractContentReferencesFromResourceXml(doc: Document): Set {
return doc.contentReferences().entries.mapNotNullToSet { AndroidResSource.AttrRef.from(it.key to it.value) }
}
}
internal class AndroidManifestParser(
private val manifests: Iterable,
private val projectDir: File,
private val namespace: String
) {
private val parser = ManifestParser(namespace)
val explodedManifests: List = compute()
private fun compute(): List {
// If we have a namespace defined by the Android DSL, it is safe to parse the manifests immediately.
if (namespace.isNotBlank()) return parseManifests()
// Otherwise, parsing may result in an exception. We attempt to parse each file. If there's a failure, we catch it
// and put it in a queue for a second attempt. One of the manifests should parse correctly. We'll get the namespace
// from that one (probably src/main/AndroidManifest.xml).
val parseResults = mutableListOf>()
val malformedManifests = mutableListOf()
for (manifest in manifests) {
if (!manifest.exists()) continue
try {
parseResults += manifest to parser.parse(manifest)
} catch (_: ManifestParseException) {
malformedManifests += manifest
}
}
val mainNamespace = parseResults.firstOrNull()?.second?.packageName.orEmpty()
val malformedParser = ManifestParser(mainNamespace)
for (remainder in malformedManifests) {
parseResults += remainder to malformedParser.parse(remainder)
}
return parseResults.toExplodedManifests()
}
private fun parseManifests(): List = manifests
.filter { it.exists() }
.map { it to parser.parse(it) }
.toExplodedManifests()
private fun Iterable>.toExplodedManifests(): List {
return map { it.toExplodedManifest() }
}
private fun Pair.toExplodedManifest(): ExplodedManifest {
val file = first
val parseResult = second
val applicationName = parseResult.applicationName
val theme = AndroidResSource.AttrRef.style(parseResult.theme)
return ExplodedManifest(
relativePath = file.toRelativeString(projectDir),
applicationName = applicationName,
theme = theme,
)
}
}
internal class AndroidResBuilder(private val relativePath: String) {
// TODO sort these
val styleParentRefs = mutableSetOf()
val attrRefs = mutableSetOf()
val usedClasses = mutableSetOf()
fun concat(other: AndroidResBuilder): AndroidResBuilder {
styleParentRefs.addAll(other.styleParentRefs)
attrRefs.addAll(other.attrRefs)
usedClasses.addAll(other.usedClasses)
return this
}
fun build() = AndroidResSource(
relativePath = relativePath,
styleParentRefs = styleParentRefs,
attrRefs = attrRefs,
usedClasses = usedClasses.toSortedSet()
)
}
internal class ExplodedLayout(
val relativePath: String,
val usedClasses: Set
)
internal class ExplodedRes(
val relativePath: String,
val styleParentRefs: Set,
val attrRefs: Set
)
internal class ExplodedManifest(
val relativePath: String,
val applicationName: String,
val theme: AndroidResSource.AttrRef?
)