diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt index 1c8a6b9f2ff..a51729e569d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt @@ -18,11 +18,13 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.codeInsight.CodeInsightBundle import com.intellij.codeInsight.FileModificationService +import com.intellij.codeInsight.generation.OverrideImplementUtil import com.intellij.ide.util.PsiClassListCellRenderer import com.intellij.ide.util.PsiElementListCellRenderer import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.PopupChooserBuilder import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiClass @@ -30,11 +32,11 @@ import com.intellij.psi.PsiElement import com.intellij.ui.components.JBList import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.asJava.KtLightClass -import org.jetbrains.kotlin.asJava.KtLightClassForExplicitDeclaration -import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.asJava.toLightMethods import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject @@ -81,12 +83,17 @@ class ImplementAbstractMemberIntention : if (subMember?.kind?.isReal ?: false) return subMember else return null } - private fun findClassesToProcess(member: KtNamedDeclaration): Sequence { + private fun findClassesToProcess(member: KtNamedDeclaration): Sequence { val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence() val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence() - fun acceptSubClass(classOrObject: KtClassOrObject): Boolean { - val classDescriptor = classOrObject.resolveToDescriptorIfAny() as? ClassDescriptor ?: return false + fun acceptSubClass(subClass: PsiElement): Boolean { + val classDescriptor = when (subClass) { + is KtLightClass -> subClass.getOrigin()?.resolveToDescriptorIfAny() + is KtEnumEntry -> subClass.resolveToDescriptorIfAny() + is PsiClass -> subClass.getJavaClassDescriptor() + else -> null + } as? ClassDescriptor ?: return false return classDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(classDescriptor, memberDescriptor) == null } @@ -100,7 +107,6 @@ class ImplementAbstractMemberIntention : return HierarchySearchRequest(baseClass, baseClass.useScope, false) .searchInheritors() .asSequence() - .mapNotNull { (it as? KtLightClassForExplicitDeclaration)?.getOrigin() } .filter(::acceptSubClass) } @@ -118,6 +124,24 @@ class ImplementAbstractMemberIntention : return element.nameIdentifier?.textRange } + private fun implementInKotlinClass(member: KtNamedDeclaration, targetClass: KtClassOrObject) { + val subClassDescriptor = targetClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: return + val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return + val superClassDescriptor = superMemberDescriptor.containingDeclaration as? ClassDescriptor ?: return + val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, subClassDescriptor.defaultType) + ?: TypeSubstitutor.EMPTY + val descriptorToImplement = superMemberDescriptor.substitute(substitutor) as CallableMemberDescriptor + val chooserObject = OverrideMemberChooserObject.create(member.project, + descriptorToImplement, + descriptorToImplement, + OverrideMemberChooserObject.BodyType.EMPTY) + OverrideImplementMembersHandler.generateMembers(null, targetClass, chooserObject.singletonList()) + } + + private fun implementInJavaClass(member: KtNamedDeclaration, targetClass: PsiClass) { + member.toLightMethods().forEach { OverrideImplementUtil.overrideOrImplement(targetClass, it) } + } + private fun implementInClass(member: KtNamedDeclaration, targetClasses: List) { val project = member.project project.executeCommand(CodeInsightBundle.message("intention.implement.abstract.method.command.name")) { @@ -125,18 +149,11 @@ class ImplementAbstractMemberIntention : runWriteAction { for (targetClass in targetClasses) { try { - val subClass = (targetClass as? KtLightClass)?.getOrigin() ?: targetClass as? KtClassOrObject ?: continue - val subClassDescriptor = subClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: continue - val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: continue - val superClassDescriptor = superMemberDescriptor.containingDeclaration as? ClassDescriptor ?: continue - val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, subClassDescriptor.defaultType) - ?: TypeSubstitutor.EMPTY - val descriptorToImplement = superMemberDescriptor.substitute(substitutor) as CallableMemberDescriptor - val chooserObject = OverrideMemberChooserObject.create(project, - descriptorToImplement, - descriptorToImplement, - OverrideMemberChooserObject.BodyType.EMPTY) - OverrideImplementMembersHandler.generateMembers(null, subClass, chooserObject.singletonList()) + when (targetClass) { + is KtLightClass -> targetClass.getOrigin()?.let { implementInKotlinClass(member, it) } + is KtEnumEntry -> implementInKotlinClass(member, targetClass) + is PsiClass -> implementInJavaClass(member, targetClass) + } } catch(e: IncorrectOperationException) { LOG.error(e) @@ -156,7 +173,7 @@ class ImplementAbstractMemberIntention : o1 is KtEnumEntry && o2 is KtEnumEntry -> o1.name!!.compareTo(o2.name!!) o1 is KtEnumEntry -> -1 o2 is KtEnumEntry -> 1 - o1 is PsiClass && o2 is PsiClass -> baseComparator.compare(o1 as PsiClass, o2 as PsiClass) + o1 is PsiClass && o2 is PsiClass -> baseComparator.compare(o1, o2) else -> 0 } } @@ -187,7 +204,7 @@ class ImplementAbstractMemberIntention : val classesToProcess = project.runSynchronouslyWithProgress( CodeInsightBundle.message("intention.implement.abstract.method.searching.for.descendants.progress"), true - ) { findClassesToProcess(element).map { it.toLightClass() ?: it }.toList() } ?: return + ) { findClassesToProcess(element).toList() } ?: return if (classesToProcess.isEmpty()) return classesToProcess.singleOrNull()?.let { return implementInClass(element, it.singletonList()) } diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/inheritors.java new file mode 100644 index 00000000000..ba0c7ae496b --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/inheritors.java @@ -0,0 +1,28 @@ +package source; + +abstract class X implements T { + + @Override + public S foo(S s) { + return null; + } +} + +class Y implements T { + + @Override + public String foo(String s) { + return null; + } +} + +class Z implements T { + @Override + public Boolean foo(Boolean b) { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/test.kt new file mode 100644 index 00000000000..3c06186b353 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/after/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + fun foo(x: X): X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/inheritors.java new file mode 100644 index 00000000000..ab432967bf9 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/inheritors.java @@ -0,0 +1,20 @@ +package source; + +abstract class X implements T { + +} + +class Y implements T { + +} + +class Z implements T { + @Override + public Boolean foo(Boolean b) { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/test.kt new file mode 100644 index 00000000000..08a3ea19155 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/before/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + fun foo(x: X): X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test new file mode 100644 index 00000000000..216c8b03823 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/test.kt", + "intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/inheritors.java new file mode 100644 index 00000000000..10371ea12b5 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/inheritors.java @@ -0,0 +1,28 @@ +package source; + +abstract class X implements T { + + @Override + public S getFoo() { + return null; + } +} + +class Y implements T { + + @Override + public String getFoo() { + return null; + } +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/test.kt new file mode 100644 index 00000000000..314ad1d906e --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/after/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + val foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/inheritors.java new file mode 100644 index 00000000000..87617c8cd1b --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/inheritors.java @@ -0,0 +1,20 @@ +package source; + +abstract class X implements T { + +} + +class Y implements T { + +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/test.kt new file mode 100644 index 00000000000..3ab67874ecd --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/before/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + val foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/implementAllInJava.test b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/implementAllInJava.test new file mode 100644 index 00000000000..216c8b03823 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/implementAllInJava.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/test.kt", + "intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/inheritors.java new file mode 100644 index 00000000000..88d2d3f5bd0 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/inheritors.java @@ -0,0 +1,54 @@ +package source; + +abstract class X implements T { + @Override + public S getFoo() { + return null; + } + + @Override + public void setFoo(S s) { + + } +} + +class Y implements T { + @Override + public void setFoo(String s) { + + } + + @Override + public String getFoo() { + return null; + } +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } + + @Override + public void setFoo(Boolean b) { + + } +} + +class W implements T { + + @Override + public Integer getFoo() { + return null; + } + + @Override + public void setFoo(Integer integer) { + + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/test.kt new file mode 100644 index 00000000000..04bfef351eb --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/after/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + var foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/inheritors.java b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/inheritors.java new file mode 100644 index 00000000000..78d9f515e97 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/inheritors.java @@ -0,0 +1,35 @@ +package source; + +abstract class X implements T { + @Override + public S getFoo() { + return null; + } +} + +class Y implements T { + @Override + public void setFoo(String s) { + + } +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } + + @Override + public void setFoo(Boolean b) { + + } +} + +class W implements T { + +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/test.kt b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/test.kt new file mode 100644 index 00000000000..ecfc4156e0c --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/before/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + var foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/implementAllInJava.test b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/implementAllInJava.test new file mode 100644 index 00000000000..216c8b03823 --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/implementAllInJava.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/test.kt", + "intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java index 66c639d9336..dcd5f72be3e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java @@ -35,6 +35,24 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileIntentions"), Pattern.compile("^(.+)\\.test$")); } + @TestMetadata("implementAbstractMember/implementFunctionInJava/implementAllInJava.test") + public void testImplementAbstractMember_implementFunctionInJava_ImplementAllInJava() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test"); + doTest(fileName); + } + + @TestMetadata("implementAbstractMember/implementValInJava/implementAllInJava.test") + public void testImplementAbstractMember_implementValInJava_ImplementAllInJava() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementValInJava/implementAllInJava.test"); + doTest(fileName); + } + + @TestMetadata("implementAbstractMember/implementVarInJava/implementAllInJava.test") + public void testImplementAbstractMember_implementVarInJava_ImplementAllInJava() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementVarInJava/implementAllInJava.test"); + doTest(fileName); + } + @TestMetadata("moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test") public void testMoveDeclarationToSeparateFile_moveClassToExistingFile_MoveClassToExistingFile() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test");