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