Cleanup: NotNull/Nullable
[idea/community.git] / java / java-indexing-impl / src / com / intellij / psi / impl / JavaSimplePropertyIndex.kt
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.impl
3
4 import com.intellij.ide.highlighter.JavaFileType
5 import com.intellij.lang.LighterASTNode
6 import com.intellij.lang.java.JavaParserDefinition
7 import com.intellij.openapi.diagnostic.Logger
8 import com.intellij.openapi.vfs.VirtualFile
9 import com.intellij.psi.JavaPsiFacade
10 import com.intellij.psi.JavaTokenType
11 import com.intellij.psi.PsiField
12 import com.intellij.psi.impl.cache.RecordUtil
13 import com.intellij.psi.impl.source.JavaLightStubBuilder
14 import com.intellij.psi.impl.source.JavaLightTreeUtil
15 import com.intellij.psi.impl.source.PsiMethodImpl
16 import com.intellij.psi.impl.source.tree.ElementType
17 import com.intellij.psi.impl.source.tree.JavaElementType
18 import com.intellij.psi.impl.source.tree.LightTreeUtil
19 import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor
20 import com.intellij.psi.search.GlobalSearchScope
21 import com.intellij.psi.stub.JavaStubImplUtil
22 import com.intellij.psi.tree.TokenSet
23 import com.intellij.psi.util.PropertyUtil
24 import com.intellij.psi.util.PropertyUtilBase
25 import com.intellij.util.containers.ContainerUtil
26 import com.intellij.util.indexing.*
27 import com.intellij.util.io.DataExternalizer
28 import com.intellij.util.io.EnumeratorIntegerDescriptor
29 import com.intellij.util.io.EnumeratorStringDescriptor
30 import com.intellij.util.io.KeyDescriptor
31 import java.io.DataInput
32 import java.io.DataOutput
33
34 private val indexId = ID.create<Int, PropertyIndexValue>("java.simple.property")
35 private val log = Logger.getInstance(JavaSimplePropertyIndex::class.java)
36
37 fun getFieldOfGetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, true)
38
39 fun getFieldOfSetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, false)
40
41 private fun resolveFieldFromIndexValue(method: PsiMethodImpl, isGetter: Boolean): PsiField? {
42   val id = JavaStubImplUtil.getMethodStubIndex(method)
43   if (id != -1) {
44     val values = FileBasedIndex.getInstance().getValues(indexId, id, GlobalSearchScope.fileScope(method.containingFile))
45     when (values.size) {
46       0 -> return null
47       1 -> {
48         val indexValue = values[0]
49         if (isGetter != indexValue.getter) return null
50         val psiClass = method.containingClass
51         val project = psiClass!!.project
52         val expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(indexValue.propertyRefText, psiClass)
53         return PropertyUtil.getSimplyReturnedField(expr)
54       }
55       else -> {
56         log.error("multiple index values for method $method")
57       }
58     }
59   }
60   return null
61 }
62
63 data class PropertyIndexValue(val propertyRefText: String, val getter: Boolean)
64
65 class JavaSimplePropertyIndex : FileBasedIndexExtension<Int, PropertyIndexValue>(), PsiDependentIndex {
66   private val allowedExpressions by lazy {
67     TokenSet.create(ElementType.REFERENCE_EXPRESSION, ElementType.THIS_EXPRESSION, ElementType.SUPER_EXPRESSION)
68   } 
69   
70   override fun getIndexer(): DataIndexer<Int, PropertyIndexValue, FileContent> = DataIndexer { inputData ->
71     val result = ContainerUtil.newHashMap<Int, PropertyIndexValue>()
72     val tree = (inputData as FileContentImpl).lighterASTForPsiDependentIndex
73
74     object : RecursiveLighterASTNodeWalkingVisitor(tree) {
75       var methodIndex = 0
76
77       override fun visitNode(element: LighterASTNode) {
78         if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
79
80         if (element.tokenType === JavaElementType.METHOD) {
81           extractProperty(element)?.let { result.put(methodIndex, it) }
82           methodIndex++
83         }
84
85         super.visitNode(element)
86       }
87
88       private fun extractProperty(method: LighterASTNode): PropertyIndexValue? {
89         var isConstructor = true
90         var isGetter = true
91
92         var isBooleanReturnType = false
93         var isVoidReturnType = false
94         var setterParameterName: String? = null
95
96         var refText: String? = null
97
98         for (child in tree.getChildren(method)) {
99           when (child.tokenType) {
100             JavaElementType.TYPE -> {
101               val children = tree.getChildren(child)
102               if (children.size != 1) return null
103               val typeElement = children[0]
104               if (typeElement.tokenType == JavaTokenType.VOID_KEYWORD) isVoidReturnType = true
105               if (typeElement.tokenType == JavaTokenType.BOOLEAN_KEYWORD) isBooleanReturnType = true
106               isConstructor = false
107             }
108             JavaElementType.PARAMETER_LIST -> {
109               if (isGetter) {
110                 if (LightTreeUtil.firstChildOfType(tree, child, JavaElementType.PARAMETER) != null) return null
111               } else {
112                 val parameters = LightTreeUtil.getChildrenOfType(tree, child, JavaElementType.PARAMETER)
113                 if (parameters.size != 1) return null
114                 setterParameterName = JavaLightTreeUtil.getNameIdentifierText(tree, parameters[0])
115                 if (setterParameterName == null) return null
116               }
117             }
118             JavaElementType.CODE_BLOCK -> {
119               refText = if (isGetter) getGetterPropertyRefText(child) else getSetterPropertyRefText(child, setterParameterName!!)
120               if (refText == null) return null
121             }
122             JavaTokenType.IDENTIFIER -> {
123               if (isConstructor) return null
124               val name = RecordUtil.intern(tree.charTable, child)
125               val flavour = PropertyUtil.getMethodNameGetterFlavour(name)
126               when (flavour) {
127                 PropertyUtilBase.GetterFlavour.NOT_A_GETTER -> {
128                   if (PropertyUtil.isSetterName(name)) {
129                     isGetter = false
130                   }
131                   else {
132                     return null
133                   }
134                 }
135                 PropertyUtilBase.GetterFlavour.BOOLEAN -> if (!isBooleanReturnType) return null
136                 else -> { }
137               }
138               if (isVoidReturnType && isGetter) return null
139             }
140           }
141         }
142
143         return refText?.let { PropertyIndexValue(it, isGetter) }
144       }
145
146       private fun getSetterPropertyRefText(codeBlock: LighterASTNode, setterParameterName: String): String? {
147         val assignment = tree
148           .getChildren(codeBlock)
149           .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) }
150           ?.takeIf { it.tokenType == JavaElementType.EXPRESSION_STATEMENT }
151           ?.let { LightTreeUtil.firstChildOfType(tree, it, JavaElementType.ASSIGNMENT_EXPRESSION) }
152         if (assignment == null || LightTreeUtil.firstChildOfType(tree, assignment, JavaTokenType.EQ) == null) return null
153         val operands = LightTreeUtil.getChildrenOfType(tree, assignment, ElementType.EXPRESSION_BIT_SET)
154         if (operands.size != 2 || LightTreeUtil.toFilteredString(tree, operands[1], null) != setterParameterName) return null
155         val lhsText = LightTreeUtil.toFilteredString(tree, operands[0], null)
156         if (lhsText == setterParameterName) return null
157         return lhsText
158       }
159
160       private fun getGetterPropertyRefText(codeBlock: LighterASTNode): String? {
161         return tree
162           .getChildren(codeBlock)
163           .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) }
164           ?.takeIf { it.tokenType == JavaElementType.RETURN_STATEMENT}
165           ?.let { LightTreeUtil.firstChildOfType(tree, it, allowedExpressions) }
166           ?.takeIf(this::checkQualifiers)
167           ?.let { LightTreeUtil.toFilteredString(tree, it, null) }
168       }
169
170       private fun checkQualifiers(expression: LighterASTNode): Boolean {
171         if (!allowedExpressions.contains(expression.tokenType)) {
172           return false
173         }
174         val qualifier = JavaLightTreeUtil.findExpressionChild(tree, expression)
175         return qualifier == null || checkQualifiers(qualifier)
176       }
177     }.visitNode(tree.root)
178     result
179   }
180
181   override fun getKeyDescriptor(): KeyDescriptor<Int> = EnumeratorIntegerDescriptor.INSTANCE
182
183   override fun getValueExternalizer(): DataExternalizer<PropertyIndexValue> = object: DataExternalizer<PropertyIndexValue> {
184     override fun save(out: DataOutput, value: PropertyIndexValue?) {
185       value!!
186       EnumeratorStringDescriptor.INSTANCE.save(out, value.propertyRefText)
187       out.writeBoolean(value.getter)
188     }
189
190     override fun read(input: DataInput): PropertyIndexValue = PropertyIndexValue(EnumeratorStringDescriptor.INSTANCE.read(input), input.readBoolean())
191   }
192
193   override fun getName(): ID<Int, PropertyIndexValue> = indexId
194
195   override fun getInputFilter(): FileBasedIndex.InputFilter = object : DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE) {
196     override fun acceptInput(file: VirtualFile): Boolean = JavaParserDefinition.JAVA_FILE.shouldBuildStubFor(file)
197   }
198
199   override fun dependsOnFileContent(): Boolean = true
200
201   override fun getVersion(): Int = 1
202 }