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.psi 18 19 import com.android.tools.metalava.model.CallableItem 20 import com.android.tools.metalava.model.ClassItem 21 import com.android.tools.metalava.model.ClassOrigin 22 import com.android.tools.metalava.model.Codebase 23 import com.android.tools.metalava.model.FieldItem 24 import com.android.tools.metalava.model.Item 25 import com.android.tools.metalava.model.item.DefaultCodebase 26 import com.intellij.openapi.project.Project 27 import com.intellij.psi.PsiClass 28 import com.intellij.psi.PsiField 29 import com.intellij.psi.PsiMethod 30 import java.io.File 31 import org.jetbrains.uast.UMethod 32 33 const val METHOD_ESTIMATE = 1000 34 35 /** 36 * A codebase containing Java, Kotlin, or UAST PSI classes 37 * 38 * After creation, a list of PSI file is passed to [PsiCodebaseAssembler.initializeFromSources] or a 39 * JAR file is passed to [PsiCodebaseAssembler.initializeFromJar]. This creates package and class 40 * items along with their members. Any classes defined in those files will have [ClassItem.origin] 41 * set based on [fromClasspath]. 42 * 43 * Classes that are created through [findOrCreateClass] will have [ClassItem.origin] set to 44 * [ClassOrigin.SOURCE_PATH] or [ClassOrigin.CLASS_PATH] depending on whether the class is defined 45 * on the source path or on the class path respectively. 46 */ 47 internal class PsiBasedCodebase( 48 location: File, 49 description: String = "Unknown", 50 config: Codebase.Config, 51 val allowReadingComments: Boolean, 52 val fromClasspath: Boolean = false, 53 assembler: PsiCodebaseAssembler, 54 val isMultiplatform: Boolean, 55 ) : 56 DefaultCodebase( 57 location = location, 58 description = description, 59 preFiltered = false, 60 config = config, 61 trustedApi = false, 62 supportsDocumentation = true, 63 assembler = assembler, 64 ) { 65 66 internal val psiAssembler = assembler 67 68 internal val project: Project 69 get() = psiAssembler.project 70 71 /** 72 * Printer which can convert PSI, UAST and constants into source code, with ability to filter 73 * out elements that are not part of a codebase etc 74 */ 75 internal val printer = CodePrinter(this, reporter) 76 77 /** 78 * Map from classes to the set of callables for each (but only for classes where we've called 79 * [findCallableByPsiMethod] 80 */ 81 private val methodMap: MutableMap<ClassItem, MutableMap<PsiMethod, PsiCallableItem>> = 82 HashMap(METHOD_ESTIMATE) 83 84 /** [PsiTypeItemFactory] used to create [PsiTypeItem]s. */ 85 internal val globalTypeItemFactory 86 get() = psiAssembler.globalTypeItemFactory 87 disposenull88 override fun dispose() { 89 psiAssembler.dispose() 90 super.dispose() 91 } 92 findClassnull93 fun findClass(psiClass: PsiClass): ClassItem? { 94 val qualifiedName: String = psiClass.classQualifiedName 95 return findClass(qualifiedName) 96 } 97 findOrCreateClassnull98 internal fun findOrCreateClass(psiClass: PsiClass) = psiAssembler.findOrCreateClass(psiClass) 99 100 internal fun findCallableByPsiMethod(method: PsiMethod): PsiCallableItem { 101 val containingClass = method.containingClass 102 val cls = findOrCreateClass(containingClass!!) 103 104 // Ensure initialized/registered via [#registerMethods] 105 if (methodMap[cls] == null) { 106 val map = HashMap<PsiMethod, PsiCallableItem>(40) 107 registerCallablesByPsiMethod(cls.methods(), map) 108 registerCallablesByPsiMethod(cls.constructors(), map) 109 methodMap[cls] = map 110 } 111 112 val methods = methodMap[cls]!! 113 val methodItem = methods[method] 114 if (methodItem == null) { 115 // Probably switched psi classes (e.g. used source PsiClass in registry but found 116 // duplicate class in .jar library, and we're now pointing to it; in that case, find the 117 // equivalent method by signature 118 val psiClass = (cls as PsiClassItem).psiClass 119 val updatedMethod = psiClass.findMethodBySignature(method, true) 120 val result = methods[updatedMethod!!] 121 if (result == null) { 122 val extra = 123 PsiMethodItem.create(this, cls, updatedMethod, globalTypeItemFactory.from(cls)) 124 methods[method] = extra 125 methods[updatedMethod] = extra 126 127 return extra 128 } 129 return result 130 } 131 132 return methodItem 133 } 134 findFieldnull135 internal fun findField(field: PsiField): FieldItem? { 136 val containingClass = field.containingClass ?: return null 137 val cls = findOrCreateClass(containingClass) 138 return cls.findField(field.name) 139 } 140 registerCallablesByPsiMethodnull141 private fun registerCallablesByPsiMethod( 142 callables: List<CallableItem>, 143 map: MutableMap<PsiMethod, PsiCallableItem> 144 ) { 145 for (callable in callables) { 146 val psiMethod = (callable as PsiCallableItem).psiMethod 147 map[psiMethod] = callable 148 if (psiMethod is UMethod) { 149 // Register LC method as a key too 150 // so that we can find the corresponding [CallableItem] 151 // Otherwise, we will end up creating a new [CallableItem] 152 // without source PSI, resulting in wrong modifier. 153 map[psiMethod.javaPsi] = callable 154 } 155 } 156 } 157 isFromClassPathnull158 override fun isFromClassPath() = fromClasspath 159 160 override fun createAnnotation(source: String, context: Item?) = 161 psiAssembler.createAnnotation(source, context) 162 } 163