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