1 /*
<lambda>null2 * 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.AnnotationItem
20 import com.android.tools.metalava.model.AnnotationRetention
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ClassKind
23 import com.android.tools.metalava.model.ClassTypeItem
24 import com.android.tools.metalava.model.ConstructorItem
25 import com.android.tools.metalava.model.DefaultModifierList
26 import com.android.tools.metalava.model.FieldItem
27 import com.android.tools.metalava.model.MethodItem
28 import com.android.tools.metalava.model.PackageItem
29 import com.android.tools.metalava.model.PropertyItem
30 import com.android.tools.metalava.model.SourceFile
31 import com.android.tools.metalava.model.TypeParameterList
32 import com.android.tools.metalava.model.VisibilityLevel
33 import com.android.tools.metalava.model.hasAnnotation
34 import com.android.tools.metalava.model.isRetention
35 import com.android.tools.metalava.model.updateCopiedMethodState
36 import com.intellij.psi.PsiClass
37 import com.intellij.psi.PsiClassType
38 import com.intellij.psi.PsiCompiledFile
39 import com.intellij.psi.PsiMethod
40 import com.intellij.psi.PsiModifier
41 import com.intellij.psi.PsiType
42 import com.intellij.psi.PsiTypeParameter
43 import com.intellij.psi.util.PsiUtil
44 import org.jetbrains.kotlin.psi.KtParameter
45 import org.jetbrains.kotlin.psi.KtProperty
46 import org.jetbrains.kotlin.psi.KtPropertyAccessor
47 import org.jetbrains.kotlin.psi.KtTypeReference
48 import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
49 import org.jetbrains.uast.UClass
50 import org.jetbrains.uast.UFile
51 import org.jetbrains.uast.getParentOfType
52
53 open class PsiClassItem
54 internal constructor(
55 codebase: PsiBasedCodebase,
56 val psiClass: PsiClass,
57 private val name: String,
58 private val fullName: String,
59 private val qualifiedName: String,
60 private val hasImplicitDefaultConstructor: Boolean,
61 override val classKind: ClassKind,
62 override val typeParameterList: TypeParameterList,
63 private val superClassType: ClassTypeItem?,
64 private var interfaceTypes: List<ClassTypeItem>,
65 modifiers: DefaultModifierList,
66 documentation: String,
67 /** True if this class is from the class path (dependencies). Exposed in [isFromClassPath]. */
68 private val fromClassPath: Boolean
69 ) :
70 PsiItem(
71 codebase = codebase,
72 modifiers = modifiers,
73 documentation = documentation,
74 element = psiClass
75 ),
76 ClassItem {
77
78 init {
79 emit = !modifiers.isExpect()
80 }
81
82 lateinit var containingPackage: PsiPackageItem
83
84 override fun containingPackage(): PackageItem =
85 containingClass?.containingPackage() ?: containingPackage
86
87 override fun simpleName(): String = name
88
89 override fun fullName(): String = fullName
90
91 override fun qualifiedName(): String = qualifiedName
92
93 override fun psi() = psiClass
94
95 override fun isFromClassPath(): Boolean = fromClassPath
96
97 override fun hasImplicitDefaultConstructor(): Boolean = hasImplicitDefaultConstructor
98
99 override fun superClass(): ClassItem? = superClassType?.asClass()
100
101 override fun superClassType(): ClassTypeItem? = superClassType
102
103 override var stubConstructor: ConstructorItem? = null
104 override var artifact: String? = null
105
106 private var containingClass: PsiClassItem? = null
107
108 override fun containingClass(): PsiClassItem? = containingClass
109
110 override var hasPrivateConstructor: Boolean = false
111
112 override fun interfaceTypes(): List<ClassTypeItem> = interfaceTypes
113
114 override fun setInterfaceTypes(interfaceTypes: List<ClassTypeItem>) {
115 this.interfaceTypes = interfaceTypes
116 }
117
118 private var allInterfaces: List<ClassItem>? = null
119
120 override fun allInterfaces(): Sequence<ClassItem> {
121 if (allInterfaces == null) {
122 val classes = mutableSetOf<PsiClass>()
123 var curr: PsiClass? = psiClass
124 while (curr != null) {
125 if (curr.isInterface && !classes.contains(curr)) {
126 classes.add(curr)
127 }
128 addInterfaces(classes, curr.interfaces)
129 curr = curr.superClass
130 }
131 val result = mutableListOf<ClassItem>()
132 for (cls in classes) {
133 val item = codebase.findOrCreateClass(cls)
134 result.add(item)
135 }
136
137 allInterfaces = result
138 }
139
140 return allInterfaces!!.asSequence()
141 }
142
143 private fun addInterfaces(result: MutableSet<PsiClass>, interfaces: Array<out PsiClass>) {
144 for (itf in interfaces) {
145 if (itf.isInterface && !result.contains(itf)) {
146 result.add(itf)
147 addInterfaces(result, itf.interfaces)
148 val superClass = itf.superClass
149 if (superClass != null) {
150 addInterfaces(result, arrayOf(superClass))
151 }
152 }
153 }
154 }
155
156 private lateinit var innerClasses: List<PsiClassItem>
157 private lateinit var constructors: List<PsiConstructorItem>
158 private lateinit var methods: MutableList<PsiMethodItem>
159 private lateinit var properties: List<PsiPropertyItem>
160 private lateinit var fields: List<FieldItem>
161
162 /**
163 * If this item was created by filtering down a different codebase, this temporarily points to
164 * the original item during construction. This is used to let us initialize for example throws
165 * lists later, when all classes in the codebase have been initialized.
166 */
167 internal var source: PsiClassItem? = null
168
169 override fun innerClasses(): List<PsiClassItem> = innerClasses
170
171 override fun constructors(): List<ConstructorItem> = constructors
172
173 override fun methods(): List<PsiMethodItem> = methods
174
175 override fun properties(): List<PropertyItem> = properties
176
177 override fun fields(): List<FieldItem> = fields
178
179 final override var primaryConstructor: PsiConstructorItem? = null
180 private set
181
182 /** Must only be used by [type] to cache its result. */
183 private lateinit var classTypeItem: PsiClassTypeItem
184
185 override fun type(): ClassTypeItem {
186 if (!::classTypeItem.isInitialized) {
187 classTypeItem = codebase.globalTypeItemFactory.getClassTypeForClass(this)
188 }
189 return classTypeItem
190 }
191
192 override fun hasTypeVariables(): Boolean = psiClass.hasTypeParameters()
193
194 override fun getSourceFile(): SourceFile? {
195 if (isInnerClass()) {
196 return null
197 }
198
199 val containingFile = psiClass.containingFile ?: return null
200 if (containingFile is PsiCompiledFile) {
201 return null
202 }
203
204 val uFile =
205 if (psiClass is UClass) {
206 psiClass.getParentOfType(UFile::class.java)
207 } else {
208 null
209 }
210
211 return PsiSourceFile(codebase, containingFile, uFile)
212 }
213
214 override fun equals(other: Any?): Boolean {
215 if (this === other) {
216 return true
217 }
218 return other is ClassItem && qualifiedName == other.qualifiedName()
219 }
220
221 /** Creates a constructor in this class */
222 override fun createDefaultConstructor(): ConstructorItem {
223 return PsiConstructorItem.createDefaultConstructor(codebase, this, psiClass)
224 }
225
226 override fun inheritMethodFromNonApiAncestor(template: MethodItem): MethodItem {
227 val method = template as PsiMethodItem
228 require(method.codebase == codebase) {
229 "Unexpected attempt to copy $method from one codebase (${method.codebase.location}) to another (${codebase.location})"
230 }
231 val newMethod = PsiMethodItem.create(this, method)
232
233 // Remember which class this method was copied from.
234 newMethod.inheritedFrom = template.containingClass()
235
236 newMethod.updateCopiedMethodState()
237
238 return newMethod
239 }
240
241 override fun addMethod(method: MethodItem) {
242 methods.add(method as PsiMethodItem)
243 }
244
245 private var retention: AnnotationRetention? = null
246
247 override fun getRetention(): AnnotationRetention {
248 retention?.let {
249 return it
250 }
251
252 if (!isAnnotationType()) {
253 error("getRetention() should only be called on annotation classes")
254 }
255
256 retention = ClassItem.findRetention(this)
257 return retention!!
258 }
259
260 override fun hashCode(): Int = qualifiedName.hashCode()
261
262 companion object {
263 private fun hasExplicitRetention(
264 modifiers: DefaultModifierList,
265 psiClass: PsiClass,
266 isKotlin: Boolean
267 ): Boolean {
268 if (modifiers.hasAnnotation(AnnotationItem::isRetention)) {
269 return true
270 }
271 if (isKotlin && psiClass is UClass) {
272 // In Kotlin some annotations show up on the Java facade only; for example,
273 // a @DslMarker annotation will imply a runtime annotation which is present
274 // in the java facade, not in the source list of annotations
275 val modifierList = psiClass.modifierList
276 if (
277 modifierList != null &&
278 modifierList.annotations.any { isRetention(it.qualifiedName) }
279 ) {
280 return true
281 }
282 }
283 return false
284 }
285
286 internal fun create(
287 codebase: PsiBasedCodebase,
288 psiClass: PsiClass,
289 containingClassItem: PsiClassItem?,
290 enclosingClassTypeItemFactory: PsiTypeItemFactory,
291 fromClassPath: Boolean,
292 ): PsiClassItem {
293 if (psiClass is PsiTypeParameter) {
294 error(
295 "Must not be called with PsiTypeParameter; use PsiTypeParameterItem.create(...) instead"
296 )
297 }
298 val simpleName = psiClass.name!!
299 val fullName = computeFullClassName(psiClass)
300 val qualifiedName = psiClass.qualifiedName ?: simpleName
301 val hasImplicitDefaultConstructor = hasImplicitDefaultConstructor(psiClass)
302 val classKind = getClassKind(psiClass)
303
304 val commentText = javadoc(psiClass, codebase.allowReadingComments)
305 val modifiers = PsiModifierItem.create(codebase, psiClass, commentText)
306
307 // Create the TypeParameterList for this before wrapping any of the other types used by
308 // it as they may reference a type parameter in the list.
309 val (typeParameterList, classTypeItemFactory) =
310 PsiTypeParameterList.create(
311 codebase,
312 enclosingClassTypeItemFactory,
313 "class $qualifiedName",
314 psiClass
315 )
316
317 val (superClassType, interfaceTypes) =
318 computeSuperTypes(psiClass, classKind, classTypeItemFactory)
319
320 val item =
321 PsiClassItem(
322 codebase = codebase,
323 psiClass = psiClass,
324 name = simpleName,
325 fullName = fullName,
326 qualifiedName = qualifiedName,
327 classKind = classKind,
328 typeParameterList = typeParameterList,
329 superClassType = superClassType,
330 interfaceTypes = interfaceTypes,
331 hasImplicitDefaultConstructor = hasImplicitDefaultConstructor,
332 documentation = commentText,
333 modifiers = modifiers,
334 fromClassPath = fromClassPath,
335 )
336 item.containingClass = containingClassItem
337
338 // Register this class now.
339 codebase.registerClass(item)
340
341 // Construct the children
342 val psiMethods = psiClass.methods
343 val methods: MutableList<PsiMethodItem> = ArrayList(psiMethods.size)
344 val isKotlin = psiClass.isKotlin()
345
346 if (
347 classKind == ClassKind.ANNOTATION_TYPE &&
348 !hasExplicitRetention(modifiers, psiClass, isKotlin)
349 ) {
350 // By policy, include explicit retention policy annotation if missing
351 val defaultRetentionPolicy = AnnotationRetention.getDefault(isKotlin)
352 modifiers.addAnnotation(
353 codebase.createAnnotation(
354 buildString {
355 append('@')
356 append(java.lang.annotation.Retention::class.qualifiedName)
357 append('(')
358 append(java.lang.annotation.RetentionPolicy::class.qualifiedName)
359 append('.')
360 append(defaultRetentionPolicy.name)
361 append(')')
362 },
363 item,
364 )
365 )
366 }
367
368 // create methods
369 val constructors: MutableList<PsiConstructorItem> = ArrayList(5)
370 var hasConstructorWithOnlyOptionalArgs = false
371 var noArgConstructor: PsiConstructorItem? = null
372 for (psiMethod in psiMethods) {
373 if (psiMethod.isConstructor) {
374 val constructor =
375 PsiConstructorItem.create(
376 codebase,
377 item,
378 psiMethod,
379 classTypeItemFactory,
380 )
381 // After KT-13495, "all constructors of `sealed` classes now have `protected`
382 // visibility by default," and (S|U)LC follows that (hence the same in UAST).
383 // However, that change was made to allow more flexible class hierarchy and
384 // nesting. If they're compiled to JVM bytecode, sealed class's ctor is still
385 // technically `private` to block instantiation from outside class hierarchy.
386 // Another synthetic constructor, along with an internal ctor marker, is added
387 // for subclasses of a sealed class. Therefore, from Metalava's perspective,
388 // it is not necessary to track such semantically protected ctor. Here we force
389 // set the visibility to `private` back to ignore it during signature writing.
390 if (item.modifiers.isSealed()) {
391 constructor.modifiers.setVisibilityLevel(VisibilityLevel.PRIVATE)
392 }
393 if (constructor.areAllParametersOptional()) {
394 if (constructor.parameters().isNotEmpty()) {
395 constructors.add(constructor)
396 // uast reported a constructor having only optional arguments, so if we
397 // later find an explicit no-arg constructor, we can skip it because
398 // its existence is implied
399 hasConstructorWithOnlyOptionalArgs = true
400 } else {
401 noArgConstructor = constructor
402 }
403 } else {
404 constructors.add(constructor)
405 }
406 } else if (classKind == ClassKind.ENUM && psiMethod.isSyntheticEnumMethod()) {
407 // skip
408 } else {
409 val method =
410 PsiMethodItem.create(codebase, item, psiMethod, classTypeItemFactory)
411 methods.add(method)
412 }
413 }
414
415 // Add the no-arg constructor back in if no constructors have only optional arguments
416 // or if an all-optional constructor created it as part of @JvmOverloads
417 if (
418 noArgConstructor != null &&
419 (!hasConstructorWithOnlyOptionalArgs ||
420 noArgConstructor.modifiers.isAnnotatedWith("kotlin.jvm.JvmOverloads"))
421 ) {
422 constructors.add(noArgConstructor)
423 }
424
425 // Note that this is dependent on the constructor filtering above. UAST sometimes
426 // reports duplicate primary constructors, e.g.: the implicit no-arg constructor
427 constructors.singleOrNull { it.isPrimary }?.let { item.primaryConstructor = it }
428
429 if (hasImplicitDefaultConstructor) {
430 assert(constructors.isEmpty())
431 constructors.add(
432 PsiConstructorItem.createDefaultConstructor(codebase, item, psiClass)
433 )
434 }
435
436 val fields: MutableList<PsiFieldItem> = mutableListOf()
437 val psiFields = psiClass.fields
438 if (psiFields.isNotEmpty()) {
439 psiFields.asSequence().mapTo(fields) {
440 PsiFieldItem.create(codebase, item, it, classTypeItemFactory)
441 }
442 }
443
444 if (classKind == ClassKind.INTERFACE) {
445 // All members are implicitly public, fields are implicitly static, non-static
446 // methods are abstract
447 // (except in Java 1.9, where they can be private
448 for (method in methods) {
449 if (!method.isPrivate) {
450 method.mutableModifiers().setVisibilityLevel(VisibilityLevel.PUBLIC)
451 }
452 }
453 for (method in fields) {
454 val m = method.mutableModifiers()
455 m.setVisibilityLevel(VisibilityLevel.PUBLIC)
456 m.setStatic(true)
457 }
458 }
459
460 item.constructors = constructors
461 item.methods = methods
462 item.fields = fields
463
464 item.properties = emptyList()
465
466 if (isKotlin && methods.isNotEmpty()) {
467 val getters = mutableMapOf<String, PsiMethodItem>()
468 val setters = mutableMapOf<String, PsiMethodItem>()
469 val backingFields = fields.associateBy { it.name() }
470 val constructorParameters =
471 item.primaryConstructor
472 ?.parameters()
473 ?.filter { (it.sourcePsi as? KtParameter)?.isPropertyParameter() ?: false }
474 ?.associateBy { it.name() }
475 .orEmpty()
476
477 for (method in methods) {
478 if (method.isKotlinProperty()) {
479 val name =
480 when (val sourcePsi = method.sourcePsi) {
481 is KtProperty -> sourcePsi.name
482 is KtPropertyAccessor -> sourcePsi.property.name
483 is KtParameter -> sourcePsi.name
484 else -> null
485 }
486 ?: continue
487
488 if (method.parameters().isEmpty()) {
489 if (!method.name().startsWith("component")) {
490 getters[name] = method
491 }
492 } else {
493 setters[name] = method
494 }
495 }
496 }
497
498 val properties = mutableListOf<PsiPropertyItem>()
499 for ((name, getter) in getters) {
500 val type = getter.returnType() as? PsiTypeItem ?: continue
501 properties +=
502 PsiPropertyItem.create(
503 codebase = codebase,
504 containingClass = item,
505 name = name,
506 type = type,
507 getter = getter,
508 setter = setters[name],
509 constructorParameter = constructorParameters[name],
510 backingField = backingFields[name]
511 )
512 }
513 item.properties = properties
514 }
515
516 val psiInnerClasses = psiClass.innerClasses
517 item.innerClasses =
518 if (psiInnerClasses.isEmpty()) {
519 emptyList()
520 } else {
521 val result =
522 psiInnerClasses
523 .asSequence()
524 .map {
525 codebase.createClass(
526 psiClass = it,
527 containingClassItem = item,
528 enclosingClassTypeItemFactory = classTypeItemFactory
529 )
530 }
531 .toMutableList()
532 result
533 }
534
535 return item
536 }
537
538 /**
539 * Compute the super types for the class.
540 *
541 * Returns a pair of the optional super class type and the possibly empty list of interface
542 * types.
543 */
544 private fun computeSuperTypes(
545 psiClass: PsiClass,
546 classKind: ClassKind,
547 classTypeItemFactory: PsiTypeItemFactory
548 ): Pair<ClassTypeItem?, List<ClassTypeItem>> {
549
550 // A map from the qualified type name to the corresponding [KtTypeReference]. This is
551 // empty for non-Kotlin code, otherwise it maps from the qualified type name of a
552 // super type to the associated [KtTypeReference]. The qualified name is used to map
553 // between them because Kotlin does not differentiate between `implements` and `extends`
554 // lists and just has one super type list. The qualified name is safe because a class
555 // cannot implement/extend the same generic type multiple times with different type
556 // arguments so the qualified name should be unique among the super type list.
557 // The [KtTypeReference] is needed to access the type nullability of the generic type
558 // arguments.
559 val qualifiedNameToKt =
560 if (psiClass is UClass) {
561 psiClass.uastSuperTypes.associateBy({ it.getQualifiedName() }) {
562 it.sourcePsi as KtTypeReference
563 }
564 } else emptyMap()
565
566 // Get the [KtTypeReference], if any, associated with ths [PsiType] which must be a
567 // [PsiClassType] as that is the only type allowed in an extends/implements list.
568 fun PsiType.ktTypeReference(): KtTypeReference? {
569 val qualifiedName = (this as PsiClassType).computeQualifiedName()
570 return qualifiedNameToKt[qualifiedName]
571 }
572
573 // Construct the super class type if needed and available.
574 val superClassType =
575 if (classKind != ClassKind.INTERFACE) {
576 val superClassPsiType = psiClass.superClassType as? PsiType
577 superClassPsiType?.let { superClassType ->
578 val ktTypeRef = superClassType.ktTypeReference()
579 classTypeItemFactory.getSuperClassType(
580 PsiTypeInfo(superClassType, ktTypeRef)
581 )
582 }
583 } else null
584
585 // Get the interfaces from the appropriate list.
586 val interfaces =
587 if (classKind == ClassKind.INTERFACE || classKind == ClassKind.ANNOTATION_TYPE) {
588 // An interface uses "extends <interfaces>", either explicitly for normal
589 // interfaces or implicitly for annotations.
590 psiClass.extendsListTypes
591 } else {
592 // A class uses "extends <interfaces>".
593 psiClass.implementsListTypes
594 }
595
596 // Map them to PsiTypeItems.
597 val interfaceTypes =
598 interfaces.map { interfaceType ->
599 val ktTypeRef = interfaceType.ktTypeReference()
600 classTypeItemFactory.getInterfaceType(PsiTypeInfo(interfaceType, ktTypeRef))
601 }
602 return Pair(superClassType, interfaceTypes)
603 }
604
605 private fun getClassKind(psiClass: PsiClass): ClassKind {
606 return when {
607 psiClass.isAnnotationType -> ClassKind.ANNOTATION_TYPE
608 psiClass.isInterface -> ClassKind.INTERFACE
609 psiClass.isEnum -> ClassKind.ENUM
610 psiClass is PsiTypeParameter ->
611 error("Must not call this with a PsiTypeParameter - $psiClass")
612 else -> ClassKind.CLASS
613 }
614 }
615
616 /**
617 * Computes the "full" class name; this is not the qualified class name (e.g. with package)
618 * but for an inner class it includes all the outer classes
619 */
620 fun computeFullClassName(cls: PsiClass): String {
621 if (cls.containingClass == null) {
622 val name = cls.name
623 return name!!
624 } else {
625 val list = mutableListOf<String>()
626 var curr: PsiClass? = cls
627 while (curr != null) {
628 val name = curr.name
629 curr =
630 if (name != null) {
631 list.add(name)
632 curr.containingClass
633 } else {
634 break
635 }
636 }
637 return list.asReversed().joinToString(separator = ".") { it }
638 }
639 }
640
641 private fun hasImplicitDefaultConstructor(psiClass: PsiClass): Boolean {
642 if (psiClass.name?.startsWith("-") == true) {
643 // Deliberately hidden; see examples like
644 // @file:JvmName("-ViewModelExtensions") // Hide from Java sources in the IDE.
645 return false
646 }
647 if (psiClass is UClass && psiClass.sourcePsi == null) {
648 // Top level kt classes (FooKt for Foo.kt) do not have implicit default constructor
649 return false
650 }
651
652 val constructors = psiClass.constructors
653 if (
654 constructors.isEmpty() &&
655 !psiClass.isInterface &&
656 !psiClass.isAnnotationType &&
657 !psiClass.isEnum
658 ) {
659 if (PsiUtil.hasDefaultConstructor(psiClass)) {
660 return true
661 }
662
663 // The above method isn't always right; for example, for the
664 // ContactsContract.Presence class
665 // in the framework, which looks like this:
666 // @Deprecated
667 // public static final class Presence extends StatusUpdates {
668 // }
669 // javac makes a default constructor:
670 // public final class android.provider.ContactsContract$Presence extends
671 // android.provider.ContactsContract$StatusUpdates {
672 // public android.provider.ContactsContract$Presence();
673 // }
674 // but the above method returns false. So add some of our own heuristics:
675 if (
676 psiClass.hasModifierProperty(PsiModifier.FINAL) &&
677 !psiClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
678 psiClass.hasModifierProperty(PsiModifier.PUBLIC)
679 ) {
680 return true
681 }
682 }
683
684 return false
685 }
686 }
687 }
688
689 /**
690 * Check whether the method is a synthetic enum method.
691 *
692 * i.e. `getEntries()` from Kotlin and `values()` and `valueOf(String)` from both Java and Kotlin.
693 */
PsiMethodnull694 private fun PsiMethod.isSyntheticEnumMethod(): Boolean {
695 if (containingClass?.isEnum != true) return false
696 val parameterCount = parameterList.parametersCount
697 return (parameterCount == 0 && (name == "values" || name == "getEntries")) ||
698 (parameterCount == 1 &&
699 name == "valueOf" &&
700 (parameterList.parameters[0].type as? PsiClassType)?.computeQualifiedName() ==
701 "java.lang.String")
702 }
703