From ec0f21c887dcca75d017528960a2ab2fbca6d97e Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 23 May 2016 15:55:09 +0300 Subject: [PATCH] Spring Support: Fixed rename of custom-named beans specified with Kotlin annotation #KT-12096 Fixed --- ChangeLog.md | 1 + .../kotlin/asJava/KtLightAnnotation.kt | 63 +++++++++++++------ idea/src/META-INF/plugin.xml | 2 + .../refactoring/rename/AbstractRenameTest.kt | 4 +- .../after/bean/BeanA.java | 4 ++ .../after/bean/JavaRef.java | 8 +++ .../after/bean/KotlinConfig.kt | 8 +++ .../after/bean/spring-config.xml | 9 +++ .../annotationArgBySpELRefInXMLConf.test | 11 ++++ .../before/bean/BeanA.java | 4 ++ .../before/bean/JavaRef.java | 8 +++ .../before/bean/KotlinConfig.kt | 8 +++ .../before/bean/spring-config.xml | 9 +++ .../rename/SpringRenameTestGenerated.java | 6 ++ 14 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/BeanA.java create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/JavaRef.java create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/KotlinConfig.kt create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/spring-config.xml create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/BeanA.java create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/JavaRef.java create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/KotlinConfig.kt create mode 100644 ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/spring-config.xml diff --git a/ChangeLog.md b/ChangeLog.md index 7267bdff84f..3969ae8513b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -89,6 +89,7 @@ ###### Issues fixed - [`KT-12091`](https://youtrack.jetbrains.com/issue/KT-12091) Fixed unstable behavior of Spring line markers +- [`KT-12096`](https://youtrack.jetbrains.com/issue/KT-12096) Fixed rename of custom-named beans specified with Kotlin annotation - [`KT-12117`](https://youtrack.jetbrains.com/issue/KT-12117) Group Kotlin classes from the same file in the Choose Bean dialog - [`KT-12120`](https://youtrack.jetbrains.com/issue/KT-12120) Show autowiring candidates line markers for @Autowired-annotated constructors and constructor parameters - [`KT-12122`](https://youtrack.jetbrains.com/issue/KT-12122) Fixed line marker popup on functions with @Qualifier-annotated parameters diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt index 1b121ff5478..685b8661408 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt @@ -17,13 +17,11 @@ package org.jetbrains.kotlin.asJava import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtParameter -import org.jetbrains.kotlin.psi.ValueArgument +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument @@ -36,9 +34,12 @@ class KtLightAnnotation( override val kotlinOrigin: KtAnnotationEntry, private val owner: PsiAnnotationOwner ) : PsiAnnotation by clsDelegate, KtLightElement { - open inner class LightExpressionValue(val delegate: D) : PsiAnnotationMemberValue, PsiExpression by delegate { + open inner class LightExpressionValue( + val delegate: D, + private val parent: PsiElement + ) : PsiAnnotationMemberValue, PsiExpression by delegate { val originalExpression: PsiElement? by lazy(LazyThreadSafetyMode.PUBLICATION) { - val nameAndValue = getStrictParentOfType() ?: return@lazy null + val nameAndValue = delegate.getStrictParentOfType() ?: return@lazy null val annotationEntry = this@KtLightAnnotation.kotlinOrigin val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry) val resolvedCall = annotationEntry.getResolvedCall(context) ?: return@lazy null @@ -81,32 +82,56 @@ class KtLightAnnotation( override fun getLanguage() = KotlinLanguage.INSTANCE override fun getNavigationElement() = originalExpression override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE + override fun getParent() = parent + + override fun replace(newElement: PsiElement): PsiElement { + val value = (newElement as? PsiLiteral)?.value as? String ?: return this + val origin = originalExpression ?: return this + + val exprToReplace = + if (origin is KtCallExpression /*arrayOf*/) { + unwrapArray(origin.valueArguments) + } else { + origin as? KtExpression + } ?: return this + exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) + + return this + } } - inner class LightStringLiteral(delegate: PsiLiteralExpression): LightExpressionValue(delegate), PsiLiteralExpression { + inner class LightStringLiteral( + delegate: PsiLiteralExpression, + parent: PsiElement + ): LightExpressionValue(delegate, parent), PsiLiteralExpression { override fun getValue() = delegate.value } inner class LightClassLiteral( - delegate: PsiClassObjectAccessExpression - ) : LightExpressionValue(delegate), PsiClassObjectAccessExpression { + delegate: PsiClassObjectAccessExpression, + parent: PsiElement + ) : LightExpressionValue(delegate, parent), PsiClassObjectAccessExpression { override fun getType() = delegate.type override fun getOperand(): PsiTypeElement = delegate.operand } - inner class LightArrayInitializerValue(private val delegate: PsiArrayInitializerMemberValue) : PsiArrayInitializerMemberValue by delegate { - private val _initializers by lazy(LazyThreadSafetyMode.PUBLICATION) { delegate.initializers.map { wrapAnnotationValue(it) }.toTypedArray() } + inner class LightArrayInitializerValue( + private val delegate: PsiArrayInitializerMemberValue, + private val parent: PsiElement + ) : PsiArrayInitializerMemberValue by delegate { + private val _initializers by lazy(LazyThreadSafetyMode.PUBLICATION) { delegate.initializers.map { wrapAnnotationValue(it, this) }.toTypedArray() } override fun getInitializers() = _initializers override fun getLanguage() = KotlinLanguage.INSTANCE + override fun getParent() = parent } - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue { + private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement): PsiAnnotationMemberValue { return when { - value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value) - value is PsiClassObjectAccessExpression -> LightClassLiteral(value) - value is PsiExpression -> LightExpressionValue(value) - value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value) + value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent) + value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent) + value is PsiExpression -> LightExpressionValue(value, parent) + value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent) else -> value } } @@ -118,14 +143,16 @@ class KtLightAnnotation( override fun getOwner() = owner - override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) } - override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) } + override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it, this) } + override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it, this) } override fun getText() = kotlinOrigin.text ?: "" override fun getTextRange() = kotlinOrigin.textRange ?: TextRange.EMPTY_RANGE override fun getParent() = owner as? PsiElement + override fun getLanguage() = KotlinLanguage.INSTANCE + override fun delete() { kotlinOrigin.delete() } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 40b94719c76..54338791434 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -661,6 +661,8 @@ language="kotlin" implementationClass="org.jetbrains.kotlin.idea.KotlinLightConstantExpressionEvaluator"/> + + org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention Kotlin diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt index 58f696e1efb..4087e21265d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt @@ -192,7 +192,9 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() { val substitution = RenamePsiElementProcessor.forElement(toRename).substituteElementToRename(toRename, null) - runRenameProcessor(context, newName, substitution, true, true) + val searchInComments = renameParamsObject["searchInComments"]?.asBoolean ?: true + val searchInTextOccurrences = renameParamsObject["searchInTextOccurrences"]?.asBoolean ?: true + runRenameProcessor(context, newName, substitution, searchInComments, searchInTextOccurrences) } } diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/BeanA.java b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/BeanA.java new file mode 100644 index 00000000000..0f693c0bcdf --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/BeanA.java @@ -0,0 +1,4 @@ +package bean; + +public class BeanA { +} diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/JavaRef.java b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/JavaRef.java new file mode 100644 index 00000000000..1520f9d7ae5 --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/JavaRef.java @@ -0,0 +1,8 @@ +package bean; + +import org.springframework.beans.factory.annotation.Value; + +public class JavaRef { + @Value("#{ nameJ }") private BeanA valueA; + @Value("#{ nameK2 }") private BeanA valueK; +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/KotlinConfig.kt b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/KotlinConfig.kt new file mode 100644 index 00000000000..bb095845190 --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/KotlinConfig.kt @@ -0,0 +1,8 @@ +package bean + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration open class KotlinConfig { + @Bean(name = arrayOf("nameK2")) open fun createBeanK() = BeanA() +} diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/spring-config.xml b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/spring-config.xml new file mode 100644 index 00000000000..8db5217300b --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/after/bean/spring-config.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test new file mode 100644 index 00000000000..b1ad5aeb2cc --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test @@ -0,0 +1,11 @@ +{ + "fixtureClasses": ["org.jetbrains.kotlin.idea.spring.tests.SpringTestFixtureExtension"], + "type": "MARKED_ELEMENT", + "mainFile": "bean/spring-config.xml", + "springFileSet": ["bean/spring-config.xml", "bean/KotlinConfig.kt"], + "newName": "nameK2", + "byRef": "true", + "withRuntime": "true", + "searchInComments": "false", + "searchInTextOccurrences": "false" +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/BeanA.java b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/BeanA.java new file mode 100644 index 00000000000..0f693c0bcdf --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/BeanA.java @@ -0,0 +1,4 @@ +package bean; + +public class BeanA { +} diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/JavaRef.java b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/JavaRef.java new file mode 100644 index 00000000000..79f3697fa4e --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/JavaRef.java @@ -0,0 +1,8 @@ +package bean; + +import org.springframework.beans.factory.annotation.Value; + +public class JavaRef { + @Value("#{ nameJ }") private BeanA valueA; + @Value("#{ nameK }") private BeanA valueK; +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/KotlinConfig.kt b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/KotlinConfig.kt new file mode 100644 index 00000000000..ad2edfd76f0 --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/KotlinConfig.kt @@ -0,0 +1,8 @@ +package bean + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration open class KotlinConfig { + @Bean(name = arrayOf("nameK")) open fun createBeanK() = BeanA() +} diff --git a/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/spring-config.xml b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/spring-config.xml new file mode 100644 index 00000000000..7a1508825f6 --- /dev/null +++ b/ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/before/bean/spring-config.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/rename/SpringRenameTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/rename/SpringRenameTestGenerated.java index 8f1e767e2da..71d3393fc41 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/rename/SpringRenameTestGenerated.java +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/rename/SpringRenameTestGenerated.java @@ -35,6 +35,12 @@ public class SpringRenameTestGenerated extends AbstractSpringRenameTest { KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("ultimate/testData/spring/core/rename"), Pattern.compile("^(.+)\\.test$")); } + @TestMetadata("annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test") + public void testAnnotationArgBySpELRefInXMLConf_AnnotationArgBySpELRefInXMLConf() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/rename/annotationArgBySpELRefInXMLConf/annotationArgBySpELRefInXMLConf.test"); + doTest(fileName); + } + @TestMetadata("classWithXmlRefs/classWithXmlRef.test") public void testClassWithXmlRefs_ClassWithXmlRef() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/rename/classWithXmlRefs/classWithXmlRef.test");