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