Merge remote-tracking branch 'origin/master'
[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.ide.highlighter.JavaFileType
19 import com.intellij.lang.LighterAST
20 import com.intellij.lang.LighterASTNode
21 import com.intellij.lang.TreeBackedLighterAST
22 import com.intellij.openapi.util.Ref
23 import com.intellij.psi.PsiMethod
24 import com.intellij.psi.impl.source.JavaFileElementType
25 import com.intellij.psi.impl.source.JavaLightStubBuilder
26 import com.intellij.psi.impl.source.PsiFileImpl
27 import com.intellij.psi.impl.source.PsiMethodImpl
28 import com.intellij.psi.impl.source.tree.JavaElementType
29 import com.intellij.psi.impl.source.tree.JavaElementType.*
30 import com.intellij.psi.impl.source.tree.LightTreeUtil
31 import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor
32 import com.intellij.psi.search.GlobalSearchScope
33 import com.intellij.psi.util.CachedValueProvider
34 import com.intellij.psi.util.CachedValuesManager
35 import com.intellij.util.indexing.*
36 import com.intellij.util.io.DataExternalizer
37 import com.intellij.util.io.IntInlineKeyDescriptor
38 import java.util.*
39
40 /**
41  * @author peter
42  */
43
44 private val INDEX_ID = ID.create<Int, MethodData>("java.inferred.contracts")
45
46 class ContractInferenceIndex : FileBasedIndexExtension<Int, MethodData>(), PsiDependentIndex {
47   override fun getName() = INDEX_ID
48   override fun getVersion() = 0
49   override fun dependsOnFileContent() = true
50   override fun getKeyDescriptor() = IntInlineKeyDescriptor()
51   override fun getValueExternalizer(): DataExternalizer<MethodData> = MethodDataExternalizer
52
53   override fun getInputFilter() = FileBasedIndex.InputFilter {
54     it.fileType == JavaFileType.INSTANCE && JavaFileElementType.isInSourceContent(it)
55   }
56
57   override fun getIndexer() = DataIndexer<Int, MethodData, FileContent> { fc ->
58     val result = HashMap<Int, MethodData>()
59
60     val tree = (fc as FileContentImpl).lighterASTForPsiDependentIndex
61     object : RecursiveLighterASTNodeWalkingVisitor(tree) {
62       var methodIndex = 0
63
64       override fun visitNode(element: LighterASTNode) {
65         if (element.tokenType === JavaElementType.METHOD) {
66           calcData(tree, element)?.let { data -> result[methodIndex] = data }
67           methodIndex++
68         }
69
70         if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
71
72         super.visitNode(element)
73       }
74     }.visitNode(tree.root)
75
76     result
77   }
78
79 }
80
81 private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
82   val body = LightTreeUtil.firstChildOfType(tree, method, CODE_BLOCK) ?: return null
83   val statements = ContractInferenceInterpreter.getStatements(body, tree)
84
85   val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements)
86
87   val nullityVisitor = NullityInference.NullityInferenceVisitor(tree, body)
88   val purityVisitor = PurityInference.PurityInferenceVisitor(tree, body)
89   for (statement in statements) {
90     walkMethodBody(tree, statement) { nullityVisitor.visitNode(it); purityVisitor.visitNode(it) }
91   }
92
93   return createData(body, contracts, nullityVisitor.result, purityVisitor.result)
94 }
95
96 private fun walkMethodBody(tree: LighterAST, root: LighterASTNode, processor: (LighterASTNode) -> Unit) {
97   object : RecursiveLighterASTNodeWalkingVisitor(tree) {
98     override fun visitNode(element: LighterASTNode) {
99       val type = element.tokenType
100       if (type === CLASS || type === ANONYMOUS_CLASS || type === LAMBDA_EXPRESSION) return
101
102       processor(element)
103       super.visitNode(element)
104     }
105   }.visitNode(root)
106 }
107
108 private fun createData(body: LighterASTNode,
109                        contracts: List<PreContract>,
110                        nullity: NullityInferenceResult?,
111                        purity: PurityInferenceResult?): MethodData? {
112   if (nullity == null && purity == null && !contracts.isNotEmpty()) return null
113
114   return MethodData(nullity, purity, contracts, body.startOffset, body.endOffset)
115 }
116
117 fun getIndexedData(method: PsiMethod): MethodData? {
118   if (method !is PsiMethodImpl || !InferenceFromSourceUtil.shouldInferFromSource(method)) return null
119   val vFile = method.containingFile.virtualFile ?: return calcNonPhysicalMethodData(method)
120
121   val ref = Ref<MethodData>()
122   val scope = GlobalSearchScope.fileScope(method.project, vFile)
123   FileBasedIndex.getInstance().processValues(INDEX_ID, methodIndex(method), vFile, { file, data -> ref.set(data); true }, scope)
124   return ref.get()
125 }
126
127 private fun methodIndex(method: PsiMethodImpl): Int {
128   val file = method.containingFile as PsiFileImpl
129   val stubTree = file.stubTree ?: file.calcStubTree()
130   return stubTree.plainList.filter { it.stubType == JavaElementType.METHOD }.map { it.psi }.indexOf(method)
131 }
132
133 private fun calcNonPhysicalMethodData(method: PsiMethodImpl): MethodData? {
134   return CachedValuesManager.getCachedValue(method) {
135     CachedValueProvider.Result(calcData(method.containingFile.node.lighterAST, TreeBackedLighterAST.wrap(method.node)), method)
136   }
137 }