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