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.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.objectweb.asm.Type 36 import org.objectweb.asm.tree.ClassNode 37 import org.objectweb.asm.tree.FieldInsnNode 38 import org.objectweb.asm.tree.FieldNode 39 import org.objectweb.asm.tree.MethodInsnNode 40 import org.objectweb.asm.tree.MethodNode 41 import java.io.File 42 import java.util.function.Predicate 43 import kotlin.text.Charsets.UTF_8 44 45 /** 46 * Represents a complete unit of code -- typically in the form of a set 47 * of source trees, but also potentially backed by .jar files or even 48 * signature files 49 */ 50 interface Codebase { 51 /** Description of what this codebase is (useful during debugging) */ 52 var description: String 53 54 /** 55 * The location of the API. Could point to a signature file, or a directory 56 * root for source files, or a jar file, etc. 57 */ 58 var location: File 59 60 /** The packages in the codebase (may include packages that are not included in the API) */ 61 fun getPackages(): PackageList 62 63 /** 64 * The package documentation, if any - this returns overview.html files for each package 65 * that provided one. Not all codebases provide this. 66 */ 67 fun getPackageDocs(): PackageDocs? 68 69 /** The rough size of the codebase (package count) */ 70 fun size(): Int 71 72 /** Returns a class identified by fully qualified name, if in the codebase */ 73 fun findClass(className: String): ClassItem? 74 75 /** Returns a package identified by fully qualified name, if in the codebase */ 76 fun findPackage(pkgName: String): PackageItem? 77 78 /** Returns true if this codebase supports documentation. */ 79 fun supportsDocumentation(): Boolean 80 81 /** 82 * Returns true if this codebase corresponds to an already trusted API (e.g. 83 * is read in from something like an existing signature file); in that case, 84 * signature checks etc will not be performed. 85 */ 86 fun trustedApi(): Boolean 87 88 fun accept(visitor: ItemVisitor) { 89 getPackages().accept(visitor) 90 } 91 92 fun acceptTypes(visitor: TypeVisitor) { 93 getPackages().acceptTypes(visitor) 94 } 95 96 /** 97 * Visits this codebase and compares it with another codebase, informing the visitors about 98 * the correlations and differences that it finds 99 */ 100 fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>? = null) { 101 CodebaseComparator().compare(visitor, other, this, filter) 102 } 103 104 /** 105 * Creates an annotation item for the given (fully qualified) Java source 106 */ 107 fun createAnnotation( 108 source: String, 109 context: Item? = null, 110 mapName: Boolean = true 111 ): AnnotationItem = TextBackedAnnotationItem( 112 this, source, mapName 113 ) 114 115 /** 116 * Returns true if the codebase contains one or more Kotlin files 117 */ 118 fun hasKotlin(): Boolean { 119 return units.any { it.fileType.name == "Kotlin" } 120 } 121 122 /** 123 * Returns true if the codebase contains one or more Java files 124 */ 125 fun hasJava(): Boolean { 126 return units.any { it.fileType.name == "JAVA" } 127 } 128 129 /** The manifest to associate with this codebase, if any */ 130 var manifest: File? 131 132 /** 133 * Returns the permission level of the named permission, if specified 134 * in the manifest. This method should only be called if the codebase has 135 * been configured with a manifest 136 */ 137 fun getPermissionLevel(name: String): String? 138 139 fun getMinSdkVersion(): MinSdkVersion 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.isNotEmpty()) { 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.isNotEmpty()) { 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 sealed class MinSdkVersion 260 data class SetMinSdkVersion(val value: Int) : MinSdkVersion() 261 object UnsetMinSdkVersion : MinSdkVersion() 262 263 abstract class DefaultCodebase(override var location: File) : Codebase { 264 override var manifest: File? = null 265 private var permissions: Map<String, String>? = null 266 private var minSdkVersion: MinSdkVersion? = null 267 override var original: Codebase? = null 268 override var units: List<PsiFile> = emptyList() 269 @Suppress("LeakingThis") 270 override val printer = CodePrinter(this) 271 @Suppress("LeakingThis") 272 override var preFiltered: Boolean = original != null 273 getPermissionLevelnull274 override fun getPermissionLevel(name: String): String? { 275 if (permissions == null) { 276 assert(manifest != null) { 277 "This method should only be called when a manifest has been configured on the codebase" 278 } 279 try { 280 val map = HashMap<String, String>(600) 281 val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true) 282 var current = getFirstSubTagByName(doc.documentElement, TAG_PERMISSION) 283 while (current != null) { 284 val permissionName = current.getAttributeNS(ANDROID_URI, ATTR_NAME) 285 val protectionLevel = current.getAttributeNS(ANDROID_URI, "protectionLevel") 286 map[permissionName] = protectionLevel 287 current = getNextTagByName(current, TAG_PERMISSION) 288 } 289 permissions = map 290 } catch (error: Throwable) { 291 reporter.report(Issues.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}") 292 permissions = emptyMap() 293 } 294 } 295 296 return permissions!![name] 297 } 298 getMinSdkVersionnull299 override fun getMinSdkVersion(): MinSdkVersion { 300 if (minSdkVersion == null) { 301 if (manifest == null) { 302 minSdkVersion = UnsetMinSdkVersion 303 return minSdkVersion!! 304 } 305 minSdkVersion = try { 306 val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true) 307 val usesSdk = getFirstSubTagByName(doc.documentElement, TAG_USES_SDK) 308 if (usesSdk == null) { 309 UnsetMinSdkVersion 310 } else { 311 val value = usesSdk.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION) 312 if (value.isEmpty()) UnsetMinSdkVersion else SetMinSdkVersion(value.toInt()) 313 } 314 } catch (error: Throwable) { 315 reporter.report(Issues.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}") 316 UnsetMinSdkVersion 317 } 318 } 319 return minSdkVersion!! 320 } 321 getPackageDocsnull322 override fun getPackageDocs(): PackageDocs? = null 323 324 override fun unsupported(desc: String?): Nothing { 325 error(desc ?: "This operation is not available on this type of codebase (${this.javaClass.simpleName})") 326 } 327 } 328