use PsiFileGist instead of time-expensive indices for contract inference
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / dataFlow / ContractInferenceIndex.kt
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
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 package com.intellij.codeInspection.dataFlow
17
18 import com.intellij.lang.LighterAST
19 import com.intellij.lang.LighterASTNode
20 import com.intellij.psi.PsiMethod
21 import com.intellij.psi.impl.source.JavaLightStubBuilder
22 import com.intellij.psi.impl.source.PsiFileImpl
23 import com.intellij.psi.impl.source.PsiMethodImpl
24 import com.intellij.psi.impl.source.tree.JavaElementType
25 import com.intellij.psi.impl.source.tree.JavaElementType.*
26 import com.intellij.psi.impl.source.tree.LightTreeUtil
27 import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor
28 import com.intellij.util.gist.GistManager
29 import java.util.*
30
31 /**
32  * @author peter
33  */
34
35 private val gist = GistManager.getInstance().newPsiFileGist("contractInference", 0, MethodDataExternalizer) { file ->
36   indexFile(file.node.lighterAST)
37 }
38
39 private fun indexFile(tree: LighterAST): Map<Int, MethodData> {
40   val result = HashMap<Int, MethodData>()
41
42   object : RecursiveLighterASTNodeWalkingVisitor(tree) {
43     var methodIndex = 0
44
45     override fun visitNode(element: LighterASTNode) {
46       if (element.tokenType === METHOD) {
47         calcData(tree, element)?.let { data -> result[methodIndex] = data }
48         methodIndex++
49       }
50
51       if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
52
53       super.visitNode(element)
54     }
55   }.visitNode(tree.root)
56
57   return result
58 }
59
60 private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
61   val body = LightTreeUtil.firstChildOfType(tree, method, CODE_BLOCK) ?: return null
62   val statements = ContractInferenceInterpreter.getStatements(body, tree)
63
64   val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements)
65
66   val nullityVisitor = NullityInference.NullityInferenceVisitor(tree, body)
67   val purityVisitor = PurityInference.PurityInferenceVisitor(tree, body)
68   for (statement in statements) {
69     walkMethodBody(tree, statement) { nullityVisitor.visitNode(it); purityVisitor.visitNode(it) }
70   }
71
72   return createData(body, contracts, nullityVisitor.result, purityVisitor.result)
73 }
74
75 private fun walkMethodBody(tree: LighterAST, root: LighterASTNode, processor: (LighterASTNode) -> Unit) {
76   object : RecursiveLighterASTNodeWalkingVisitor(tree) {
77     override fun visitNode(element: LighterASTNode) {
78       val type = element.tokenType
79       if (type === CLASS || type === FIELD || type == METHOD || type == ANNOTATION_METHOD || type === LAMBDA_EXPRESSION) return
80
81       processor(element)
82       super.visitNode(element)
83     }
84   }.visitNode(root)
85 }
86
87 private fun createData(body: LighterASTNode,
88                        contracts: List<PreContract>,
89                        nullity: NullityInferenceResult?,
90                        purity: PurityInferenceResult?): MethodData? {
91   if (nullity == null && purity == null && !contracts.isNotEmpty()) return null
92
93   return MethodData(nullity, purity, contracts, body.startOffset, body.endOffset)
94 }
95
96 fun getIndexedData(method: PsiMethod): MethodData? {
97   if (method !is PsiMethodImpl || !InferenceFromSourceUtil.shouldInferFromSource(method)) return null
98
99   return gist.getFileData(method.containingFile)?.get(methodIndex(method))
100 }
101
102 private fun methodIndex(method: PsiMethodImpl): Int {
103   val file = method.containingFile as PsiFileImpl
104   val stubTree = file.stubTree ?: file.calcStubTree()
105   return stubTree.plainList.filter { it.stubType == JavaElementType.METHOD }.map { it.psi }.indexOf(method)
106 }