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