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