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.ClassItem
20 import com.android.tools.metalava.model.DefaultModifierList
21 import com.android.tools.metalava.model.FieldItem
22 import com.android.tools.metalava.model.TypeItem
23 import com.android.tools.metalava.model.TypeNullability
24 import com.android.tools.metalava.model.isNonNullAnnotation
25 import com.intellij.psi.PsiCallExpression
26 import com.intellij.psi.PsiClassType
27 import com.intellij.psi.PsiEnumConstant
28 import com.intellij.psi.PsiField
29 import com.intellij.psi.PsiModifierListOwner
30 import com.intellij.psi.PsiPrimitiveType
31 import com.intellij.psi.PsiReference
32 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
33 
34 class PsiFieldItem(
35     codebase: PsiBasedCodebase,
36     private val psiField: PsiField,
37     containingClass: PsiClassItem,
38     name: String,
39     modifiers: DefaultModifierList,
40     documentation: String,
41     private val fieldType: TypeItem,
42     private val isEnumConstant: Boolean,
43     private val fieldValue: PsiFieldValue?,
44 ) :
45     PsiMemberItem(
46         codebase = codebase,
47         modifiers = modifiers,
48         documentation = documentation,
49         element = psiField,
50         containingClass = containingClass,
51         name = name,
52     ),
53     FieldItem {
54 
55     override var property: PsiPropertyItem? = null
56 
typenull57     override fun type(): TypeItem = fieldType
58 
59     override fun initialValue(requireConstant: Boolean): Any? {
60         return fieldValue?.initialValue(requireConstant)
61     }
62 
isEnumConstantnull63     override fun isEnumConstant(): Boolean = isEnumConstant
64 
65     override fun psi(): PsiField = psiField
66 
67     override fun duplicate(targetContainingClass: ClassItem): PsiFieldItem {
68         val duplicated =
69             create(
70                 codebase,
71                 targetContainingClass as PsiClassItem,
72                 psiField,
73                 codebase.globalTypeItemFactory.from(targetContainingClass),
74             )
75         duplicated.inheritedFrom = containingClass
76 
77         // Preserve flags that may have been inherited (propagated) from surrounding packages
78         if (targetContainingClass.hidden) {
79             duplicated.hidden = true
80         }
81         if (targetContainingClass.removed) {
82             duplicated.removed = true
83         }
84         if (targetContainingClass.docOnly) {
85             duplicated.docOnly = true
86         }
87 
88         return duplicated
89     }
90 
91     override var inheritedFrom: ClassItem? = null
92 
equalsnull93     override fun equals(other: Any?): Boolean {
94         if (this === other) {
95             return true
96         }
97         return other is FieldItem &&
98             name == other.name() &&
99             containingClass == other.containingClass()
100     }
101 
hashCodenull102     override fun hashCode(): Int {
103         return name.hashCode()
104     }
105 
106     companion object {
createnull107         internal fun create(
108             codebase: PsiBasedCodebase,
109             containingClass: PsiClassItem,
110             psiField: PsiField,
111             enclosingClassTypeItemFactory: PsiTypeItemFactory,
112         ): PsiFieldItem {
113             val name = psiField.name
114             val commentText = javadoc(psiField, codebase.allowReadingComments)
115             val modifiers = modifiers(codebase, psiField, commentText)
116 
117             val isEnumConstant = psiField is PsiEnumConstant
118 
119             // Wrap the PsiField in a PsiFieldValue that can provide the field's initial value.
120             val fieldValue = PsiFieldValue(psiField)
121 
122             // Create a type for the field, taking into account the modifiers, whether it is an
123             // enum constant and whether the field's initial value is non-null.
124             val fieldType =
125                 enclosingClassTypeItemFactory.getFieldType(
126                     underlyingType = PsiTypeInfo(psiField.type, psiField),
127                     itemAnnotations = modifiers.annotations(),
128                     isEnumConstant = isEnumConstant,
129                     isFinal = modifiers.isFinal(),
130                     isInitialValueNonNull = {
131                         // The initial value is non-null if the field initializer is a method that
132                         // is annotated as being non-null so would produce a non-null value, or the
133                         // value is a literal which is not null.
134                         psiField.isFieldInitializerNonNull() ||
135                             fieldValue.initialValue(false) != null
136                     },
137                 )
138 
139             return PsiFieldItem(
140                 codebase = codebase,
141                 psiField = psiField,
142                 containingClass = containingClass,
143                 name = name,
144                 documentation = commentText,
145                 modifiers = modifiers,
146                 fieldType = fieldType,
147                 isEnumConstant = isEnumConstant,
148                 fieldValue = fieldValue
149             )
150         }
151     }
152 }
153 
154 /**
155  * Check to see whether the [PsiField] on which this is called has an initializer whose
156  * [TypeNullability] is known to be [TypeNullability.NONNULL].
157  */
PsiFieldnull158 private fun PsiField.isFieldInitializerNonNull(): Boolean {
159     // If we're looking at a final field, look on the right hand side of the field to the
160     // field initialization. If that right hand side for example represents a method call,
161     // and the method we're calling is annotated with @NonNull, then the field (since it is
162     // final) will always be @NonNull as well.
163     val resolved =
164         when (val initializer = initializer) {
165             is PsiReference -> {
166                 initializer.resolve()
167             }
168             is PsiCallExpression -> {
169                 initializer.resolveMethod()
170             }
171             else -> null
172         }
173             ?: return false
174 
175     return resolved is PsiModifierListOwner &&
176         resolved.annotations.any { isNonNullAnnotation(it.qualifiedName ?: "") }
177 }
178 
179 /**
180  * Wrapper around a [PsiField] that will provide access to the initial value of the field, if
181  * available, or `null` otherwise.
182  */
183 class PsiFieldValue(private val psiField: PsiField) {
184 
initialValuenull185     fun initialValue(requireConstant: Boolean): Any? {
186         val constant = psiField.computeConstantValue()
187         // Offset [ClsFieldImpl#computeConstantValue] for [TYPE] field in boxed primitive types.
188         // Those fields hold [Class] object, but the constant value should not be of [PsiType].
189         if (
190             constant is PsiPrimitiveType &&
191                 psiField.name == "TYPE" &&
192                 (psiField.type as? PsiClassType)?.computeQualifiedName() == "java.lang.Class"
193         ) {
194             return null
195         }
196         if (constant != null) {
197             return constant
198         }
199 
200         return if (!requireConstant) {
201             val initializer = psiField.initializer ?: return null
202             JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false)
203         } else {
204             null
205         }
206     }
207 }
208