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.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<KtClassOrObject> {
private fun findClassesToProcess(member: KtNamedDeclaration): Sequence<PsiElement> {
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<PsiElement>) {
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()) }
@@ -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$"));
}
@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");