1 /*
<lambda>null2  * 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.DefaultModifierList
20 import com.android.tools.metalava.model.FieldItem
21 import com.android.tools.metalava.model.PropertyItem
22 import com.android.tools.metalava.model.TypeItem
23 import com.intellij.psi.PsiMethod
24 import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
25 import org.jetbrains.kotlin.psi.KtPropertyAccessor
26 import org.jetbrains.uast.UAnnotation
27 import org.jetbrains.uast.toUElement
28 
29 class PsiPropertyItem
30 private constructor(
31     codebase: PsiBasedCodebase,
32     private val psiMethod: PsiMethod,
33     containingClass: PsiClassItem,
34     name: String,
35     modifiers: DefaultModifierList,
36     documentation: String,
37     private val fieldType: PsiTypeItem,
38     override val getter: PsiMethodItem,
39     override val setter: PsiMethodItem?,
40     override val constructorParameter: PsiParameterItem?,
41     override val backingField: PsiFieldItem?
42 ) :
43     PsiMemberItem(
44         codebase = codebase,
45         modifiers = modifiers,
46         documentation = documentation,
47         element = psiMethod,
48         containingClass = containingClass,
49         name = name,
50     ),
51     PropertyItem {
52 
53     override fun type(): TypeItem = fieldType
54 
55     override fun psi() = psiMethod
56 
57     override fun equals(other: Any?): Boolean {
58         if (this === other) {
59             return true
60         }
61         return other is FieldItem &&
62             name == other.name() &&
63             containingClass == other.containingClass()
64     }
65 
66     override fun hashCode(): Int {
67         return name.hashCode()
68     }
69 
70     companion object {
71         /**
72          * Creates a new property item, given a [name], [type] and relationships to other items.
73          *
74          * Kotlin's properties consist of up to four other declarations: Their accessor functions,
75          * primary constructor parameter, and a backing field. These relationships are useful for
76          * resolving documentation and exposing the model correctly in Kotlin stubs.
77          *
78          * Metalava currently requires all properties to have a [getter]. It does not currently
79          * support private, `const val`, or [JvmField] properties. Mutable `var` properties usually
80          * have a [setter], but properties with a private default setter may use direct field access
81          * instead.
82          *
83          * Properties declared in the primary constructor of a class have an associated
84          * [constructorParameter]. This relationship is important for resolving docs which may exist
85          * on the constructor parameter.
86          *
87          * Most properties on classes without a custom getter have a [backingField] to hold their
88          * value. This is private except for [JvmField] properties.
89          */
90         internal fun create(
91             codebase: PsiBasedCodebase,
92             containingClass: PsiClassItem,
93             name: String,
94             type: PsiTypeItem,
95             getter: PsiMethodItem,
96             setter: PsiMethodItem? = null,
97             constructorParameter: PsiParameterItem? = null,
98             backingField: PsiFieldItem? = null
99         ): PsiPropertyItem {
100             val psiMethod = getter.psiMethod
101             val documentation =
102                 when (val sourcePsi = getter.sourcePsi) {
103                     is KtPropertyAccessor ->
104                         javadoc(sourcePsi.property, codebase.allowReadingComments)
105                     else -> javadoc(sourcePsi ?: psiMethod, codebase.allowReadingComments)
106                 }
107             val modifiers = modifiers(codebase, psiMethod, documentation)
108             // Alas, annotations whose target is property won't be bound to anywhere in LC/UAST,
109             // if the property doesn't need a backing field. Same for unspecified use-site target.
110             // To preserve such annotations, our last resort is to examine source PSI directly.
111             if (backingField == null) {
112                 val ktProperty = (getter.sourcePsi as? KtPropertyAccessor)?.property
113                 val annotations =
114                     ktProperty?.annotationEntries?.mapNotNull {
115                         val useSiteTarget = it.useSiteTarget?.getAnnotationUseSiteTarget()
116                         if (
117                             useSiteTarget == null ||
118                                 useSiteTarget == AnnotationUseSiteTarget.PROPERTY
119                         ) {
120                             it.toUElement() as? UAnnotation
121                         } else null
122                     }
123                 annotations?.forEach { uAnnotation ->
124                     val annotationItem = UAnnotationItem.create(codebase, uAnnotation)
125                     if (annotationItem !in modifiers.annotations()) {
126                         modifiers.addAnnotation(annotationItem)
127                     }
128                 }
129             }
130             val property =
131                 PsiPropertyItem(
132                     codebase = codebase,
133                     psiMethod = psiMethod,
134                     containingClass = containingClass,
135                     name = name,
136                     documentation = documentation,
137                     modifiers = modifiers,
138                     fieldType = type,
139                     getter = getter,
140                     setter = setter,
141                     constructorParameter = constructorParameter,
142                     backingField = backingField
143                 )
144             getter.property = property
145             setter?.property = property
146             constructorParameter?.property = property
147             backingField?.property = property
148             return property
149         }
150     }
151 }
152