1 /*
2  * Copyright (C) 2018 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.psi
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.Import
21 import com.android.tools.metalava.model.Item
22 import com.android.tools.metalava.model.SourceFile
23 import com.intellij.psi.PsiClass
24 import com.intellij.psi.PsiClassOwner
25 import com.intellij.psi.PsiComment
26 import com.intellij.psi.PsiElement
27 import com.intellij.psi.PsiField
28 import com.intellij.psi.PsiFile
29 import com.intellij.psi.PsiJavaFile
30 import com.intellij.psi.PsiMethod
31 import com.intellij.psi.PsiPackage
32 import com.intellij.psi.PsiWhiteSpace
33 import java.util.TreeSet
34 import java.util.function.Predicate
35 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
36 import org.jetbrains.kotlin.psi.KtFile
37 import org.jetbrains.kotlin.psi.psiUtil.startOffset
38 import org.jetbrains.uast.UFile
39 
40 /** Whether we should limit import statements to symbols found in class docs */
41 private const val ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS = true
42 
43 internal class PsiSourceFile(
44     val codebase: PsiBasedCodebase,
45     val file: PsiFile,
46     val uFile: UFile? = null
47 ) : SourceFile {
getHeaderCommentsnull48     override fun getHeaderComments(): String? {
49         if (uFile != null) {
50             var comment: String? = null
51             for (uComment in uFile.allCommentsInFile) {
52                 val text = uComment.text
53                 comment =
54                     if (comment != null) {
55                         comment + "\n" + text
56                     } else {
57                         text
58                     }
59             }
60             return comment
61         }
62 
63         // https://youtrack.jetbrains.com/issue/KT-22135
64         if (file is PsiJavaFile) {
65             val pkg = file.packageStatement ?: return null
66             return file.text.substring(0, pkg.startOffset)
67         } else if (file is KtFile) {
68             var curr: PsiElement? = file.firstChild
69             var comment: String? = null
70             while (curr != null) {
71                 if (curr is PsiComment || curr is KDoc) {
72                     val text = curr.text
73                     comment =
74                         if (comment != null) {
75                             comment + "\n" + text
76                         } else {
77                             text
78                         }
79                 } else if (curr !is PsiWhiteSpace) {
80                     break
81                 }
82                 curr = curr.nextSibling
83             }
84             return comment
85         }
86 
87         return super.getHeaderComments()
88     }
89 
getImportsnull90     override fun getImports(predicate: Predicate<Item>): Collection<Import> {
91         val imports = TreeSet<Import>(compareBy { it.pattern })
92 
93         if (file is PsiJavaFile) {
94             val importList = file.importList
95             if (importList != null) {
96                 for (importStatement in importList.importStatements) {
97                     val resolved = importStatement.resolve() ?: continue
98                     if (resolved is PsiClass) {
99                         val classItem = codebase.findClass(resolved) ?: continue
100                         if (predicate.test(classItem)) {
101                             imports.add(Import(classItem))
102                         }
103                     } else if (resolved is PsiPackage) {
104                         val pkgItem = codebase.findPackage(resolved.qualifiedName) ?: continue
105                         if (
106                             predicate.test(pkgItem) &&
107                                 // Also make sure it isn't an empty package (after applying the
108                                 // filter)
109                                 // since in that case we'd have an invalid import
110                                 pkgItem.topLevelClasses().any { it.emit && predicate.test(it) }
111                         ) {
112                             imports.add(Import(pkgItem))
113                         }
114                     } else if (resolved is PsiMethod) {
115                         codebase.findClass(resolved.containingClass ?: continue) ?: continue
116                         val methodItem = codebase.findMethod(resolved)
117                         if (predicate.test(methodItem)) {
118                             imports.add(Import(methodItem))
119                         }
120                     } else if (resolved is PsiField) {
121                         val classItem =
122                             codebase.findClass(resolved.containingClass ?: continue) ?: continue
123                         val fieldItem =
124                             classItem.findField(
125                                 resolved.name,
126                                 includeSuperClasses = true,
127                                 includeInterfaces = false
128                             )
129                                 ?: continue
130                         if (predicate.test(fieldItem)) {
131                             imports.add(Import(fieldItem))
132                         }
133                     }
134                 }
135             }
136         } else if (file is KtFile) {
137             for (importDirective in file.importDirectives) {
138                 val resolved = importDirective.reference?.resolve() ?: continue
139                 if (resolved is PsiClass) {
140                     val classItem = codebase.findClass(resolved) ?: continue
141                     if (predicate.test(classItem)) {
142                         imports.add(Import(classItem))
143                     }
144                 }
145             }
146         }
147 
148         // Next only keep those that are present in any docs; those are the only ones
149         // we need to import
150         if (imports.isNotEmpty()) {
151             @Suppress("ConstantConditionIf")
152             return if (ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS) {
153                 filterImports(imports, predicate)
154             } else {
155                 imports
156             }
157         }
158 
159         return emptyList()
160     }
161 
classesnull162     override fun classes(): Sequence<ClassItem> {
163         return (file as? PsiClassOwner)
164             ?.classes
165             ?.asSequence()
166             ?.mapNotNull { codebase.findClass(it) }
167             .orEmpty()
168     }
169 
equalsnull170     override fun equals(other: Any?): Boolean {
171         if (this === other) return true
172         return other is PsiSourceFile && file == other.file
173     }
174 
hashCodenull175     override fun hashCode(): Int = file.hashCode()
176 
177     override fun toString(): String = "file ${file.virtualFile?.path}"
178 }
179