• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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