Home | History | Annotate | Download | only in model
      1 /*
      2  * 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 
    162     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 
    187     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 }