All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.autonomousapps.internal.parse.explodeAndroidResSource.kt Maven / Gradle / Ivy

There is a newer version: 2.6.1
Show newest version
// Copyright (c) 2024. Tony Robalik.
// SPDX-License-Identifier: Apache-2.0
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.JAVA_FQCN_REGEX_DOTTY
import com.autonomousapps.internal.utils.buildDocument
import com.autonomousapps.internal.utils.document.attrs
import com.autonomousapps.internal.utils.document.contentReferences
import com.autonomousapps.internal.utils.document.map
import com.autonomousapps.internal.utils.document.mapNotNull
import com.autonomousapps.internal.utils.filterToOrderedSet
import com.autonomousapps.internal.utils.mapToSet
import com.autonomousapps.model.AndroidResSource
import org.w3c.dom.Document
import org.xml.sax.SAXParseException
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,
) {

  internal class Container {
    val attrRefs = mutableSetOf()
    val newIds = mutableSetOf()

    fun nonLocalAttrRefs(): Set = attrRefs - newIds
  }

  private val container = Container()

  val androidResSource: Set = resources
    .mapNotNull {
      try {
        it to buildDocument(it)
      } catch (_: SAXParseException) {
        // https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/1211
        null
      }
    }
    .mapToSet { (file, doc) ->
      // Populate the container
      extractAttrsFromResourceXml(doc)
      extractContentReferencesFromResourceXml(doc)

      ExplodedRes(
        relativePath = file.toRelativeString(projectDir),
        styleParentRefs = extractStyleParentsFromResourceXml(doc),
        attrRefs = container.nonLocalAttrRefs()
      )
    }

  // e.g., "Theme.AppCompat.Light.DarkActionBar"
  private fun extractStyleParentsFromResourceXml(doc: Document): Set =
    doc.getElementsByTagName("style").mapNotNull {
      it.attributes.getNamedItem("parent")?.nodeValue
    }.mapToSet {
      AndroidResSource.StyleParentRef.of(it)
    }

  private fun extractAttrsFromResourceXml(doc: Document) {
    doc.attrs().forEach {
      AndroidResSource.AttrRef.from(it, container)
    }
  }

  private fun extractContentReferencesFromResourceXml(doc: Document) {
    doc.contentReferences().entries.forEach {
      AndroidResSource.AttrRef.from(it.key to it.value, container)
    }
  }
}

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) {

  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.toSortedSet(),
    attrRefs = attrRefs.toSortedSet(),
    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?,
)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy