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.text.TextBackedAnnotationItem
26 import com.android.tools.metalava.model.visitors.ItemVisitor
27 import com.android.tools.metalava.model.visitors.TypeVisitor
28 import com.android.tools.metalava.reporter
29 import com.android.utils.XmlUtils
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 java.io.File
35 import java.util.function.Predicate
36 import kotlin.text.Charsets.UTF_8
37 
38 /**
39  * Represents a complete unit of code -- typically in the form of a set
40  * of source trees, but also potentially backed by .jar files or even
41  * signature files
42  */
43 interface Codebase {
44     /** Description of what this codebase is (useful during debugging) */
45     var description: String
46 
47     /** The API level of this codebase, or -1 if not known */
48     var apiLevel: Int
49 
50     /** The packages in the codebase (may include packages that are not included in the API) */
51     fun getPackages(): PackageList
52 
53     /**
54      * The package documentation, if any - this returns overview.html files for each package
55      * that provided one. Note all codebases provide this.
56      */
57     fun getPackageDocs(): PackageDocs?
58 
59     /** The rough size of the codebase (package count) */
60     fun size(): Int
61 
62     /** Returns a class identified by fully qualified name, if in the codebase */
63     fun findClass(className: String): ClassItem?
64 
65     /** Returns a package identified by fully qualified name, if in the codebase */
66     fun findPackage(pkgName: String): PackageItem?
67 
68     /** Returns true if this codebase supports documentation. */
69     fun supportsDocumentation(): Boolean
70 
71     /**
72      * Returns true if this codebase corresponds to an already trusted API (e.g.
73      * is read in from something like an existing signature file); in that case,
74      * signature checks etc will not be performed.
75      */
76     fun trustedApi(): Boolean
77 
78     fun accept(visitor: ItemVisitor) {
79         getPackages().accept(visitor)
80     }
81 
82     fun acceptTypes(visitor: TypeVisitor) {
83         getPackages().acceptTypes(visitor)
84     }
85 
86     /**
87      * Visits this codebase and compares it with another codebase, informing the visitors about
88      * the correlations and differences that it finds
89      */
90     fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>? = null) {
91         CodebaseComparator().compare(visitor, other, this, filter)
92     }
93 
94     /**
95      * Creates an annotation item for the given (fully qualified) Java source
96      */
97     fun createAnnotation(
98         @Language("JAVA") source: String,
99         context: Item? = null,
100         mapName: Boolean = true
101     ): AnnotationItem = TextBackedAnnotationItem(
102         this, source, mapName
103     )
104 
105     /**
106      * Returns true if the codebase contains one or more Kotlin files
107      */
108     fun hasKotlin(): Boolean {
109         return units.any { it.fileType.name == "Kotlin" }
110     }
111 
112     /**
113      * Returns true if the codebase contains one or more Java files
114      */
115     fun hasJava(): Boolean {
116         return units.any { it.fileType.name == "JAVA" }
117     }
118 
119     /** The manifest to associate with this codebase, if any */
120     var manifest: File?
121 
122     /**
123      * Returns the permission level of the named permission, if specified
124      * in the manifest. This method should only be called if the codebase has
125      * been configured with a manifest
126      */
127     fun getPermissionLevel(name: String): String?
128 
129     /** Clear the [Item.tag] fields (prior to iteration like DFS) */
130     fun clearTags() {
131         getPackages().packages.forEach { pkg -> pkg.allClasses().forEach { cls -> cls.tag = false } }
132     }
133 
134     /**
135      * Creates a filtered version of this codebase
136      */
137     fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase
138 
139     /** Reports that the given operation is unsupported for this codebase type */
140     fun unsupported(desc: String? = null): Nothing
141 
142     /** Whether this codebase supports staged nullability (RecentlyNullable etc) */
143     var supportsStagedNullability: Boolean
144 
145     /** If this codebase was filtered from another codebase, this points to the original */
146     var original: Codebase?
147 
148     /** Returns the compilation units used in this codebase (may be empty
149      * when the codebase is not loaded from source, such as from .jar files or
150      * from signature files) */
151     var units: List<PsiFile>
152 }
153 
154 abstract class DefaultCodebase : Codebase {
155     override var manifest: File? = null
156     private var permissions: Map<String, String>? = null
157     override var original: Codebase? = null
158     override var supportsStagedNullability: Boolean = false
159     override var units: List<PsiFile> = emptyList()
160     override var apiLevel: Int = -1
161 
getPermissionLevelnull162     override fun getPermissionLevel(name: String): String? {
163         if (permissions == null) {
164             assert(manifest != null) {
165                 "This method should only be called when a manifest has been configured on the codebase"
166             }
167             try {
168                 val map = HashMap<String, String>(600)
169                 val doc = XmlUtils.parseDocument(manifest?.readText(UTF_8), true)
170                 var current = getFirstSubTagByName(doc.documentElement, TAG_PERMISSION)
171                 while (current != null) {
172                     val permissionName = current.getAttributeNS(ANDROID_URI, ATTR_NAME)
173                     val protectionLevel = current.getAttributeNS(ANDROID_URI, "protectionLevel")
174                     map[permissionName] = protectionLevel
175                     current = getNextTagByName(current, TAG_PERMISSION)
176                 }
177                 permissions = map
178             } catch (error: Throwable) {
179                 reporter.report(Errors.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}")
180                 permissions = emptyMap()
181             }
182         }
183 
184         return permissions!![name]
185     }
186 
getPackageDocsnull187     override fun getPackageDocs(): PackageDocs? = null
188 
189     override fun unsupported(desc: String?): Nothing {
190         error(desc ?: "This operation is not available on this type of codebase (${this.javaClass.simpleName})")
191     }
192 }