diff --git a/java/java-indexing-impl/src/META-INF/JavaIndexingPlugin.xml b/java/java-indexing-impl/src/META-INF/JavaIndexingPlugin.xml index c81d8a13264b..bd92247b5fb5 100644 --- a/java/java-indexing-impl/src/META-INF/JavaIndexingPlugin.xml +++ b/java/java-indexing-impl/src/META-INF/JavaIndexingPlugin.xml @@ -35,7 +35,6 @@ - diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyGist.kt b/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyGist.kt new file mode 100644 index 000000000000..3884d3003eaf --- /dev/null +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyGist.kt @@ -0,0 +1,201 @@ +// Copyright 2000-2019 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. +package com.intellij.psi.impl + +import com.google.common.annotations.VisibleForTesting +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.LighterAST +import com.intellij.lang.LighterASTNode +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiField +import com.intellij.psi.impl.cache.RecordUtil +import com.intellij.psi.impl.source.JavaLightStubBuilder +import com.intellij.psi.impl.source.JavaLightTreeUtil +import com.intellij.psi.impl.source.PsiMethodImpl +import com.intellij.psi.impl.source.tree.ElementType +import com.intellij.psi.impl.source.tree.JavaElementType +import com.intellij.psi.impl.source.tree.LightTreeUtil +import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor +import com.intellij.psi.stub.JavaStubImplUtil +import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PropertyUtil +import com.intellij.psi.util.PropertyUtilBase +import com.intellij.util.gist.GistManager +import com.intellij.util.io.DataExternalizer +import com.intellij.util.io.DataInputOutputUtil +import com.intellij.util.io.EnumeratorStringDescriptor +import gnu.trove.TIntObjectHashMap +import java.io.DataInput +import java.io.DataOutput + +fun getFieldOfGetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, true) + +fun getFieldOfSetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, false) + +private fun resolveFieldFromIndexValue(method: PsiMethodImpl, isGetter: Boolean): PsiField? { + val file = method.containingFile + if (file.fileType != JavaFileType.INSTANCE) return null + val id = JavaStubImplUtil.getMethodStubIndex(method) + if (id != -1) { + return javaSimplePropertyGist.getFileData(file).get(id)?.let { indexValue -> + if (isGetter != indexValue.getter) return null + val psiClass = method.containingClass + val project = psiClass!!.project + val expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(indexValue.propertyRefText, psiClass) + return PropertyUtil.getSimplyReturnedField(expr) + } + } + return null +} + +@VisibleForTesting +val javaSimplePropertyGist = GistManager.getInstance().newPsiFileGist("java.simple.property", + 1, + SimplePropertiesExternalizer()) { file -> + findSimplePropertyCandidates(file.node.lighterAST) +} + +private val allowedExpressions by lazy { + TokenSet.create(ElementType.REFERENCE_EXPRESSION, ElementType.THIS_EXPRESSION, ElementType.SUPER_EXPRESSION) +} + +private fun findSimplePropertyCandidates(tree: LighterAST): TIntObjectHashMap { + val result = TIntObjectHashMap() + + object : RecursiveLighterASTNodeWalkingVisitor(tree) { + var methodIndex = 0 + + override fun visitNode(element: LighterASTNode) { + if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return + + if (element.tokenType === JavaElementType.METHOD) { + extractProperty(element)?.let { + result.put(methodIndex, it) + } + methodIndex++ + } + + super.visitNode(element) + } + + private fun extractProperty(method: LighterASTNode): PropertyIndexValue? { + var isConstructor = true + var isGetter = true + + var isBooleanReturnType = false + var isVoidReturnType = false + var setterParameterName: String? = null + + var refText: String? = null + + for (child in tree.getChildren(method)) { + when (child.tokenType) { + JavaElementType.TYPE -> { + val children = tree.getChildren(child) + if (children.size != 1) return null + val typeElement = children[0] + if (typeElement.tokenType == JavaTokenType.VOID_KEYWORD) isVoidReturnType = true + if (typeElement.tokenType == JavaTokenType.BOOLEAN_KEYWORD) isBooleanReturnType = true + isConstructor = false + } + JavaElementType.PARAMETER_LIST -> { + if (isGetter) { + if (LightTreeUtil.firstChildOfType(tree, child, JavaElementType.PARAMETER) != null) return null + } + else { + val parameters = LightTreeUtil.getChildrenOfType(tree, child, JavaElementType.PARAMETER) + if (parameters.size != 1) return null + setterParameterName = JavaLightTreeUtil.getNameIdentifierText(tree, parameters[0]) + if (setterParameterName == null) return null + } + } + JavaElementType.CODE_BLOCK -> { + refText = if (isGetter) getGetterPropertyRefText(child) else getSetterPropertyRefText(child, setterParameterName!!) + if (refText == null) return null + } + JavaTokenType.IDENTIFIER -> { + if (isConstructor) return null + val name = RecordUtil.intern(tree.charTable, child) + val flavour = PropertyUtil.getMethodNameGetterFlavour(name) + when (flavour) { + PropertyUtilBase.GetterFlavour.NOT_A_GETTER -> { + if (PropertyUtil.isSetterName(name)) { + isGetter = false + } + else { + return null + } + } + PropertyUtilBase.GetterFlavour.BOOLEAN -> if (!isBooleanReturnType) return null + else -> { + } + } + if (isVoidReturnType && isGetter) return null + } + } + } + + return refText?.let { PropertyIndexValue(it, isGetter) } + } + + private fun getSetterPropertyRefText(codeBlock: LighterASTNode, setterParameterName: String): String? { + val assignment = tree + .getChildren(codeBlock) + .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) } + ?.takeIf { it.tokenType == JavaElementType.EXPRESSION_STATEMENT } + ?.let { LightTreeUtil.firstChildOfType(tree, it, JavaElementType.ASSIGNMENT_EXPRESSION) } + if (assignment == null || LightTreeUtil.firstChildOfType(tree, assignment, JavaTokenType.EQ) == null) return null + val operands = LightTreeUtil.getChildrenOfType(tree, assignment, ElementType.EXPRESSION_BIT_SET) + if (operands.size != 2 || LightTreeUtil.toFilteredString(tree, operands[1], null) != setterParameterName) return null + val lhsText = LightTreeUtil.toFilteredString(tree, operands[0], null) + if (lhsText == setterParameterName) return null + return lhsText + } + + private fun getGetterPropertyRefText(codeBlock: LighterASTNode): String? { + return tree + .getChildren(codeBlock) + .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) } + ?.takeIf { it.tokenType == JavaElementType.RETURN_STATEMENT } + ?.let { LightTreeUtil.firstChildOfType(tree, it, allowedExpressions) } + ?.takeIf(this::checkQualifiers) + ?.let { LightTreeUtil.toFilteredString(tree, it, null) } + } + + private fun checkQualifiers(expression: LighterASTNode): Boolean { + if (!allowedExpressions.contains(expression.tokenType)) { + return false + } + val qualifier = JavaLightTreeUtil.findExpressionChild(tree, expression) + return qualifier == null || checkQualifiers(qualifier) + } + }.visitNode(tree.root) + return result +} + +@VisibleForTesting +data class PropertyIndexValue(val propertyRefText: String, val getter: Boolean) + +private class SimplePropertiesExternalizer : DataExternalizer> { + override fun save(out: DataOutput, values: TIntObjectHashMap) { + DataInputOutputUtil.writeINT(out, values.size()) + values.forEachEntry { id, value -> + DataInputOutputUtil.writeINT(out, id) + EnumeratorStringDescriptor.INSTANCE.save(out, value.propertyRefText) + out.writeBoolean(value.getter) + return@forEachEntry true + } + } + + override fun read(input: DataInput): TIntObjectHashMap { + val size = DataInputOutputUtil.readINT(input) + if (size == 0) return TIntObjectHashMap() + val values = TIntObjectHashMap(size) + repeat(size) { + val id = DataInputOutputUtil.readINT(input) + val value = PropertyIndexValue(EnumeratorStringDescriptor.INSTANCE.read(input), input.readBoolean()) + values.put(id, value) + } + return values + } +} \ No newline at end of file diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyIndex.kt b/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyIndex.kt deleted file mode 100644 index 5f39a810853f..000000000000 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/JavaSimplePropertyIndex.kt +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2000-2019 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. -package com.intellij.psi.impl - -import com.intellij.ide.highlighter.JavaFileType -import com.intellij.lang.LighterASTNode -import com.intellij.lang.java.JavaParserDefinition -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.JavaTokenType -import com.intellij.psi.PsiField -import com.intellij.psi.impl.cache.RecordUtil -import com.intellij.psi.impl.source.JavaLightStubBuilder -import com.intellij.psi.impl.source.JavaLightTreeUtil -import com.intellij.psi.impl.source.PsiMethodImpl -import com.intellij.psi.impl.source.tree.ElementType -import com.intellij.psi.impl.source.tree.JavaElementType -import com.intellij.psi.impl.source.tree.LightTreeUtil -import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor -import com.intellij.psi.stub.JavaStubImplUtil -import com.intellij.psi.tree.TokenSet -import com.intellij.psi.util.PropertyUtil -import com.intellij.psi.util.PropertyUtilBase -import com.intellij.util.indexing.* -import com.intellij.util.io.DataExternalizer -import com.intellij.util.io.DataInputOutputUtil -import com.intellij.util.io.EnumeratorStringDescriptor -import gnu.trove.TIntObjectHashMap -import java.io.DataInput -import java.io.DataOutput - -private val indexId = ID.create>("java.simple.property") -private object EmptyTIntObjectHashMap : TIntObjectHashMap() { - override fun put(key: Int, value: PropertyIndexValue?)= throw UnsupportedOperationException() -} - -fun getFieldOfGetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, true) - -fun getFieldOfSetter(method: PsiMethodImpl): PsiField? = resolveFieldFromIndexValue(method, false) - -private fun resolveFieldFromIndexValue(method: PsiMethodImpl, isGetter: Boolean): PsiField? { - val id = JavaStubImplUtil.getMethodStubIndex(method) - if (id != -1) { - val virtualFile = method.containingFile.virtualFile - val data = FileBasedIndex.getInstance().getFileData(indexId, virtualFile, method.project) - return data.values.firstOrNull()?.get(id)?.let { indexValue -> - if (isGetter != indexValue.getter) return null - val psiClass = method.containingClass - val project = psiClass!!.project - val expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(indexValue.propertyRefText, psiClass) - return PropertyUtil.getSimplyReturnedField(expr) - } - } - return null -} - -data class PropertyIndexValue(val propertyRefText: String, val getter: Boolean) - -class JavaSimplePropertyIndex : SingleEntryFileBasedIndexExtension>() { - private val allowedExpressions by lazy { - TokenSet.create(ElementType.REFERENCE_EXPRESSION, ElementType.THIS_EXPRESSION, ElementType.SUPER_EXPRESSION) - } - - override fun getIndexer(): SingleEntryIndexer> = object : SingleEntryIndexer>(false) { - override fun computeValue(inputData: FileContent): TIntObjectHashMap? { - var result: TIntObjectHashMap? = null - val tree = (inputData as PsiDependentFileContent).lighterAST - - object : RecursiveLighterASTNodeWalkingVisitor(tree) { - var methodIndex = 0 - - override fun visitNode(element: LighterASTNode) { - if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return - - if (element.tokenType === JavaElementType.METHOD) { - extractProperty(element)?.let { - if (result == null) { - result = TIntObjectHashMap() - } - result!!.put(methodIndex, it) - } - methodIndex++ - } - - super.visitNode(element) - } - - private fun extractProperty(method: LighterASTNode): PropertyIndexValue? { - var isConstructor = true - var isGetter = true - - var isBooleanReturnType = false - var isVoidReturnType = false - var setterParameterName: String? = null - - var refText: String? = null - - for (child in tree.getChildren(method)) { - when (child.tokenType) { - JavaElementType.TYPE -> { - val children = tree.getChildren(child) - if (children.size != 1) return null - val typeElement = children[0] - if (typeElement.tokenType == JavaTokenType.VOID_KEYWORD) isVoidReturnType = true - if (typeElement.tokenType == JavaTokenType.BOOLEAN_KEYWORD) isBooleanReturnType = true - isConstructor = false - } - JavaElementType.PARAMETER_LIST -> { - if (isGetter) { - if (LightTreeUtil.firstChildOfType(tree, child, JavaElementType.PARAMETER) != null) return null - } else { - val parameters = LightTreeUtil.getChildrenOfType(tree, child, JavaElementType.PARAMETER) - if (parameters.size != 1) return null - setterParameterName = JavaLightTreeUtil.getNameIdentifierText(tree, parameters[0]) - if (setterParameterName == null) return null - } - } - JavaElementType.CODE_BLOCK -> { - refText = if (isGetter) getGetterPropertyRefText(child) else getSetterPropertyRefText(child, setterParameterName!!) - if (refText == null) return null - } - JavaTokenType.IDENTIFIER -> { - if (isConstructor) return null - val name = RecordUtil.intern(tree.charTable, child) - val flavour = PropertyUtil.getMethodNameGetterFlavour(name) - when (flavour) { - PropertyUtilBase.GetterFlavour.NOT_A_GETTER -> { - if (PropertyUtil.isSetterName(name)) { - isGetter = false - } - else { - return null - } - } - PropertyUtilBase.GetterFlavour.BOOLEAN -> if (!isBooleanReturnType) return null - else -> { } - } - if (isVoidReturnType && isGetter) return null - } - } - } - - return refText?.let { PropertyIndexValue(it, isGetter) } - } - - private fun getSetterPropertyRefText(codeBlock: LighterASTNode, setterParameterName: String): String? { - val assignment = tree - .getChildren(codeBlock) - .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) } - ?.takeIf { it.tokenType == JavaElementType.EXPRESSION_STATEMENT } - ?.let { LightTreeUtil.firstChildOfType(tree, it, JavaElementType.ASSIGNMENT_EXPRESSION) } - if (assignment == null || LightTreeUtil.firstChildOfType(tree, assignment, JavaTokenType.EQ) == null) return null - val operands = LightTreeUtil.getChildrenOfType(tree, assignment, ElementType.EXPRESSION_BIT_SET) - if (operands.size != 2 || LightTreeUtil.toFilteredString(tree, operands[1], null) != setterParameterName) return null - val lhsText = LightTreeUtil.toFilteredString(tree, operands[0], null) - if (lhsText == setterParameterName) return null - return lhsText - } - - private fun getGetterPropertyRefText(codeBlock: LighterASTNode): String? { - return tree - .getChildren(codeBlock) - .singleOrNull { ElementType.JAVA_STATEMENT_BIT_SET.contains(it.tokenType) } - ?.takeIf { it.tokenType == JavaElementType.RETURN_STATEMENT} - ?.let { LightTreeUtil.firstChildOfType(tree, it, allowedExpressions) } - ?.takeIf(this::checkQualifiers) - ?.let { LightTreeUtil.toFilteredString(tree, it, null) } - } - - private fun checkQualifiers(expression: LighterASTNode): Boolean { - if (!allowedExpressions.contains(expression.tokenType)) { - return false - } - val qualifier = JavaLightTreeUtil.findExpressionChild(tree, expression) - return qualifier == null || checkQualifiers(qualifier) - } - }.visitNode(tree.root) - return result - } - } - - override fun getValueExternalizer(): DataExternalizer> = object: DataExternalizer> { - override fun save(out: DataOutput, values: TIntObjectHashMap) { - DataInputOutputUtil.writeINT(out, values.size()) - values.forEachEntry { id, value -> - DataInputOutputUtil.writeINT(out, id) - EnumeratorStringDescriptor.INSTANCE.save(out, value.propertyRefText) - out.writeBoolean(value.getter) - return@forEachEntry true - } - } - - override fun read(input: DataInput): TIntObjectHashMap { - val size = DataInputOutputUtil.readINT(input) - if (size == 0) return EmptyTIntObjectHashMap - val values = TIntObjectHashMap(size) - repeat(size) { - val id = DataInputOutputUtil.readINT(input) - val value = PropertyIndexValue(EnumeratorStringDescriptor.INSTANCE.read(input), input.readBoolean()) - values.put(id, value) - } - return values - } - } - - override fun getName(): ID> = indexId - - override fun getInputFilter(): FileBasedIndex.InputFilter = object : DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE) { - override fun acceptInput(file: VirtualFile): Boolean = JavaParserDefinition.JAVA_FILE.shouldBuildStubFor(file) - } - - override fun getVersion(): Int = 3 -} \ No newline at end of file diff --git a/java/java-indexing-impl/src/com/intellij/psi/util/PropertyUtil.java b/java/java-indexing-impl/src/com/intellij/psi/util/PropertyUtil.java index 2922a2f2fdc0..abe796a953af 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/util/PropertyUtil.java +++ b/java/java-indexing-impl/src/com/intellij/psi/util/PropertyUtil.java @@ -4,7 +4,7 @@ package com.intellij.psi.util; import com.intellij.lang.java.beans.PropertyKind; import com.intellij.lang.jvm.JvmModifier; import com.intellij.psi.*; -import com.intellij.psi.impl.JavaSimplePropertyIndexKt; +import com.intellij.psi.impl.JavaSimplePropertyGistKt; import com.intellij.psi.impl.source.PsiMethodImpl; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -23,7 +23,7 @@ public class PropertyUtil extends PropertyUtilBase { @Nullable private static PsiField getFieldOfGetter(PsiMethod method, boolean useIndex) { PsiField field = useIndex && method instanceof PsiMethodImpl && method.isPhysical() - ? JavaSimplePropertyIndexKt.getFieldOfGetter((PsiMethodImpl)method) + ? JavaSimplePropertyGistKt.getFieldOfGetter((PsiMethodImpl)method) : getSimplyReturnedField(getGetterReturnExpression(method)); if (field == null || !checkFieldLocation(method, field)) return null; final PsiType returnType = method.getReturnType(); @@ -90,7 +90,7 @@ public class PropertyUtil extends PropertyUtilBase { PsiField field; if (useIndex && method instanceof PsiMethodImpl && method.isPhysical()) { - field = JavaSimplePropertyIndexKt.getFieldOfSetter((PsiMethodImpl)method); + field = JavaSimplePropertyGistKt.getFieldOfSetter((PsiMethodImpl)method); } else { @NonNls final String name = method.getName(); diff --git a/java/java-tests/testSrc/com/intellij/util/JavaPropertyDetectionTest.kt b/java/java-tests/testSrc/com/intellij/util/JavaPropertyDetectionTest.kt index 99efd6b93dd8..77891b699c48 100644 --- a/java/java-tests/testSrc/com/intellij/util/JavaPropertyDetectionTest.kt +++ b/java/java-tests/testSrc/com/intellij/util/JavaPropertyDetectionTest.kt @@ -3,14 +3,12 @@ package com.intellij.util import com.intellij.ide.highlighter.JavaFileType import com.intellij.psi.PsiMethod -import com.intellij.psi.impl.JavaSimplePropertyIndex import com.intellij.psi.impl.PropertyIndexValue +import com.intellij.psi.impl.javaSimplePropertyGist import com.intellij.psi.util.PropertyMemberType import com.intellij.psi.util.PropertyUtil import com.intellij.psi.util.PsiTreeUtil import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase -import com.intellij.util.indexing.FileContentImpl -import com.intellij.util.indexing.IndexingDataKeys import gnu.trove.TIntObjectHashMap import kotlin.test.assertNotEquals @@ -181,8 +179,7 @@ class JavaPropertyDetectionTest : LightJavaCodeInsightFixtureTestCase() { private fun assertJavaSimplePropertyIndex(text: String, expected: TIntObjectHashMap) { val file = myFixture.configureByText(JavaFileType.INSTANCE, text) - val content = FileContentImpl.createByFile(file.virtualFile, project) - val data = JavaSimplePropertyIndex().indexer.map(content).values.firstOrNull() ?: TIntObjectHashMap() + val data = javaSimplePropertyGist.getFileData(file) assertEquals(expected, data) } }