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 java.util.TreeSet 20 21 /** Represents a Kotlin/Java source file */ 22 interface SourceFile { 23 /** Top level classes contained in this file */ classesnull24 fun classes(): Sequence<ClassItem> 25 26 fun getHeaderComments(): String? = null 27 28 /** Get all the imports. */ 29 fun getImports() = getImports { true } 30 31 /** Get only those imports that reference [Item]s for which [predicate] returns `true`. */ getImportsnull32 fun getImports(predicate: FilterPredicate): Collection<Import> = emptyList() 33 34 /** 35 * Compute set of import statements that are actually referenced from the documentation (we do 36 * inexact matching here; we don't need to have an exact set of imports since it's okay to have 37 * some extras). This isn't a big problem since our code style forbids/discourages wildcards, so 38 * it shows up in fewer places, but we need to handle it when it does -- such as in ojluni. 39 */ 40 fun filterImports(imports: TreeSet<Import>, predicate: FilterPredicate): TreeSet<Import> { 41 // Create a map from the short name for the import to a list of the items imported. A 42 // list is needed because classes and members could be imported with the same short 43 // name. 44 val remainingImports = mutableMapOf<String, MutableList<Import>>() 45 imports.groupByTo(remainingImports) { it.name } 46 47 val result = TreeSet<Import>(compareBy { it.pattern }) 48 49 // We keep the wildcard imports since we don't know which ones of those are relevant 50 imports.filter { it.name == "*" }.forEach { result.add(it) } 51 52 for (cls in classes().filter { predicate.test(it) }) { 53 cls.accept( 54 object : TraversingVisitor() { 55 override fun visitItem(item: Item): TraversalAction { 56 if (item !is SelectableItem) return TraversalAction.SKIP_CHILDREN 57 58 // Do not let documentation on hidden items affect the imports. 59 if (!predicate.test(item)) { 60 // Just because an item like a class is hidden does not mean 61 // that its child items are so make sure to visit them. 62 return TraversalAction.CONTINUE 63 } 64 val doc = item.documentation.text 65 if (doc.isNotBlank()) { 66 // Scan the documentation text to see if it contains any of the 67 // short names imported. It does not check whether the names 68 // are actually used as part of a link, so they could just be in 69 // as text but having extra imports should not be an issue. 70 var found: MutableList<String>? = null 71 for (name in remainingImports.keys) { 72 if (docContainsWord(doc, name)) { 73 if (found == null) { 74 found = mutableListOf() 75 } 76 found.add(name) 77 } 78 } 79 80 // For every imported name add all the matching imports and then 81 // remove them from the available imports as there is no need to 82 // check them again. 83 found?.let { 84 for (name in found) { 85 val all = remainingImports.remove(name) ?: continue 86 result.addAll(all) 87 } 88 89 if (remainingImports.isEmpty()) { 90 // There is nothing to do if the map of imports to add 91 // is empty. 92 return TraversalAction.SKIP_TRAVERSAL 93 } 94 } 95 } 96 97 return TraversalAction.CONTINUE 98 } 99 } 100 ) 101 } 102 return result 103 } 104 docContainsWordnull105 fun docContainsWord(doc: String, word: String): Boolean { 106 // Cache pattern compilation across source files 107 val regexMap = HashMap<String, Regex>() 108 109 if (!doc.contains(word)) { 110 return false 111 } 112 113 val regex = 114 regexMap[word] 115 ?: run { 116 val new = Regex("""\b$word\b""") 117 regexMap[word] = new 118 new 119 } 120 return regex.find(doc) != null 121 } 122 } 123 124 /** Encapsulates information about the imports used in a [SourceFile]. */ 125 @ConsistentCopyVisibility 126 data class Import 127 internal constructor( 128 /** 129 * The import pattern, i.e. the whole part of the import statement after `import static? ` and 130 * before the optional `;`, excluding any whitespace. 131 */ 132 val pattern: String, 133 134 /** 135 * The name that is being imported, i.e. the part after the last `.`. Is `*` for wildcard 136 * imports. 137 */ 138 val name: String, 139 140 /** 141 * True if the item that is being imported is a member of a class. Corresponds to the `static` 142 * keyword in Java, has no effect on Kotlin import statements. 143 */ 144 val isMember: Boolean, 145 ) { 146 /** Import a whole [PackageItem], i.e. uses a wildcard. */ 147 constructor(pkgItem: PackageItem) : this("${pkgItem.qualifiedName()}.*", "*", false) 148 149 /** Import a [ClassItem]. */ 150 constructor( 151 classItem: ClassItem 152 ) : this( 153 classItem.qualifiedName(), 154 classItem.simpleName(), 155 false, 156 ) 157 158 /** Import a [MemberItem]. */ 159 constructor( 160 memberItem: MemberItem 161 ) : this( 162 "${memberItem.containingClass().qualifiedName()}.${memberItem.name()}", 163 memberItem.name(), 164 true, 165 ) 166 } 167