2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInspection.dataFlow
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
44 private val INDEX_ID = ID.create<Int, MethodData>("java.inferred.contracts")
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
53 override fun getInputFilter() = FileBasedIndex.InputFilter {
54 it.fileType == JavaFileType.INSTANCE && JavaFileElementType.isInSourceContent(it)
57 override fun getIndexer() = DataIndexer<Int, MethodData, FileContent> { fc ->
58 val result = HashMap<Int, MethodData>()
60 val tree = (fc as FileContentImpl).lighterASTForPsiDependentIndex
61 object : RecursiveLighterASTNodeWalkingVisitor(tree) {
64 override fun visitNode(element: LighterASTNode) {
65 if (element.tokenType === JavaElementType.METHOD) {
66 calcData(tree, element)?.let { data -> result[methodIndex] = data }
70 if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
72 super.visitNode(element)
74 }.visitNode(tree.root)
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)
85 val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements)
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) }
93 return createData(body, contracts, nullityVisitor.result, purityVisitor.result)
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 === FIELD || type == METHOD || type == ANNOTATION_METHOD || type === LAMBDA_EXPRESSION) return
103 super.visitNode(element)
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
114 return MethodData(nullity, purity, contracts, body.startOffset, body.endOffset)
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)
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)
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)
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)