1944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis/*
24a360e3a3af230badc847867c117f605367170aaFilip Pavlis * Copyright 2017 The Android Open Source Project
3944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis *
4944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * Licensed under the Apache License, Version 2.0 (the "License");
5944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * you may not use this file except in compliance with the License.
6944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * You may obtain a copy of the License at
7944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis *
84a360e3a3af230badc847867c117f605367170aaFilip Pavlis *      http://www.apache.org/licenses/LICENSE-2.0
9944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis *
10944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * Unless required by applicable law or agreed to in writing, software
11944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * distributed under the License is distributed on an "AS IS" BASIS,
12944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * See the License for the specific language governing permissions and
144a360e3a3af230badc847867c117f605367170aaFilip Pavlis * limitations under the License.
15944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis */
16944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
17ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlispackage com.android.tools.build.jetifier.processor.transform.pom
18944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
19ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.jetifier.core.pom.PomDependency
20ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.jetifier.core.pom.PomRewriteRule
21ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.jetifier.core.utils.Log
22ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.jetifier.processor.archive.ArchiveFile
23ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.jetifier.processor.transform.TransformationContext
24944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlisimport org.jdom2.Document
25944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlisimport org.jdom2.Element
26944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
27944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis/**
28944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis * Wraps a single POM XML [ArchiveFile] with parsed metadata about transformation related sections.
29944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis */
30944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlisclass PomDocument(val file: ArchiveFile, private val document: Document) {
31944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
32944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    companion object {
33944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        private const val TAG = "Pom"
34944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
3560486c575581dcc40605ea91e1062afa30598e36Filip Pavlis        fun loadFrom(file: ArchiveFile): PomDocument {
36944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            val document = XmlUtils.createDocumentFromByteArray(file.data)
37944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            val pomDoc = PomDocument(file, document)
38944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            pomDoc.initialize()
39944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            return pomDoc
40944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
41944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
42944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
4360486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    val dependencies: MutableSet<PomDependency> = mutableSetOf()
4460486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    private val properties: MutableMap<String, String> = mutableMapOf()
4560486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    private var dependenciesGroup: Element? = null
4660486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    private var hasChanged: Boolean = false
47944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
48944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    private fun initialize() {
49944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        val propertiesGroup = document.rootElement
50944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis                .getChild("properties", document.rootElement.namespace)
51944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        if (propertiesGroup != null) {
52944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            propertiesGroup.children
53944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis                .filterNot { it.value.isNullOrEmpty() }
54944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis                .forEach { properties[it.name] = it.value }
55944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
56944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
57944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        dependenciesGroup = document.rootElement
58944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis                .getChild("dependencies", document.rootElement.namespace) ?: return
59944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        dependenciesGroup!!.children.mapTo(dependencies) {
604a360e3a3af230badc847867c117f605367170aaFilip Pavlis            XmlUtils.createDependencyFrom(it, properties)
61944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
62944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
63944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
64944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    /**
65944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Validates that this document is consistent with the provided [rules].
66944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     *
67944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Currently it checks that all the dependencies that are going to be rewritten by the given
68944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * rules satisfy the minimal version requirements defined by the rules.
69944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     */
7042ecffe61656f054f9390a61236b1441b659407cFilip Pavlis    fun validate(rules: Set<PomRewriteRule>): Boolean {
71944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        if (dependenciesGroup == null) {
72944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            // Nothing to validate as this file has no dependencies section
73944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            return true
74944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
75944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
7690f7af8ae099adad8555b7268c36d072dd139b48Filip Pavlis        return dependencies.all { dep -> rules.all { it.validateVersion(dep) } }
77944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
78944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
79944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    /**
80944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Applies the given [rules] to rewrite the POM file.
81944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     *
82944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Changes are not saved back until requested.
83944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     */
8414046f89f16795bd405c4f0710e0988a97871e4cFilip Pavlis    fun applyRules(context: TransformationContext) {
859462a308f59846b0947a0ca22226f0f4dee51414Filip Pavlis        tryRewriteOwnArtifactInfo(context)
860962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
87944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        if (dependenciesGroup == null) {
88944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            // Nothing to transform as this file has no dependencies section
89944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            return
90944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
91944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
92944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        val newDependencies = mutableSetOf<PomDependency>()
93944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        for (dependency in dependencies) {
9446fd22a7ae48f6952264396ba3932f72c7701de7Filip Pavlis            newDependencies.add(mapDependency(dependency, context))
95944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
96944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
97944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        if (newDependencies.isEmpty()) {
98944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            return
99944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
100944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
101944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        dependenciesGroup!!.children.clear()
102944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        newDependencies.forEach { dependenciesGroup!!.addContent(it.toXmlElement(document)) }
103944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        hasChanged = true
104944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
105944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
1065dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis    fun getAsPomDependency(): PomDependency {
1075dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        val groupIdNode = document.rootElement
1085dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                .getChild("groupId", document.rootElement.namespace)
1095dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        val artifactIdNode = document.rootElement
1105dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                .getChild("artifactId", document.rootElement.namespace)
1115dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        val version = document.rootElement
1125dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                .getChild("version", document.rootElement.namespace)
1135dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis
1145dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        return PomDependency(groupIdNode.text, artifactIdNode.text, version.text)
1155dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis    }
1165dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis
1179462a308f59846b0947a0ca22226f0f4dee51414Filip Pavlis    private fun tryRewriteOwnArtifactInfo(context: TransformationContext) {
1180962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val groupIdNode = document.rootElement
1190962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis                .getChild("groupId", document.rootElement.namespace)
1200962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val artifactIdNode = document.rootElement
1210962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis                .getChild("artifactId", document.rootElement.namespace)
1220962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val version = document.rootElement
1230962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis                .getChild("version", document.rootElement.namespace)
1240962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1250962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        if (groupIdNode == null || artifactIdNode == null || version == null) {
1260962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            return
1270962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        }
1280962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1290962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val dependency = PomDependency(groupIdNode.text, artifactIdNode.text, version.text)
13046fd22a7ae48f6952264396ba3932f72c7701de7Filip Pavlis        val newDependency = mapDependency(dependency, context)
1310962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1320962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        if (newDependency != dependency) {
1330962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            groupIdNode.text = newDependency.groupId
1340962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            artifactIdNode.text = newDependency.artifactId
1350962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            version.text = newDependency.version
1369462a308f59846b0947a0ca22226f0f4dee51414Filip Pavlis            hasChanged = true
1370962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        }
1380962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis    }
1390962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1400962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis    private fun mapDependency(
1419462a308f59846b0947a0ca22226f0f4dee51414Filip Pavlis        dependency: PomDependency,
1429462a308f59846b0947a0ca22226f0f4dee51414Filip Pavlis        context: TransformationContext
14346fd22a7ae48f6952264396ba3932f72c7701de7Filip Pavlis    ): PomDependency {
1440962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val rule = context.config.pomRewriteRules.firstOrNull { it.matches(dependency) }
1450962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        if (rule != null) {
1460962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            // Replace with new dependencies
1475b12e59efaab28afee6ffc712a7c8d6ecbae8144Filip Pavlis            return rule.to.rewrite(dependency, context.versions)
1480962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        }
1490962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1500962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        val matchesPrefix = context.config.restrictToPackagePrefixesWithDots.any {
1510962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis            dependency.groupId!!.startsWith(it)
1520962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        }
1530962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1540962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        if (matchesPrefix) {
1554e29091e542d87269447fdc8b3254ce86af06845Filip Pavlis            context.reportNoPackageMappingFoundFailure(
1564e29091e542d87269447fdc8b3254ce86af06845Filip Pavlis                TAG,
1574e29091e542d87269447fdc8b3254ce86af06845Filip Pavlis                dependency.toStringNotation(),
1584e29091e542d87269447fdc8b3254ce86af06845Filip Pavlis                file.relativePath.toString())
1590962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        }
1600962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
1610962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis        // No rule to rewrite => keep it
16246fd22a7ae48f6952264396ba3932f72c7701de7Filip Pavlis        return dependency
1630962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis    }
1640962b3bfd04ef2bb3a52482c83368c4cd28ae73eFilip Pavlis
165944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    /**
166944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Saves any current pending changes back to the file if needed.
167944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     */
16890f7af8ae099adad8555b7268c36d072dd139b48Filip Pavlis    fun saveBackToFileIfNeeded() {
169944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        if (!hasChanged) {
170944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis            return
171944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
172944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
17360486c575581dcc40605ea91e1062afa30598e36Filip Pavlis        file.setNewData(XmlUtils.convertDocumentToByteArray(document))
174944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
175944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis
176944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    /**
177944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     * Logs the information about the current file using info level.
178944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis     */
179944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    fun logDocumentDetails() {
180944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        Log.i(TAG, "POM file at: '%s'", file.relativePath)
181944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        for ((groupId, artifactId, version) in dependencies) {
182448359062b9caf5f96c0028dc12cba5d2647ea6fFilip Pavlis            Log.v(TAG, "- Dep: %s:%s:%s", groupId, artifactId, version)
183944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis        }
184944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis    }
185944155bbada4741767c149b3f2f6a504cf79cc45Filip Pavlis}