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.0.2
Show newest version
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?
)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy