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