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 }