• 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.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