• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2017 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 
17 package com.android.tools.metalava.model
18 
19 import com.android.SdkConstants.ANDROID_URI
20 import com.android.SdkConstants.ATTR_MIN_SDK_VERSION
21 import com.android.SdkConstants.ATTR_NAME
22 import com.android.SdkConstants.TAG_PERMISSION
23 import com.android.SdkConstants.TAG_USES_SDK
24 import com.android.tools.metalava.CodebaseComparator
25 import com.android.tools.metalava.ComparisonVisitor
26 import com.android.tools.metalava.doclava1.Issues
27 import com.android.tools.metalava.model.psi.CodePrinter
28 import com.android.tools.metalava.model.text.TextBackedAnnotationItem
29 import com.android.tools.metalava.model.visitors.ItemVisitor
30 import com.android.tools.metalava.model.visitors.TypeVisitor
31 import com.android.tools.metalava.reporter
32 import com.android.utils.XmlUtils.getFirstSubTagByName
33 import com.android.utils.XmlUtils.getNextTagByName
34 import com.intellij.psi.PsiFile
35 import org.intellij.lang.annotations.Language
36 import org.objectweb.asm.Type
37 import org.objectweb.asm.tree.ClassNode
38 import org.objectweb.asm.tree.FieldInsnNode
39 import org.objectweb.asm.tree.FieldNode
40 import org.objectweb.asm.tree.MethodInsnNode
41 import org.objectweb.asm.tree.MethodNode
42 import java.io.File
43 import java.util.function.Predicate
44 import kotlin.text.Charsets.UTF_8
45 
46 /**
47  * Represents a complete unit of code -- typically in the form of a set
48  * of source trees, but also potentially backed by .jar files or even
49  * signature files
50  */
51 interface Codebase {
52     /** Description of what this codebase is (useful during debugging) */
53     var description: String
54 
55     /**
56      * The location of the API. Could point to a signature file, or a directory
57      * root for source files, or a jar file, etc.
58      */
59     var location: File
60 
61     /** The API level of this codebase, or -1 if not known */
62     var apiLevel: Int
63 
64     /** The packages in the codebase (may include packages that are not included in the API) */
65     fun getPackages(): PackageList
66 
67     /**
68      * The package documentation, if any - this returns overview.html files for each package
69      * that provided one. Not all codebases provide this.
70      */
71     fun getPackageDocs(): PackageDocs?
72 
73     /** The rough size of the codebase (package count) */
74     fun size(): Int
75 
76     /** Returns a class identified by fully qualified name, if in the codebase */
77     fun findClass(className: String): ClassItem?
78 
79     /** Returns a package identified by fully qualified name, if in the codebase */
80     fun findPackage(pkgName: String): PackageItem?
81 
82     /** Returns true if this codebase supports documentation. */
83     fun supportsDocumentation(): Boolean
84 
85     /**
86      * Returns true if this codebase corresponds to an already trusted API (e.g.
87      * is read in from something like an existing signature file); in that case,
88      * signature checks etc will not be performed.
89      */
90     fun trustedApi(): Boolean
91 
92     fun accept(visitor: ItemVisitor) {
93         getPackages().accept(visitor)
94     }
95 
96     fun acceptTypes(visitor: TypeVisitor) {
97         getPackages().acceptTypes(visitor)
98     }
99 
100     /**
101      * Visits this codebase and compares it with another codebase, informing the visitors about
102      * the correlations and differences that it finds
103      */
104     fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>? = null) {
105         CodebaseComparator().compare(visitor, other, this, filter)
106     }
107 
108     /**
109      * Creates an annotation item for the given (fully qualified) Java source
110      */
111     fun createAnnotation(
112         @Language("JAVA") source: String,
113         context: Item? = null,
114         mapName: Boolean = true
115     ): AnnotationItem = TextBackedAnnotationItem(
116         this, source, mapName
117     )
118 
119     /**
120      * Returns true if the codebase contains one or more Kotlin files
121      */
122     fun hasKotlin(): Boolean {
123         return units.any { it.fileType.name == "Kotlin" }
124     }
125 
126     /**
127      * Returns true if the codebase contains one or more Java files
128      */
129     fun hasJava(): Boolean {
130         return units.any { it.fileType.name == "JAVA" }
131     }
132 
133     /** The manifest to associate with this codebase, if any */
134     var manifest: File?
135 
136     /**
137      * Returns the permission level of the named permission, if specified
138      * in the manifest. This method should only be called if the codebase has
139      * been configured with a manifest
140      */
141     fun getPermissionLevel(name: String): String?
142 
143     fun getMinSdkVersion(): MinSdkVersion
144 
145     /** Clear the [Item.tag] fields (prior to iteration like DFS) */
146     fun clearTags() {
147         getPackages().packages.forEach { pkg -> pkg.allClasses().forEach { cls -> cls.tag = false } }
148     }
149 
150     /** Reports that the given operation is unsupported for this codebase type */
151     fun unsupported(desc: String? = null): Nothing
152 
153     /** Discards this model */
154     fun dispose() {
155         description += " [disposed]"
156     }
157 
158     /** If this codebase was filtered from another codebase, this points to the original */
159     var original: Codebase?
160 
161     /** Returns the compilation units used in this codebase (may be empty
162      * when the codebase is not loaded from source, such as from .jar files or
163      * from signature files) */
164     var units: List<PsiFile>
165 
166     /**
167      * Printer which can convert PSI, UAST and constants into source code,
168      * with ability to filter out elements that are not part of a codebase etc
169      */
170     val printer: CodePrinter
171 
172     /** If true, this codebase has already been filtered */
173     val preFiltered: Boolean
174 
175     /** Finds the given class by JVM owner */
176     fun findClassByOwner(owner: String, apiFilter: Predicate<Item>): ClassItem? {
177         val className = owner.replace('/', '.').replace('$', '.')
178         val cls = findClass(className)
179         return if (cls != null && apiFilter.test(cls)) {
180             cls
181         } else {
182             null
183         }
184     }
185 
186     fun findClass(node: ClassNode, apiFilter: Predicate<Item>): ClassItem? {
187         return findClassByOwner(node.name, apiFilter)
188     }
189 
190     fun findMethod(node: MethodInsnNode, apiFilter: Predicate<Item>): MethodItem? {
191         val cls = findClassByOwner(node.owner, apiFilter) ?: return null
192         val types = Type.getArgumentTypes(node.desc)
193         val parameters = if (types.isNotEmpty()) {
194             val sb = StringBuilder()
195             for (type in types) {
196                 if (!sb.isEmpty()) {
197                     sb.append(", ")
198                 }
199                 sb.append(type.className.replace('/', '.').replace('$', '.'))
200             }
201             sb.toString()
202         } else {
203             ""
204         }
205         val methodName = if (node.name == "<init>") cls.simpleName() else node.name
206         val method = cls.findMethod(methodName, parameters)
207         return if (method != null && apiFilter.test(method)) {
208             method
209         } else {
210             null
211         }
212     }
213 
214     fun findMethod(classNode: ClassNode, node: MethodNode, apiFilter: Predicate<Item>): MethodItem? {
215         val cls = findClass(classNode, apiFilter) ?: return null
216         val types = Type.getArgumentTypes(node.desc)
217         val parameters = if (types.isNotEmpty()) {
218             val sb = StringBuilder()
219             for (type in types) {
220                 if (!sb.isEmpty()) {
221                     sb.append(", ")
222                 }
223                 sb.append(type.className.replace('/', '.').replace('$', '.'))
224             }
225             sb.toString()
226         } else {
227             ""
228         }
229         val methodName = if (node.name == "<init>") cls.simpleName() else node.name
230         val method = cls.findMethod(methodName, parameters)
231         return if (method != null && apiFilter.test(method)) {
232             method
233         } else {
234             null
235         }
236     }
237 
238     fun findField(classNode: ClassNode, node: FieldNode, apiFilter: Predicate<Item>): FieldItem? {
239         val cls = findClass(classNode, apiFilter) ?: return null
240         val field = cls.findField(node.name)
241         return if (field != null && apiFilter.test(field)) {
242             field
243         } else {
244             null
245         }
246     }
247 
248     fun findField(node: FieldInsnNode, apiFilter: Predicate<Item>): FieldItem? {
249         val cls = findClassByOwner(node.owner, apiFilter) ?: return null
250         val field = cls.findField(node.name)
251         return if (field != null && apiFilter.test(field)) {
252             field
253         } else {
254             null
255         }
256     }
257 
258     fun isEmpty(): Boolean {
259         return getPackages().packages.isEmpty()
260     }
261 }
262 
263 sealed class MinSdkVersion
264 data class SetMinSdkVersion(val value: Int) : MinSdkVersion()
265 object UnsetMinSdkVersion : MinSdkVersion()
266 
267 abstract class DefaultCodebase(override var location: File) : Codebase {
268     override var manifest: File? = null
269     private var permissions: Map<String, String>? = null
270     private var minSdkVersion: MinSdkVersion? = null
271     override var original: Codebase? = null
272     override var units: List<PsiFile> = emptyList()
273     override var apiLevel: Int = -1
274     @Suppress("LeakingThis")
275     override val printer = CodePrinter(this)
276     @Suppress("LeakingThis")
277     override var preFiltered: Boolean = original != null
278 
getPermissionLevelnull279     override fun getPermissionLevel(name: String): String? {
280         if (permissions == null) {
281             assert(manifest != null) {
282                 "This method should only be called when a manifest has been configured on the codebase"
283             }
284             try {
285                 val map = HashMap<String, String>(600)
286                 val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true)
287                 var current = getFirstSubTagByName(doc.documentElement, TAG_PERMISSION)
288                 while (current != null) {
289                     val permissionName = current.getAttributeNS(ANDROID_URI, ATTR_NAME)
290                     val protectionLevel = current.getAttributeNS(ANDROID_URI, "protectionLevel")
291                     map[permissionName] = protectionLevel
292                     current = getNextTagByName(current, TAG_PERMISSION)
293                 }
294                 permissions = map
295             } catch (error: Throwable) {
296                 reporter.report(Issues.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}")
297                 permissions = emptyMap()
298             }
299         }
300 
301         return permissions!![name]
302     }
303 
getMinSdkVersionnull304     override fun getMinSdkVersion(): MinSdkVersion {
305         if (minSdkVersion == null) {
306             if (manifest == null) {
307                 minSdkVersion = UnsetMinSdkVersion
308                 return minSdkVersion!!
309             }
310             try {
311                 val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true)
312                 val usesSdk = getFirstSubTagByName(doc.documentElement, TAG_USES_SDK)
313                 minSdkVersion = if (usesSdk == null) {
314                     UnsetMinSdkVersion
315                 } else {
316                     val value = usesSdk.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)
317                     if (value.isEmpty()) UnsetMinSdkVersion else SetMinSdkVersion(value.toInt())
318                 }
319             } catch (error: Throwable) {
320                 reporter.report(Issues.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}")
321                 minSdkVersion = UnsetMinSdkVersion
322             }
323         }
324         return minSdkVersion!!
325     }
326 
getPackageDocsnull327     override fun getPackageDocs(): PackageDocs? = null
328 
329     override fun unsupported(desc: String?): Nothing {
330         error(desc ?: "This operation is not available on this type of codebase (${this.javaClass.simpleName})")
331     }
332 }
333