1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tools.build.jetifier.core.config
18
19import com.android.tools.build.jetifier.core.PackageMap
20import com.android.tools.build.jetifier.core.pom.DependencyVersionsMap
21import com.android.tools.build.jetifier.core.pom.PomRewriteRule
22import com.android.tools.build.jetifier.core.proguard.ProGuardType
23import com.android.tools.build.jetifier.core.proguard.ProGuardTypesMap
24import com.android.tools.build.jetifier.core.rule.RewriteRule
25import com.android.tools.build.jetifier.core.rule.RewriteRulesMap
26import com.android.tools.build.jetifier.core.type.JavaType
27import com.android.tools.build.jetifier.core.type.PackageName
28import com.android.tools.build.jetifier.core.type.TypesMap
29import com.google.gson.annotations.SerializedName
30import java.util.regex.Pattern
31
32/**
33 * The main and only one configuration that is used by the tool and all its transformers.
34 *
35 * @param restrictToPackagePrefixes Package prefixes that limit the scope of the rewriting. In most
36 *  cases the rules have priority over this. We use this mainly to determine if we are actually
37 *  missing a rule in case we fail to rewrite.
38 * @param reversedRestrictToPackagePrefixes Same as [restrictToPackagePrefixes] but used when
39 *  running in reversed mode.
40 * @param rulesMap Rules to scan support libraries to generate [TypesMap]
41 * @param slRule List of rules used when rewriting the support library itself in the reversed mode
42 *  to ignore packages that don't need rewriting anymore.
43 * @param pomRewriteRules Rules to rewrite POM files
44 * @param typesMap Map of all java types and fields to be used to rewrite libraries.
45 * @param proGuardMap Proguard types map to be used for ProGuard files rewriting.
46 * @param versionsMap Pre-defined maps of versions to be substituted in pom dependency rules.
47 * @param packageMap Package map to be used to rewrite packages, used only during the support
48 *  library rewrite.
49 */
50data class Config(
51    val restrictToPackagePrefixes: Set<String>,
52    val reversedRestrictToPackagePrefixes: Set<String>,
53    val rulesMap: RewriteRulesMap,
54    val slRules: List<RewriteRule>,
55    val pomRewriteRules: Set<PomRewriteRule>,
56    val typesMap: TypesMap,
57    val proGuardMap: ProGuardTypesMap,
58    val versionsMap: DependencyVersionsMap,
59    val packageMap: PackageMap = PackageMap(PackageMap.DEFAULT_RULES)
60) {
61
62    init {
63        // Verify pom rules
64        val testSet = mutableSetOf<String>()
65        pomRewriteRules.forEach {
66            val raw = "${it.from.groupId}:${it.from.artifactId}"
67            if (!testSet.add(raw)) {
68                throw IllegalArgumentException("Artifact '$raw' is defined twice in pom rules!")
69            }
70        }
71    }
72
73    // Merges all packages prefixes into one regEx pattern
74    private val packagePrefixPattern = Pattern.compile(
75        "^(" + restrictToPackagePrefixes.map { "($it)" }.joinToString("|") + ").*$")
76
77    val restrictToPackagePrefixesWithDots: List<String> = restrictToPackagePrefixes
78        .map { it.replace("/", ".") }
79
80    companion object {
81        /** Path to the default config file located within the jar file. */
82        const val DEFAULT_CONFIG_RES_PATH = "/default.generated.config"
83
84        val EMPTY = fromOptional()
85
86        fun fromOptional(
87            restrictToPackagePrefixes: Set<String> = emptySet(),
88            reversedRestrictToPackagesPrefixes: Set<String> = emptySet(),
89            rulesMap: RewriteRulesMap = RewriteRulesMap.EMPTY,
90            slRules: List<RewriteRule> = emptyList(),
91            pomRewriteRules: Set<PomRewriteRule> = emptySet(),
92            typesMap: TypesMap = TypesMap.EMPTY,
93            proGuardMap: ProGuardTypesMap = ProGuardTypesMap.EMPTY,
94            versionsMap: DependencyVersionsMap = DependencyVersionsMap.EMPTY,
95            packageMap: PackageMap = PackageMap.EMPTY
96        ): Config {
97            return Config(
98                restrictToPackagePrefixes = restrictToPackagePrefixes,
99                reversedRestrictToPackagePrefixes = reversedRestrictToPackagesPrefixes,
100                rulesMap = rulesMap,
101                slRules = slRules,
102                pomRewriteRules = pomRewriteRules,
103                typesMap = typesMap,
104                proGuardMap = proGuardMap,
105                versionsMap = versionsMap,
106                packageMap = packageMap
107            )
108        }
109    }
110
111    fun setNewMap(mappings: TypesMap): Config {
112        return Config(
113            restrictToPackagePrefixes = restrictToPackagePrefixes,
114            reversedRestrictToPackagePrefixes = reversedRestrictToPackagePrefixes,
115            rulesMap = rulesMap,
116            slRules = slRules,
117            pomRewriteRules = pomRewriteRules,
118            typesMap = mappings,
119            proGuardMap = proGuardMap,
120            versionsMap = versionsMap,
121            packageMap = packageMap
122        )
123    }
124
125    /**
126     * Returns whether the given type is eligible for rewrite.
127     *
128     * If not, the transformers should ignore it.
129     */
130    fun isEligibleForRewrite(type: JavaType): Boolean {
131        if (!isEligibleForRewriteInternal(type.fullName)) {
132            return false
133        }
134
135        val isIgnored = rulesMap.runtimeIgnoreRules
136            .any { it.apply(type) == RewriteRule.TypeRewriteResult.IGNORED }
137        return !isIgnored
138    }
139
140    /**
141     * Returns whether the given ProGuard type reference is eligible for rewrite.
142     *
143     * Keep in mind that his has limited capabilities - mainly when * is used as a prefix. Rules
144     * like *.v7 are not matched by prefix support.v7. So don't rely on it and use
145     * the [ProGuardTypesMap] as first.
146     */
147    fun isEligibleForRewrite(type: ProGuardType): Boolean {
148        if (!isEligibleForRewriteInternal(type.value)) {
149            return false
150        }
151
152        val isIgnored = rulesMap.runtimeIgnoreRules.any { it.doesThisIgnoreProGuard(type) }
153        return !isIgnored
154    }
155
156    fun isEligibleForRewrite(type: PackageName): Boolean {
157        if (!isEligibleForRewriteInternal(type.fullName + "/")) {
158            return false
159        }
160
161        val javaType = JavaType(type.fullName + "/")
162        val isIgnored = rulesMap.runtimeIgnoreRules
163            .any { it.apply(javaType) == RewriteRule.TypeRewriteResult.IGNORED }
164        return !isIgnored
165    }
166
167    private fun isEligibleForRewriteInternal(type: String): Boolean {
168        if (restrictToPackagePrefixes.isEmpty()) {
169            return false
170        }
171        return packagePrefixPattern.matcher(type).matches()
172    }
173
174    /** Returns JSON data model of this class */
175    fun toJson(): JsonData {
176        return JsonData(
177            restrictToPackagePrefixes.toList(),
178            reversedRestrictToPackagePrefixes.toList(),
179            rulesMap.toJson().rules.toList(),
180            slRules.map { it.toJson() }.toList(),
181            pomRewriteRules.map { it.toJson() }.toList(),
182            versionsMap.data,
183            typesMap.toJson(),
184            proGuardMap.toJson()
185        )
186    }
187
188    /**
189     * JSON data model for [Config].
190     */
191    data class JsonData(
192        @SerializedName("restrictToPackagePrefixes")
193        val restrictToPackages: List<String?>,
194
195        @SerializedName("reversedRestrictToPackagePrefixes")
196        val reversedRestrictToPackages: List<String?>,
197
198        @SerializedName("rules")
199        val rules: List<RewriteRule.JsonData?>?,
200
201        @SerializedName("slRules")
202        val slRules: List<RewriteRule.JsonData?>?,
203
204        @SerializedName("pomRules")
205        val pomRules: List<PomRewriteRule.JsonData?>,
206
207        @SerializedName("versions")
208        val versions: Map<String, Map<String, String>>? = null,
209
210        @SerializedName("map")
211        val mappings: TypesMap.JsonData? = null,
212
213        @SerializedName("proGuardMap")
214        val proGuardMap: ProGuardTypesMap.JsonData? = null
215    ) {
216        /** Creates instance of [Config] */
217        fun toConfig(): Config {
218            return Config(
219                restrictToPackagePrefixes = restrictToPackages.filterNotNull().toSet(),
220                reversedRestrictToPackagePrefixes = reversedRestrictToPackages
221                    .filterNotNull().toSet(),
222                rulesMap = rules
223                    ?.let { RewriteRulesMap(it.filterNotNull().map { it.toRule() }.toList()) }
224                    ?: RewriteRulesMap.EMPTY,
225                slRules = slRules
226                    ?.let { it.filterNotNull().map { it.toRule() }.toList() }
227                    ?: emptyList(),
228                pomRewriteRules = pomRules.filterNotNull().map { it.toRule() }.toSet(),
229                versionsMap = versions
230                    ?.let { DependencyVersionsMap(versions) }
231                    ?: DependencyVersionsMap.EMPTY,
232                typesMap = mappings?.toMappings() ?: TypesMap.EMPTY,
233                proGuardMap = proGuardMap?.toMappings() ?: ProGuardTypesMap.EMPTY
234            )
235        }
236    }
237}
238