Intentions: 'Implement abstract member' (Kotlin -> Java)

#KT-8467 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-21 20:55:56 +03:00
parent 03641ffbee
commit 4fba251fba
17 changed files with 285 additions and 20 deletions
@@ -18,11 +18,13 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.CodeInsightBundle import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.FileModificationService import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.generation.OverrideImplementUtil
import com.intellij.ide.util.PsiClassListCellRenderer import com.intellij.ide.util.PsiClassListCellRenderer
import com.intellij.ide.util.PsiElementListCellRenderer import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.PopupChooserBuilder import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
@@ -30,11 +32,11 @@ import com.intellij.psi.PsiElement
import com.intellij.ui.components.JBList import com.intellij.ui.components.JBList
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.asJava.KtLightClass import org.jetbrains.kotlin.asJava.KtLightClass
import org.jetbrains.kotlin.asJava.KtLightClassForExplicitDeclaration import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind 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.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject
@@ -81,12 +83,17 @@ class ImplementAbstractMemberIntention :
if (subMember?.kind?.isReal ?: false) return subMember else return null if (subMember?.kind?.isReal ?: false) return subMember else return null
} }
private fun findClassesToProcess(member: KtNamedDeclaration): Sequence<KtClassOrObject> { private fun findClassesToProcess(member: KtNamedDeclaration): Sequence<PsiElement> {
val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence() val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence()
val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence() val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence()
fun acceptSubClass(classOrObject: KtClassOrObject): Boolean { fun acceptSubClass(subClass: PsiElement): Boolean {
val classDescriptor = classOrObject.resolveToDescriptorIfAny() as? ClassDescriptor ?: return false 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 return classDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(classDescriptor, memberDescriptor) == null
} }
@@ -100,7 +107,6 @@ class ImplementAbstractMemberIntention :
return HierarchySearchRequest(baseClass, baseClass.useScope, false) return HierarchySearchRequest(baseClass, baseClass.useScope, false)
.searchInheritors() .searchInheritors()
.asSequence() .asSequence()
.mapNotNull { (it as? KtLightClassForExplicitDeclaration)?.getOrigin() }
.filter(::acceptSubClass) .filter(::acceptSubClass)
} }
@@ -118,6 +124,24 @@ class ImplementAbstractMemberIntention :
return element.nameIdentifier?.textRange 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<PsiElement>) { private fun implementInClass(member: KtNamedDeclaration, targetClasses: List<PsiElement>) {
val project = member.project val project = member.project
project.executeCommand(CodeInsightBundle.message("intention.implement.abstract.method.command.name")) { project.executeCommand(CodeInsightBundle.message("intention.implement.abstract.method.command.name")) {
@@ -125,18 +149,11 @@ class ImplementAbstractMemberIntention :
runWriteAction { runWriteAction {
for (targetClass in targetClasses) { for (targetClass in targetClasses) {
try { try {
val subClass = (targetClass as? KtLightClass)?.getOrigin() ?: targetClass as? KtClassOrObject ?: continue when (targetClass) {
val subClassDescriptor = subClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: continue is KtLightClass -> targetClass.getOrigin()?.let { implementInKotlinClass(member, it) }
val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: continue is KtEnumEntry -> implementInKotlinClass(member, targetClass)
val superClassDescriptor = superMemberDescriptor.containingDeclaration as? ClassDescriptor ?: continue is PsiClass -> implementInJavaClass(member, targetClass)
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())
} }
catch(e: IncorrectOperationException) { catch(e: IncorrectOperationException) {
LOG.error(e) LOG.error(e)
@@ -156,7 +173,7 @@ class ImplementAbstractMemberIntention :
o1 is KtEnumEntry && o2 is KtEnumEntry -> o1.name!!.compareTo(o2.name!!) o1 is KtEnumEntry && o2 is KtEnumEntry -> o1.name!!.compareTo(o2.name!!)
o1 is KtEnumEntry -> -1 o1 is KtEnumEntry -> -1
o2 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 else -> 0
} }
} }
@@ -187,7 +204,7 @@ class ImplementAbstractMemberIntention :
val classesToProcess = project.runSynchronouslyWithProgress( val classesToProcess = project.runSynchronouslyWithProgress(
CodeInsightBundle.message("intention.implement.abstract.method.searching.for.descendants.progress"), CodeInsightBundle.message("intention.implement.abstract.method.searching.for.descendants.progress"),
true true
) { findClassesToProcess(element).map { it.toLightClass() ?: it }.toList() } ?: return ) { findClassesToProcess(element).toList() } ?: return
if (classesToProcess.isEmpty()) return if (classesToProcess.isEmpty()) return
classesToProcess.singleOrNull()?.let { return implementInClass(element, it.singletonList()) } classesToProcess.singleOrNull()?.let { return implementInClass(element, it.singletonList()) }
@@ -0,0 +1,28 @@
package source;
abstract class X<S> implements T<S> {
@Override
public S foo(S s) {
return null;
}
}
class Y implements T<String> {
@Override
public String foo(String s) {
return null;
}
}
class Z implements T<Boolean> {
@Override
public Boolean foo(Boolean b) {
return null;
}
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
fun foo(x: X): X
}
@@ -0,0 +1,20 @@
package source;
abstract class X<S> implements T<S> {
}
class Y implements T<String> {
}
class Z implements T<Boolean> {
@Override
public Boolean foo(Boolean b) {
return null;
}
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
fun <caret>foo(x: X): X
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention",
"withRuntime": "true"
}
@@ -0,0 +1,28 @@
package source;
abstract class X<S> implements T<S> {
@Override
public S getFoo() {
return null;
}
}
class Y implements T<String> {
@Override
public String getFoo() {
return null;
}
}
class Z implements T<Boolean> {
@Override
public Boolean getFoo() {
return null;
}
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
val foo: X
}
@@ -0,0 +1,20 @@
package source;
abstract class X<S> implements T<S> {
}
class Y implements T<String> {
}
class Z implements T<Boolean> {
@Override
public Boolean getFoo() {
return null;
}
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
val <caret>foo: X
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention",
"withRuntime": "true"
}
@@ -0,0 +1,54 @@
package source;
abstract class X<S> implements T<S> {
@Override
public S getFoo() {
return null;
}
@Override
public void setFoo(S s) {
}
}
class Y implements T<String> {
@Override
public void setFoo(String s) {
}
@Override
public String getFoo() {
return null;
}
}
class Z implements T<Boolean> {
@Override
public Boolean getFoo() {
return null;
}
@Override
public void setFoo(Boolean b) {
}
}
class W implements T<Integer> {
@Override
public Integer getFoo() {
return null;
}
@Override
public void setFoo(Integer integer) {
}
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
var foo: X
}
@@ -0,0 +1,35 @@
package source;
abstract class X<S> implements T<S> {
@Override
public S getFoo() {
return null;
}
}
class Y implements T<String> {
@Override
public void setFoo(String s) {
}
}
class Z implements T<Boolean> {
@Override
public Boolean getFoo() {
return null;
}
@Override
public void setFoo(Boolean b) {
}
}
class W implements T<Integer> {
}
interface U extends T<Object> {
}
@@ -0,0 +1,5 @@
package source
interface T<X> {
var <caret>foo: X
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention",
"withRuntime": "true"
}
@@ -35,6 +35,24 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileIntentions"), Pattern.compile("^(.+)\\.test$")); 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") @TestMetadata("moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test")
public void testMoveDeclarationToSeparateFile_moveClassToExistingFile_MoveClassToExistingFile() throws Exception { public void testMoveDeclarationToSeparateFile_moveClassToExistingFile_MoveClassToExistingFile() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test");