From 63dc843c713dbe7c13b968c61063e7da2ec77d10 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 27 Jul 2015 16:48:31 +0300 Subject: [PATCH] KT-8542 Override dialog: only one class from hierarchy is shown #KT-8542 Fixed --- .idea/dictionaries/valentin.xml | 1 + .../DescriptorClassMember.kt | 16 +- .../ImplementMethodsHandler.kt | 5 +- .../OverrideImplementMethodsHandler.kt | 49 ++-- .../OverrideMethodsHandler.kt | 82 +++--- .../overrideImplement/ambiguousSuper.kt | 11 + .../overrideImplement/ambiguousSuper.kt.after | 29 +++ .../overrideImplement/doNotOverrideFinal.kt | 18 ++ .../doNotOverrideFinal.kt.after | 36 +++ .../overrideImplement/superPreference.kt | 21 ++ .../superPreference.kt.after | 47 ++++ .../AbstractOverrideImplementTest.java | 233 ------------------ .../AbstractOverrideImplementTest.kt | 189 ++++++++++++++ .../codeInsight/OverrideImplementTest.java | 12 + 14 files changed, 439 insertions(+), 310 deletions(-) create mode 100644 idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt create mode 100644 idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt create mode 100644 idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/superPreference.kt create mode 100644 idea/testData/codeInsight/overrideImplement/superPreference.kt.after delete mode 100644 idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.java create mode 100644 idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt diff --git a/.idea/dictionaries/valentin.xml b/.idea/dictionaries/valentin.xml index f97358c1c99..5cebee5ec8d 100644 --- a/.idea/dictionaries/valentin.xml +++ b/.idea/dictionaries/valentin.xml @@ -5,6 +5,7 @@ decapitalize delegator funs + immediates initializers inserter negatable diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/DescriptorClassMember.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/DescriptorClassMember.kt index 1890e13fd6f..4a3dfec3833 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/DescriptorClassMember.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/DescriptorClassMember.kt @@ -18,16 +18,18 @@ package org.jetbrains.kotlin.idea.core.overrideImplement import com.intellij.codeInsight.generation.ClassMemberWithElement import com.intellij.codeInsight.generation.MemberChooserObject -import com.intellij.codeInsight.generation.MemberChooserObjectBase import com.intellij.codeInsight.generation.PsiElementMemberChooserObject +import com.intellij.openapi.project.Project import com.intellij.openapi.util.Iconable import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMember import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.JetDescriptorIconProvider +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.psi.JetClass import org.jetbrains.kotlin.psi.JetDeclaration import org.jetbrains.kotlin.psi.JetFile @@ -39,10 +41,16 @@ import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import javax.swing.Icon -public class DescriptorClassMember( - private val psiElement: PsiElement, +class OverrideMemberChooserObject( + project: Project, + val member: CallableMemberDescriptor, + val immediateSuper: CallableMemberDescriptor +) : DescriptorClassMember(DescriptorToSourceUtilsIde.getAnyDeclaration(project, member)!!, member) + +public open class DescriptorClassMember( + psiElement: PsiElement, public val descriptor: DeclarationDescriptor -) : MemberChooserObjectBase(DescriptorClassMember.getText(descriptor), DescriptorClassMember.getIcon(psiElement, descriptor)), ClassMemberWithElement { +) : PsiElementMemberChooserObject(psiElement, DescriptorClassMember.getText(descriptor), DescriptorClassMember.getIcon(psiElement, descriptor)), ClassMemberWithElement { override fun getParentNodeDelegate(): MemberChooserObject { val parent = descriptor.containingDeclaration ?: error("No parent for $descriptor") diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMethodsHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMethodsHandler.kt index a368f1e6d29..e6772fe81f3 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMethodsHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMethodsHandler.kt @@ -25,7 +25,10 @@ import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.resolve.OverrideResolver public class ImplementMethodsHandler : OverrideImplementMethodsHandler(), IntentionAction { - override fun collectMethodsToGenerate(descriptor: ClassDescriptor) = OverrideResolver.getMissingImplementations(descriptor) + override fun collectMethodsToGenerate(descriptor: ClassDescriptor, project: Project): Collection { + return OverrideResolver.getMissingImplementations(descriptor) + .map { OverrideMemberChooserObject(project, it, it) } + } override fun getChooserTitle() = "Implement Members" diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMethodsHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMethodsHandler.kt index 6a4cf8040c7..29d1959cd12 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMethodsHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMethodsHandler.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.core.overrideImplement import com.intellij.codeInsight.hint.HintManager import com.intellij.ide.util.MemberChooser import com.intellij.lang.LanguageCodeInsightActionHandler -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper @@ -29,7 +28,6 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor -import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.quickfix.moveCaretIntoGeneratedElement import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.ShortenReferences @@ -41,14 +39,14 @@ import java.util.ArrayList public abstract class OverrideImplementMethodsHandler : LanguageCodeInsightActionHandler { - public fun collectMethodsToGenerate(classOrObject: JetClassOrObject): Set { + public fun collectMethodsToGenerate(classOrObject: JetClassOrObject): Collection { val descriptor = classOrObject.resolveToDescriptor() as? ClassDescriptor ?: return emptySet() - return collectMethodsToGenerate(descriptor) + return collectMethodsToGenerate(descriptor, classOrObject.project) } - protected abstract fun collectMethodsToGenerate(descriptor: ClassDescriptor): Set + protected abstract fun collectMethodsToGenerate(descriptor: ClassDescriptor, project: Project): Collection - private fun showOverrideImplementChooser(project: Project, members: Array): MemberChooser? { + private fun showOverrideImplementChooser(project: Project, members: Array): MemberChooser? { val chooser = MemberChooser(members, true, true, project) chooser.title = getChooserTitle() chooser.show() @@ -71,15 +69,13 @@ public abstract class OverrideImplementMethodsHandler : LanguageCodeInsightActio val elementAtCaret = file.findElementAt(editor.caretModel.offset) val classOrObject = elementAtCaret?.getNonStrictParentOfType()!! - val missingImplementations = collectMethodsToGenerate(classOrObject) - if (missingImplementations.isEmpty() && !implementAll) { + val members = collectMethodsToGenerate(classOrObject) + if (members.isEmpty() && !implementAll) { HintManager.getInstance().showErrorHint(editor, getNoMethodsFoundHint()) return } - val members = membersFromDescriptors(file as JetFile, missingImplementations) - - val selectedElements: List = if (implementAll) { + val selectedElements = if (implementAll) { members } else { @@ -108,23 +104,7 @@ public abstract class OverrideImplementMethodsHandler : LanguageCodeInsightActio typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES } - private val LOG = Logger.getInstance(javaClass().canonicalName) - - public fun membersFromDescriptors(file: JetFile, missingImplementations: Iterable): List { - val members = ArrayList() - for (memberDescriptor in missingImplementations) { - val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, memberDescriptor) - if (declaration == null) { - LOG.error("Can not find declaration for descriptor $memberDescriptor") - } - else { - members.add(DescriptorClassMember(declaration, memberDescriptor)) - } - } - return members - } - - public fun generateMethods(editor: Editor, classOrObject: JetClassOrObject, selectedElements: List) { + public fun generateMethods(editor: Editor, classOrObject: JetClassOrObject, selectedElements: Collection) { runWriteAction { val body = classOrObject.getOrCreateBody() @@ -203,16 +183,15 @@ public abstract class OverrideImplementMethodsHandler : LanguageCodeInsightActio return whiteSpace } - private fun generateOverridingMembers(selectedElements: List, + private fun generateOverridingMembers(selectedElements: Collection, classOrObject: JetClassOrObject): List { val overridingMembers = ArrayList() for (selectedElement in selectedElements) { - val descriptor = selectedElement.descriptor - if (descriptor is SimpleFunctionDescriptor) { - overridingMembers.add(overrideFunction(classOrObject, descriptor)) - } - else if (descriptor is PropertyDescriptor) { - overridingMembers.add(overrideProperty(classOrObject, descriptor)) + val descriptor = selectedElement.immediateSuper + when (descriptor) { + is SimpleFunctionDescriptor -> overridingMembers.add(overrideFunction(classOrObject, descriptor)) + is PropertyDescriptor -> overridingMembers.add(overrideProperty(classOrObject, descriptor)) + else -> error("Unknown member to override: $descriptor") } } return overridingMembers diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMethodsHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMethodsHandler.kt index cef375214e7..9658f1d4e36 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMethodsHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMethodsHandler.kt @@ -16,53 +16,61 @@ package org.jetbrains.kotlin.idea.core.overrideImplement -import com.google.common.collect.LinkedHashMultimap +import com.intellij.openapi.project.Project import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.resolve.OverrideResolver -import org.jetbrains.kotlin.resolve.OverridingUtil -import org.jetbrains.kotlin.resolve.OverridingUtil.OverrideCompatibilityInfo.Result.OVERRIDABLE -import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isOrOverridesSynthesized +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import java.util.ArrayList +import java.util.LinkedHashMap public class OverrideMethodsHandler : OverrideImplementMethodsHandler() { - override fun collectMethodsToGenerate(descriptor: ClassDescriptor): Set { - val superMethods = collectSuperMethods(descriptor) - for (member in descriptor.defaultType.memberScope.getAllDescriptors()) { - if (member is CallableMemberDescriptor) { - if (member.kind == CallableMemberDescriptor.Kind.DECLARATION) { - superMethods.removeAll(member.overriddenDescriptors) + override fun collectMethodsToGenerate(descriptor: ClassDescriptor, project: Project): Collection { + val result = ArrayList() + for (member in descriptor.unsubstitutedMemberScope.getAllDescriptors()) { + if (member is CallableMemberDescriptor && !member.isReal()) { + val overridden = member.overriddenDescriptors + if (overridden.any { it.modality == Modality.FINAL }) continue + + val realSuperToImmediates = LinkedHashMap>() + for (immediateSuper in overridden) { + for (realSuper in toRealSupers(immediateSuper)) { + realSuperToImmediates.getOrPut(realSuper) { ArrayList(1) }.add(immediateSuper) + } + } + + val realSupers = realSuperToImmediates.keySet() + val nonAbstractRealSupers = realSupers.filter { it.modality != Modality.ABSTRACT } + val realSupersToUse = if (nonAbstractRealSupers.isNotEmpty()) { + nonAbstractRealSupers + } + else { + listOf(realSupers.first()) + } + + for (realSuper in realSupersToUse) { + val immediateSupers = realSuperToImmediates[realSuper]!! + assert(immediateSupers.isNotEmpty()) + + val immediateSuperToUse = if (immediateSupers.size() == 1) { + immediateSupers.single() + } + else { + immediateSupers.singleOrNull { (it.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.CLASS } ?: immediateSupers.first() + } + result.add(OverrideMemberChooserObject(project, realSuper, immediateSuperToUse)) } } } - - return superMethods - .filter { it.modality.isOverridable && !isOrOverridesSynthesized(it) } - .toSet() + return result } - private fun collectSuperMethods(classDescriptor: ClassDescriptor): MutableSet { - val inheritedFunctionsSet = classDescriptor.typeConstructor.supertypes - .flatMap { it.memberScope.getAllDescriptors() } - .filterIsInstance() - .toSet() + private fun CallableMemberDescriptor.isReal() = kind == CallableMemberDescriptor.Kind.DECLARATION - // Only those actually inherited - val filteredMembers = OverrideResolver.filterOutOverridden(inheritedFunctionsSet) - - // Group members with "the same" signature - val factoredMembers = LinkedHashMultimap.create() - for (one in filteredMembers) { - if (factoredMembers.values().contains(one)) continue - for (another in filteredMembers) { - // if (one == another) continue; - factoredMembers.put(one, one) - if (OverridingUtil.DEFAULT.isOverridableBy(one, another).result == OVERRIDABLE || OverridingUtil.DEFAULT.isOverridableBy(another, one).result == OVERRIDABLE) { - factoredMembers.put(one, another) - } - } - } - - return factoredMembers.keySet() + private fun toRealSupers(immediateSuper: CallableMemberDescriptor): Collection { + val overridden = immediateSuper.overriddenDescriptors + if (overridden.isEmpty()) return listOf(immediateSuper) + return overridden.flatMap { toRealSupers(it) }.toSet() } override fun getChooserTitle() = "Override Members" diff --git a/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt b/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt new file mode 100644 index 00000000000..103a4525396 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt @@ -0,0 +1,11 @@ +interface I { + open fun foo(){} +} + +open class A { + open fun foo(){} +} + +class C : A(), I { + +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt.after b/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt.after new file mode 100644 index 00000000000..a6c470bc28c --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/ambiguousSuper.kt.after @@ -0,0 +1,29 @@ +interface I { + open fun foo(){} +} + +open class A { + open fun foo(){} +} + +class C : A(), I { + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + override fun foo() { + super.foo() + } + + override fun foo() { + super.foo() + } + + override fun hashCode(): Int { + return super.hashCode() + } + + override fun toString(): String { + return super.toString() + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt b/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt new file mode 100644 index 00000000000..9d121663483 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt @@ -0,0 +1,18 @@ +open class A { + fun a(){} + fun b(){} +} + +interface I { + fun b() +} + +abstract class B : A() { + open fun f(){} + abstract fun g() + fun h(){} +} + +class C : B(), I { + +} diff --git a/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt.after b/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt.after new file mode 100644 index 00000000000..cb95729e021 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/doNotOverrideFinal.kt.after @@ -0,0 +1,36 @@ +open class A { + fun a(){} + fun b(){} +} + +interface I { + fun b() +} + +abstract class B : A() { + open fun f(){} + abstract fun g() + fun h(){} +} + +class C : B(), I { + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + override fun f() { + super.f() + } + + override fun g() { + throw UnsupportedOperationException() + } + + override fun hashCode(): Int { + return super.hashCode() + } + + override fun toString(): String { + return super.toString() + } +} diff --git a/idea/testData/codeInsight/overrideImplement/superPreference.kt b/idea/testData/codeInsight/overrideImplement/superPreference.kt new file mode 100644 index 00000000000..0828885210b --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/superPreference.kt @@ -0,0 +1,21 @@ +abstract class A : I1 { + open fun a(){} +} + +interface I1 { + fun i1() + fun i() +} + +interface I2 { + fun i2() + fun a() +} + +interface I3 { + fun i() +} + +abstract class B : I2, A(), I3 { + +} diff --git a/idea/testData/codeInsight/overrideImplement/superPreference.kt.after b/idea/testData/codeInsight/overrideImplement/superPreference.kt.after new file mode 100644 index 00000000000..c2dcad42755 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/superPreference.kt.after @@ -0,0 +1,47 @@ +abstract class A : I1 { + open fun a(){} +} + +interface I1 { + fun i1() + fun i() +} + +interface I2 { + fun i2() + fun a() +} + +interface I3 { + fun i() +} + +abstract class B : I2, A(), I3 { + override fun a() { + super.a() + } + + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + override fun hashCode(): Int { + return super.hashCode() + } + + override fun i() { + throw UnsupportedOperationException() + } + + override fun i1() { + throw UnsupportedOperationException() + } + + override fun i2() { + throw UnsupportedOperationException() + } + + override fun toString(): String { + return super.toString() + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.java deleted file mode 100644 index 97fc3a178d3..00000000000 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.codeInsight; - -import com.intellij.codeInsight.generation.OverrideImplementUtil; -import com.intellij.codeInsight.generation.PsiMethodMember; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.testFramework.LightProjectDescriptor; -import com.intellij.util.SmartList; -import kotlin.KotlinPackage; -import kotlin.jvm.functions.Function1; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.builtins.KotlinBuiltIns; -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; -import org.jetbrains.kotlin.descriptors.ClassDescriptor; -import org.jetbrains.kotlin.idea.core.overrideImplement.DescriptorClassMember; -import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMethodsHandler; -import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMethodsHandler; -import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMethodsHandler; -import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase; -import org.jetbrains.kotlin.idea.test.JetLightProjectDescriptor; -import org.jetbrains.kotlin.psi.JetClassOrObject; -import org.jetbrains.kotlin.psi.JetFile; -import org.jetbrains.kotlin.resolve.OverrideResolver; -import org.jetbrains.kotlin.test.JetTestUtils; -import org.jetbrains.kotlin.test.TagsTestDataUtil; -import org.jetbrains.kotlin.utils.UtilsPackage; -import org.junit.Assert; - -import java.io.File; -import java.util.*; - -public abstract class AbstractOverrideImplementTest extends JetLightCodeInsightFixtureTestCase { - @NotNull - @Override - protected LightProjectDescriptor getProjectDescriptor() { - return JetLightProjectDescriptor.INSTANCE; - } - - protected void doImplementFileTest() { - doFileTest(new ImplementMethodsHandler()); - } - - protected void doOverrideFileTest() { - doFileTest(new OverrideMethodsHandler()); - } - - protected void doMultiImplementFileTest() { - doMultiFileTest(new ImplementMethodsHandler()); - } - - protected void doMultiOverrideFileTest() { - doMultiFileTest(new OverrideMethodsHandler()); - } - - protected void doImplementDirectoryTest() { - doDirectoryTest(new ImplementMethodsHandler()); - } - - protected void doOverrideDirectoryTest(@Nullable String memberToImplement) { - doDirectoryTest(new OverrideMethodsHandler(), memberToImplement); - } - - protected void doImplementJavaDirectoryTest(String className, String methodName) { - myFixture.copyDirectoryToProject(getTestName(true), ""); - myFixture.configureFromTempProjectFile("foo/JavaClass.java"); - - Project project = myFixture.getProject(); - - PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)); - Assert.assertNotNull("Can't find class: " + className, aClass); - - PsiMethod method = aClass.findMethodsByName(methodName, false)[0]; - Assert.assertNotNull(String.format("Can't find method '%s' in class %s", methodName, className), method); - - generateImplementation(method); - - myFixture.checkResultByFile(getTestName(true) + "/foo/JavaClass.java.after"); - } - - private void doFileTest(OverrideImplementMethodsHandler handler) { - myFixture.configureByFile(getTestName(true) + ".kt"); - doOverrideImplement(handler, null); - checkResultByFile(getTestName(true) + ".kt.after"); - } - - private void doMultiFileTest(OverrideImplementMethodsHandler handler) { - myFixture.configureByFile(getTestName(true) + ".kt"); - doMultiOverrideImplement(handler); - checkResultByFile(getTestName(true) + ".kt.after"); - } - - protected void doDirectoryTest(OverrideImplementMethodsHandler handler) { - doDirectoryTest(handler, null); - } - - private void doDirectoryTest(OverrideImplementMethodsHandler handler, @Nullable String memberToOverride) { - myFixture.copyDirectoryToProject(getTestName(true), ""); - myFixture.configureFromTempProjectFile("foo/Impl.kt"); - doOverrideImplement(handler, memberToOverride); - checkResultByFile(getTestName(true) + "/foo/Impl.kt.after"); - } - - private void doOverrideImplement(OverrideImplementMethodsHandler handler, @Nullable String memberToOverride) { - PsiElement elementAtCaret = myFixture.getFile().findElementAt(myFixture.getEditor().getCaretModel().getOffset()); - JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class); - assertNotNull("Caret should be inside class or object", classOrObject); - - JetFile jetFile = classOrObject.getContainingJetFile(); - Set descriptors = handler.collectMethodsToGenerate(classOrObject); - - CallableMemberDescriptor singleToOverride; - if (memberToOverride == null) { - // Filter out fake overrides of members of Any (equals, hashCode, toString) - List filtered = KotlinPackage.filter(descriptors, new Function1() { - @Override - public Boolean invoke(CallableMemberDescriptor descriptor) { - ClassDescriptor any = KotlinBuiltIns.getInstance().getAny(); - for (CallableMemberDescriptor overridden : OverrideResolver.getOverriddenDeclarations(descriptor)) { - if (overridden.getContainingDeclaration().equals(any)) { - return false; - } - } - return true; - } - }); - assertEquals("Invalid number of available descriptors for override", 1, filtered.size()); - singleToOverride = filtered.iterator().next(); - } - else { - CallableMemberDescriptor candidateToOverride = null; - for (CallableMemberDescriptor callable : descriptors) { - if (callable.getName().asString().equals(memberToOverride)) { - if (candidateToOverride != null) { - throw new IllegalStateException("more then one descriptor with name " + memberToOverride); - } - candidateToOverride = callable; - } - } - if (candidateToOverride == null) { - throw new IllegalStateException("no descriptors to override with name " + memberToOverride + " found"); - } - singleToOverride = candidateToOverride; - } - - performGenerateCommand(classOrObject, - OverrideImplementMethodsHandler.Companion.membersFromDescriptors(jetFile, Collections.singletonList(singleToOverride))); - } - - private void doMultiOverrideImplement(OverrideImplementMethodsHandler handler) { - PsiElement elementAtCaret = myFixture.getFile().findElementAt(myFixture.getEditor().getCaretModel().getOffset()); - JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class); - assertNotNull("Caret should be inside class or object", classOrObject); - - JetFile jetFile = classOrObject.getContainingJetFile(); - Set descriptors = handler.collectMethodsToGenerate(classOrObject); - - List descriptorsList = new ArrayList(descriptors); - Collections.sort(descriptorsList, new Comparator() { - @Override - public int compare(@NotNull CallableMemberDescriptor desc1, @NotNull CallableMemberDescriptor desc2) { - return desc1.getName().compareTo(desc2.getName()); - } - }); - - performGenerateCommand(classOrObject, OverrideImplementMethodsHandler.Companion.membersFromDescriptors(jetFile, descriptorsList)); - } - - private void generateImplementation(@NotNull final PsiMethod method) { - WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() { - @Override - public void run() { - PsiClass aClass = ((PsiClassOwner) myFixture.getFile()).getClasses()[0]; - - PsiMethodMember methodMember = new PsiMethodMember(method, PsiSubstitutor.EMPTY); - - OverrideImplementUtil.overrideOrImplementMethodsInRightPlace( - myFixture.getEditor(), aClass, new SmartList(methodMember), false); - - PostprocessReformattingAspect.getInstance(myFixture.getProject()).doPostponedFormatting(); - } - }); - } - - private void performGenerateCommand( - final JetClassOrObject classOrObject, - final List descriptorsToGenerate - ) { - try { - new WriteCommandAction(myFixture.getProject(), myFixture.getFile()) { - @Override - protected void run(@NotNull Result result) throws Throwable { - OverrideImplementMethodsHandler.Companion.generateMethods(myFixture.getEditor(), classOrObject, descriptorsToGenerate); - } - }.performCommand(); - } - catch (Throwable throwable) { - throw UtilsPackage.rethrow(throwable); - } - } - - private void checkResultByFile(String fileName) { - File expectedFile = new File(myFixture.getTestDataPath(), fileName); - try { - Assert.assertTrue(expectedFile.exists()); - myFixture.checkResultByFile(fileName); - } - catch (AssertionError error) { - JetTestUtils.assertEqualsToFile(expectedFile, TagsTestDataUtil.generateTextWithCaretAndSelection(myFixture.getEditor())); - } - } -} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt new file mode 100644 index 00000000000..5171e44d481 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.codeInsight + +import com.intellij.codeInsight.generation.OverrideImplementUtil +import com.intellij.codeInsight.generation.PsiMethodMember +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClassOwner +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiSubstitutor +import com.intellij.psi.impl.source.PostprocessReformattingAspect +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.util.SmartList +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMethodsHandler +import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMethodsHandler +import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject +import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMethodsHandler +import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.JetLightProjectDescriptor +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.JetClassOrObject +import org.jetbrains.kotlin.test.JetTestUtils +import org.jetbrains.kotlin.test.TagsTestDataUtil +import org.jetbrains.kotlin.utils.rethrow +import org.junit.Assert +import java.io.File +import kotlin.test.assertEquals + +public abstract class AbstractOverrideImplementTest : JetLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor(): LightProjectDescriptor = JetLightProjectDescriptor.INSTANCE + + protected fun doImplementFileTest() { + doFileTest(ImplementMethodsHandler()) + } + + protected fun doOverrideFileTest() { + doFileTest(OverrideMethodsHandler()) + } + + protected fun doMultiImplementFileTest() { + doMultiFileTest(ImplementMethodsHandler()) + } + + protected fun doMultiOverrideFileTest() { + doMultiFileTest(OverrideMethodsHandler()) + } + + protected fun doImplementDirectoryTest() { + doDirectoryTest(ImplementMethodsHandler()) + } + + protected fun doOverrideDirectoryTest(memberToImplement: String?) { + doDirectoryTest(OverrideMethodsHandler(), memberToImplement) + } + + protected fun doImplementJavaDirectoryTest(className: String, methodName: String) { + myFixture.copyDirectoryToProject(getTestName(true), "") + myFixture.configureFromTempProjectFile("foo/JavaClass.java") + + val project = myFixture.project + + val aClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) + ?: error("Can't find class: $className") + + val method = aClass.findMethodsByName(methodName, false)[0] + ?: error("Can't find method '$methodName' in class $className") + + generateImplementation(method) + + myFixture.checkResultByFile(getTestName(true) + "/foo/JavaClass.java.after") + } + + private fun doFileTest(handler: OverrideImplementMethodsHandler) { + myFixture.configureByFile(getTestName(true) + ".kt") + doOverrideImplement(handler, null) + checkResultByFile(getTestName(true) + ".kt.after") + } + + private fun doMultiFileTest(handler: OverrideImplementMethodsHandler) { + myFixture.configureByFile(getTestName(true) + ".kt") + doMultiOverrideImplement(handler) + checkResultByFile(getTestName(true) + ".kt.after") + } + + protected fun doDirectoryTest(handler: OverrideImplementMethodsHandler) { + doDirectoryTest(handler, null) + } + + private fun doDirectoryTest(handler: OverrideImplementMethodsHandler, memberToOverride: String?) { + myFixture.copyDirectoryToProject(getTestName(true), "") + myFixture.configureFromTempProjectFile("foo/Impl.kt") + doOverrideImplement(handler, memberToOverride) + checkResultByFile(getTestName(true) + "/foo/Impl.kt.after") + } + + private fun doOverrideImplement(handler: OverrideImplementMethodsHandler, memberToOverride: String?) { + val elementAtCaret = myFixture.file.findElementAt(myFixture.editor.caretModel.offset) + val classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, javaClass()) + ?: error("Caret should be inside class or object") + + val chooserObjects = handler.collectMethodsToGenerate(classOrObject) + + val singleToOverride = if (memberToOverride == null) { + val filtered = chooserObjects.filter { it.descriptor.containingDeclaration != KotlinBuiltIns.getInstance().any } + assertEquals(1, filtered.size(), "Invalid number of available chooserObjects for override") + filtered.single() + } + else { + var candidateToOverride: OverrideMemberChooserObject? = null + for (chooserObject in chooserObjects) { + if (chooserObject.descriptor.name.asString() == memberToOverride) { + if (candidateToOverride != null) { + throw IllegalStateException("more then one descriptor with name $memberToOverride") + } + candidateToOverride = chooserObject + } + } + if (candidateToOverride == null) { + throw IllegalStateException("no chooserObjects to override with name $memberToOverride found") + } + candidateToOverride!! + } + + performGenerateCommand(classOrObject, listOf(singleToOverride)) + } + + private fun doMultiOverrideImplement(handler: OverrideImplementMethodsHandler) { + val elementAtCaret = myFixture.file.findElementAt(myFixture.editor.caretModel.offset) + val classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, javaClass()) + ?: error("Caret should be inside class or object") + + val chooserObjects = handler.collectMethodsToGenerate(classOrObject).sortBy { it.member.name.asString() + " in " + it.immediateSuper.containingDeclaration.name.asString() } + performGenerateCommand(classOrObject, chooserObjects) + } + + private fun generateImplementation(method: PsiMethod) { + getProject().executeWriteCommand("") { + val aClass = (myFixture.file as PsiClassOwner).classes[0] + + val methodMember = PsiMethodMember(method, PsiSubstitutor.EMPTY) + + OverrideImplementUtil.overrideOrImplementMethodsInRightPlace(myFixture.editor, aClass, SmartList(methodMember), false) + + PostprocessReformattingAspect.getInstance(myFixture.project).doPostponedFormatting() + } + } + + private fun performGenerateCommand( + classOrObject: JetClassOrObject, + selectedElements: List) { + try { + myFixture.project.executeWriteCommand("") { + OverrideImplementMethodsHandler.generateMethods(myFixture.editor, classOrObject, selectedElements) + } + } + catch (throwable: Throwable) { + throw rethrow(throwable) + } + + } + + private fun checkResultByFile(fileName: String) { + val expectedFile = File(myFixture.testDataPath, fileName) + try { + Assert.assertTrue(expectedFile.exists()) + myFixture.checkResultByFile(fileName) + } + catch (error: AssertionError) { + JetTestUtils.assertEqualsToFile(expectedFile, TagsTestDataUtil.generateTextWithCaretAndSelection(myFixture.editor)) + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.java index 475843b2465..d1646a1ec8e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.java @@ -198,4 +198,16 @@ public final class OverrideImplementTest extends AbstractOverrideImplementTest { public void testVarArgs() { doOverrideFileTest(); } + + public void testDoNotOverrideFinal() { + doMultiOverrideFileTest(); + } + + public void testSuperPreference() { + doMultiOverrideFileTest(); + } + + public void testAmbiguousSuper() { + doMultiOverrideFileTest(); + } }