Extract Class: Implement 'Extract Interface' refactoring
#KT-11017 Fixed
This commit is contained in:
@@ -144,6 +144,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
|||||||
|
|
||||||
- [`KT-13155`](https://youtrack.jetbrains.com/issue/KT-13155) Implement "Introduce Type Parameter" refactoring
|
- [`KT-13155`](https://youtrack.jetbrains.com/issue/KT-13155) Implement "Introduce Type Parameter" refactoring
|
||||||
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Superclass" refactoring
|
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Superclass" refactoring
|
||||||
|
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring
|
||||||
|
|
||||||
#### Android Lint
|
#### Android Lint
|
||||||
|
|
||||||
|
|||||||
@@ -799,6 +799,7 @@ fun main(args: Array<String>) {
|
|||||||
model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest")
|
model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest")
|
||||||
model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest")
|
model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest")
|
||||||
model("refactoring/extractSuperclass", pattern = KT_OR_KTS, testMethod = "doExtractSuperclassTest")
|
model("refactoring/extractSuperclass", pattern = KT_OR_KTS, testMethod = "doExtractSuperclassTest")
|
||||||
|
model("refactoring/extractInterface", pattern = KT_OR_KTS, testMethod = "doExtractInterfaceTest")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractPullUpTest>() {
|
testClass<AbstractPullUpTest>() {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement
|
|||||||
import com.intellij.psi.PsiNameIdentifierOwner
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
import com.intellij.refactoring.RefactoringActionHandler
|
import com.intellij.refactoring.RefactoringActionHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureHandler
|
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureHandler
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractInterfaceHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceLambdaParameterHandler
|
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceLambdaParameterHandler
|
||||||
@@ -81,6 +82,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
|
|||||||
override fun getPushDownHandler() = KotlinPushDownHandler()
|
override fun getPushDownHandler() = KotlinPushDownHandler()
|
||||||
|
|
||||||
override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler
|
override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler
|
||||||
|
|
||||||
|
override fun getExtractInterfaceHandler() = KotlinExtractInterfaceHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
class KotlinVetoRenameCondition: Condition<PsiElement> {
|
class KotlinVetoRenameCondition: Condition<PsiElement> {
|
||||||
|
|||||||
+33
-23
@@ -68,22 +68,24 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class ExtractSuperclassInfo(
|
data class ExtractSuperInfo(
|
||||||
val originalClass: KtClassOrObject,
|
val originalClass: KtClassOrObject,
|
||||||
val memberInfos: Collection<KotlinMemberInfo>,
|
val memberInfos: Collection<KotlinMemberInfo>,
|
||||||
val targetParent: PsiElement,
|
val targetParent: PsiElement,
|
||||||
val targetFileName: String,
|
val targetFileName: String,
|
||||||
val newClassName: String,
|
val newClassName: String,
|
||||||
|
val isInterface: Boolean,
|
||||||
val docPolicy: DocCommentPolicy<*>
|
val docPolicy: DocCommentPolicy<*>
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExtractSuperclassRefactoring(
|
class ExtractSuperRefactoring(
|
||||||
private var extractInfo: ExtractSuperclassInfo
|
private var extractInfo: ExtractSuperInfo
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private fun getElementsToMove(
|
private fun getElementsToMove(
|
||||||
memberInfos: Collection<KotlinMemberInfo>,
|
memberInfos: Collection<KotlinMemberInfo>,
|
||||||
originalClass: KtClassOrObject
|
originalClass: KtClassOrObject,
|
||||||
|
isExtractInterface: Boolean
|
||||||
): Map<KtElement, KotlinMemberInfo?> {
|
): Map<KtElement, KotlinMemberInfo?> {
|
||||||
val project = originalClass.project
|
val project = originalClass.project
|
||||||
val elementsToMove = LinkedHashMap<KtElement, KotlinMemberInfo?>()
|
val elementsToMove = LinkedHashMap<KtElement, KotlinMemberInfo?>()
|
||||||
@@ -106,7 +108,7 @@ class ExtractSuperclassRefactoring(
|
|||||||
?: continue
|
?: continue
|
||||||
val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue
|
val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue
|
||||||
val superClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, superClassDescriptor) as? KtClass ?: continue
|
val superClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, superClassDescriptor) as? KtClass ?: continue
|
||||||
if (!superClass.isInterface() || superClass in superInterfacesToMove) {
|
if ((!isExtractInterface && !superClass.isInterface()) || superClass in superInterfacesToMove) {
|
||||||
elementsToMove[superTypeListEntry] = null
|
elementsToMove[superTypeListEntry] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +121,8 @@ class ExtractSuperclassRefactoring(
|
|||||||
originalClass: KtClassOrObject,
|
originalClass: KtClassOrObject,
|
||||||
memberInfos: List<KotlinMemberInfo>,
|
memberInfos: List<KotlinMemberInfo>,
|
||||||
targetParent: PsiElement,
|
targetParent: PsiElement,
|
||||||
newClassName: String
|
newClassName: String,
|
||||||
|
isExtractInterface: Boolean
|
||||||
): MultiMap<PsiElement, String> {
|
): MultiMap<PsiElement, String> {
|
||||||
val conflicts = MultiMap<PsiElement, String>()
|
val conflicts = MultiMap<PsiElement, String>()
|
||||||
|
|
||||||
@@ -133,7 +136,7 @@ class ExtractSuperclassRefactoring(
|
|||||||
?.let { conflicts.putValue(it, "Class $newClassName already exists in the target scope") }
|
?.let { conflicts.putValue(it, "Class $newClassName already exists in the target scope") }
|
||||||
}
|
}
|
||||||
|
|
||||||
val elementsToMove = getElementsToMove(memberInfos, originalClass).keys
|
val elementsToMove = getElementsToMove(memberInfos, originalClass, isExtractInterface).keys
|
||||||
|
|
||||||
val moveTarget = if (targetParent is PsiDirectory) {
|
val moveTarget = if (targetParent is PsiDirectory) {
|
||||||
val targetPackage = targetParent.getPackage() ?: return conflicts
|
val targetPackage = targetParent.getPackage() ?: return conflicts
|
||||||
@@ -192,7 +195,7 @@ class ExtractSuperclassRefactoring(
|
|||||||
collectTypeParameters(refTarget)
|
collectTypeParameters(refTarget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass)
|
getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass, extractInfo.isInterface)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
val (element, info) = it
|
val (element, info) = it
|
||||||
@@ -206,18 +209,22 @@ class ExtractSuperclassRefactoring(
|
|||||||
val newClassName = extractInfo.newClassName
|
val newClassName = extractInfo.newClassName
|
||||||
val originalClass = extractInfo.originalClass
|
val originalClass = extractInfo.originalClass
|
||||||
|
|
||||||
|
val kind = if (extractInfo.isInterface) "interface" else "class"
|
||||||
|
val prototype = psiFactory.createClass("$kind $newClassName")
|
||||||
val newClass = if (targetParent is PsiDirectory) {
|
val newClass = if (targetParent is PsiDirectory) {
|
||||||
val template = FileTemplateManager.getInstance(project).getInternalTemplate("Kotlin File")
|
val template = FileTemplateManager.getInstance(project).getInternalTemplate("Kotlin File")
|
||||||
val newFile = NewKotlinFileAction.createFileFromTemplate(extractInfo.targetFileName, template, targetParent) as KtFile
|
val newFile = NewKotlinFileAction.createFileFromTemplate(extractInfo.targetFileName, template, targetParent) as KtFile
|
||||||
newFile.add(psiFactory.createClass("class $newClassName")) as KtClass
|
newFile.add(prototype) as KtClass
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent }
|
val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent }
|
||||||
insertDeclaration(psiFactory.createClass("class $newClassName"), targetSibling)
|
insertDeclaration(prototype, targetSibling)
|
||||||
}
|
}
|
||||||
|
|
||||||
val shouldBeAbstract = extractInfo.memberInfos.any { it.isToAbstract }
|
val shouldBeAbstract = extractInfo.memberInfos.any { it.isToAbstract }
|
||||||
newClass.addModifier(if (shouldBeAbstract) KtTokens.ABSTRACT_KEYWORD else KtTokens.OPEN_KEYWORD)
|
if (!extractInfo.isInterface) {
|
||||||
|
newClass.addModifier(if (shouldBeAbstract) KtTokens.ABSTRACT_KEYWORD else KtTokens.OPEN_KEYWORD)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeParameters.isNotEmpty()) {
|
if (typeParameters.isNotEmpty()) {
|
||||||
val typeParameterListText = typeParameters.sortedBy { it.startOffset }.map { it.text }.joinToString(prefix = "<", postfix = ">")
|
val typeParameterListText = typeParameters.sortedBy { it.startOffset }.map { it.text }.joinToString(prefix = "<", postfix = ">")
|
||||||
@@ -235,10 +242,11 @@ class ExtractSuperclassRefactoring(
|
|||||||
append(typeParameters.sortedBy { it.startOffset }.map { it.name }.joinToString(prefix = "<", postfix = ">"))
|
append(typeParameters.sortedBy { it.startOffset }.map { it.name }.joinToString(prefix = "<", postfix = ">"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val needSuperCall = superClassEntry is KtSuperTypeCallEntry
|
val needSuperCall = !extractInfo.isInterface
|
||||||
|
&& (superClassEntry is KtSuperTypeCallEntry
|
||||||
|| originalClass.hasPrimaryConstructor()
|
|| originalClass.hasPrimaryConstructor()
|
||||||
|| originalClass.getSecondaryConstructors().isEmpty()
|
|| originalClass.getSecondaryConstructors().isEmpty())
|
||||||
val newSuperTypeCallEntry = if (needSuperCall) {
|
val newSuperTypeListEntry = if (needSuperCall) {
|
||||||
psiFactory.createSuperTypeCallEntry("$superTypeText()")
|
psiFactory.createSuperTypeCallEntry("$superTypeText()")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -253,10 +261,10 @@ class ExtractSuperclassRefactoring(
|
|||||||
}
|
}
|
||||||
else superClassEntry
|
else superClassEntry
|
||||||
newClass.addSuperTypeListEntry(superClassEntryToAdd)
|
newClass.addSuperTypeListEntry(superClassEntryToAdd)
|
||||||
ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeCallEntry))
|
ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeListEntry))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeCallEntry))
|
ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeListEntry))
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortenReferences.DEFAULT.process(newClass)
|
ShortenReferences.DEFAULT.process(newClass)
|
||||||
@@ -267,15 +275,17 @@ class ExtractSuperclassRefactoring(
|
|||||||
fun performRefactoring() {
|
fun performRefactoring() {
|
||||||
val originalClass = extractInfo.originalClass
|
val originalClass = extractInfo.originalClass
|
||||||
|
|
||||||
KotlinExtractSuperclassHandler.getErrorMessage(originalClass)?.let {
|
val handler = if (extractInfo.isInterface) KotlinExtractInterfaceHandler else KotlinExtractSuperclassHandler
|
||||||
throw CommonRefactoringUtil.RefactoringErrorHintException(it)
|
handler.getErrorMessage(originalClass)?.let { throw CommonRefactoringUtil.RefactoringErrorHintException(it) }
|
||||||
}
|
|
||||||
|
|
||||||
val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor
|
val superClassEntry = if (!extractInfo.isInterface) {
|
||||||
val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny()
|
val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor
|
||||||
val superClassEntry = originalClass.getSuperTypeListEntries().firstOrNull {
|
val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny()
|
||||||
bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor
|
originalClass.getSuperTypeListEntries().firstOrNull {
|
||||||
|
bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else null
|
||||||
|
|
||||||
project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } }
|
project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } }
|
||||||
|
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.refactoring.introduce.extractClass
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractInterfaceDialog
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
|
||||||
|
object KotlinExtractInterfaceHandler : KotlinExtractSuperHandlerBase(true) {
|
||||||
|
val REFACTORING_NAME = "Extract Interface"
|
||||||
|
|
||||||
|
override fun getErrorMessage(klass: KtClassOrObject): String? {
|
||||||
|
if (klass is KtClass && klass.isAnnotation()) return "Interface cannot be extracted from an annotation class"
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) {
|
||||||
|
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return
|
||||||
|
|
||||||
|
KotlinExtractInterfaceDialog(
|
||||||
|
originalClass = klass,
|
||||||
|
targetParent = targetParent,
|
||||||
|
conflictChecker = { checkConflicts(klass, it) },
|
||||||
|
refactoring = { ExtractSuperRefactoring(it).performRefactoring() }
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.refactoring.introduce.extractClass
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.ScrollType
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.refactoring.RefactoringActionHandler
|
||||||
|
import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil
|
||||||
|
import com.intellij.refactoring.lang.ElementsHandler
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperDialogBase
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
|
|
||||||
|
abstract class KotlinExtractSuperHandlerBase(private val isExtractInterface: Boolean) : RefactoringActionHandler, ElementsHandler {
|
||||||
|
override fun isEnabledOnElements(elements: Array<out PsiElement>) = elements.singleOrNull() is KtClassOrObject
|
||||||
|
|
||||||
|
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
|
||||||
|
val offset = editor.caretModel.offset
|
||||||
|
val element = file.findElementAt(offset) ?: return
|
||||||
|
val klass = element.getNonStrictParentOfType<KtClassOrObject>() ?: return
|
||||||
|
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
|
||||||
|
selectElements(klass, project, editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
||||||
|
if (dataContext == null) return
|
||||||
|
val editor = CommonDataKeys.EDITOR.getData(dataContext)
|
||||||
|
val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType<KtClassOrObject>() ?: return
|
||||||
|
selectElements(klass, project, editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectElements(klass: KtClassOrObject, project: Project, editor: Editor?) {
|
||||||
|
val containers = klass.getExtractionContainers(strict = true, includeAll = true) + SeparateFileWrapper(klass.manager)
|
||||||
|
|
||||||
|
if (editor == null) return doInvoke(klass, containers.first(), project, editor)
|
||||||
|
|
||||||
|
chooseContainerElementIfNecessary(
|
||||||
|
containers,
|
||||||
|
editor,
|
||||||
|
if (containers.first() is KtFile) "Select target file" else "Select target code block / file",
|
||||||
|
true,
|
||||||
|
{ it },
|
||||||
|
{ doInvoke(klass, if (it is SeparateFileWrapper) klass.containingFile.parent!! else it, project, editor) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun checkConflicts(originalClass: KtClassOrObject, dialog: KotlinExtractSuperDialogBase): Boolean {
|
||||||
|
val conflicts = ExtractSuperRefactoring.collectConflicts(
|
||||||
|
originalClass,
|
||||||
|
dialog.selectedMembers,
|
||||||
|
dialog.selectedTargetParent,
|
||||||
|
dialog.extractedSuperName,
|
||||||
|
isExtractInterface
|
||||||
|
)
|
||||||
|
return ExtractSuperClassUtil.showConflicts(dialog, conflicts, originalClass.project)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract fun getErrorMessage(klass: KtClassOrObject): String?
|
||||||
|
|
||||||
|
protected abstract fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?)
|
||||||
|
}
|
||||||
+4
-61
@@ -16,65 +16,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ScrollType
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import com.intellij.refactoring.HelpID
|
import com.intellij.refactoring.HelpID
|
||||||
import com.intellij.refactoring.RefactoringActionHandler
|
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil
|
|
||||||
import com.intellij.refactoring.lang.ElementsHandler
|
|
||||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperclassDialog
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperclassDialog
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
|
||||||
|
|
||||||
object KotlinExtractSuperclassHandler : RefactoringActionHandler, ElementsHandler {
|
object KotlinExtractSuperclassHandler : KotlinExtractSuperHandlerBase(false) {
|
||||||
val REFACTORING_NAME = "Extract Superclass"
|
val REFACTORING_NAME = "Extract Superclass"
|
||||||
|
|
||||||
override fun isEnabledOnElements(elements: Array<out PsiElement>) = elements.singleOrNull() is KtClassOrObject
|
override fun getErrorMessage(klass: KtClassOrObject): String? {
|
||||||
|
|
||||||
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
|
|
||||||
val offset = editor.caretModel.offset
|
|
||||||
val element = file.findElementAt(offset) ?: return
|
|
||||||
val klass = element.getNonStrictParentOfType<KtClassOrObject>() ?: return
|
|
||||||
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
|
|
||||||
selectElements(klass, project, editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
|
||||||
if (dataContext == null) return
|
|
||||||
val editor = CommonDataKeys.EDITOR.getData(dataContext)
|
|
||||||
val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType<KtClassOrObject>() ?: return
|
|
||||||
selectElements(klass, project, editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectElements(klass: KtClassOrObject, project: Project, editor: Editor?) {
|
|
||||||
val containers = klass.getExtractionContainers(strict = true, includeAll = true) + SeparateFileWrapper(klass.manager)
|
|
||||||
|
|
||||||
if (editor == null) return doInvoke(klass, containers.first(), project, editor)
|
|
||||||
|
|
||||||
chooseContainerElementIfNecessary(
|
|
||||||
containers,
|
|
||||||
editor,
|
|
||||||
if (containers.first() is KtFile) "Select target file" else "Select target code block / file",
|
|
||||||
true,
|
|
||||||
{ it },
|
|
||||||
{ doInvoke(klass, it, project, editor) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getErrorMessage(klass: KtClassOrObject): String? {
|
|
||||||
if (klass is KtClass) {
|
if (klass is KtClass) {
|
||||||
if (klass.isInterface()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.interface")
|
if (klass.isInterface()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.interface")
|
||||||
if (klass.isEnum()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.enum")
|
if (klass.isEnum()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.enum")
|
||||||
@@ -83,30 +38,18 @@ object KotlinExtractSuperclassHandler : RefactoringActionHandler, ElementsHandle
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConflicts(originalClass: KtClassOrObject, dialog: KotlinExtractSuperclassDialog): Boolean {
|
override fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) {
|
||||||
val conflicts = ExtractSuperclassRefactoring.collectConflicts(
|
|
||||||
originalClass,
|
|
||||||
dialog.selectedMembers,
|
|
||||||
dialog.selectedTargetParent,
|
|
||||||
dialog.extractedSuperName
|
|
||||||
)
|
|
||||||
return ExtractSuperClassUtil.showConflicts(dialog, conflicts, originalClass.project)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doInvoke(klass: KtClassOrObject, container: PsiElement, project: Project, editor: Editor?) {
|
|
||||||
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return
|
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return
|
||||||
|
|
||||||
getErrorMessage(klass)?.let {
|
getErrorMessage(klass)?.let {
|
||||||
CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(it), REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS)
|
CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(it), REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS)
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetParent = (if (container is SeparateFileWrapper) klass.containingFile.parent else container) ?: return
|
|
||||||
|
|
||||||
KotlinExtractSuperclassDialog(
|
KotlinExtractSuperclassDialog(
|
||||||
originalClass = klass,
|
originalClass = klass,
|
||||||
targetParent = targetParent,
|
targetParent = targetParent,
|
||||||
conflictChecker = { checkConflicts(klass, it) },
|
conflictChecker = { checkConflicts(klass, it) },
|
||||||
refactoring = { ExtractSuperclassRefactoring(it).performRefactoring() }
|
refactoring = { ExtractSuperRefactoring(it).performRefactoring() }
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.refactoring.introduce.extractClass.ui
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.refactoring.HelpID
|
||||||
|
import com.intellij.refactoring.JavaRefactoringSettings
|
||||||
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractInterfaceHandler
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.pullUp.mustBeAbstractInInterface
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
|
||||||
|
class KotlinExtractInterfaceDialog(
|
||||||
|
originalClass: KtClassOrObject,
|
||||||
|
targetParent: PsiElement,
|
||||||
|
conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean,
|
||||||
|
refactoring: (ExtractSuperInfo) -> Unit
|
||||||
|
) : KotlinExtractSuperDialogBase(originalClass, targetParent, conflictChecker, true, KotlinExtractInterfaceHandler.REFACTORING_NAME, refactoring) {
|
||||||
|
companion object {
|
||||||
|
private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractInterfaceDialog.RECENT_KEYS"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createMemberInfoModel(): MemberInfoModelBase {
|
||||||
|
val extractableMemberInfos = extractClassMembers(originalClass).filterNot {
|
||||||
|
val member = it.member
|
||||||
|
member is KtClass && member.hasModifier(KtTokens.INNER_KEYWORD)
|
||||||
|
}
|
||||||
|
extractableMemberInfos.forEach { it.isToAbstract = true }
|
||||||
|
return object : MemberInfoModelBase(extractableMemberInfos) {
|
||||||
|
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||||
|
val member = memberInfo.member
|
||||||
|
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isAbstractWhenDisabled(member: KotlinMemberInfo) = member.member is KtProperty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY
|
||||||
|
|
||||||
|
override fun getClassNameLabelText() = RefactoringBundle.message("interface.name.prompt")!!
|
||||||
|
|
||||||
|
override fun getPackageNameLabelText() = RefactoringBundle.message("package.for.new.interface")!!
|
||||||
|
|
||||||
|
override fun getEntityName() = RefactoringBundle.message("extractSuperInterface.interface")!!
|
||||||
|
|
||||||
|
override fun getTopLabelText() = RefactoringBundle.message("extract.interface.from")!!
|
||||||
|
|
||||||
|
override fun getDocCommentPolicySetting() = JavaRefactoringSettings.getInstance().EXTRACT_INTERFACE_JAVADOC
|
||||||
|
|
||||||
|
override fun setDocCommentPolicySetting(policy: Int) {
|
||||||
|
JavaRefactoringSettings.getInstance().EXTRACT_INTERFACE_JAVADOC = policy
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.interface.name.specified")!!
|
||||||
|
|
||||||
|
override fun getHelpId() = HelpID.EXTRACT_INTERFACE
|
||||||
|
|
||||||
|
override fun createExtractedSuperNameField() = super.createExtractedSuperNameField()!!.apply { text = "I${originalClass.name}" }
|
||||||
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.refactoring.introduce.extractClass.ui
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiComment
|
||||||
|
import com.intellij.psi.PsiDirectory
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
|
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel
|
||||||
|
import com.intellij.refactoring.classMembers.MemberInfoChange
|
||||||
|
import com.intellij.refactoring.extractSuperclass.JavaExtractSuperBaseDialog
|
||||||
|
import com.intellij.refactoring.util.DocCommentPolicy
|
||||||
|
import com.intellij.refactoring.util.RefactoringMessageUtil
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.asJava.unwrapped
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
|
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||||
|
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
||||||
|
import org.jetbrains.kotlin.idea.core.unquote
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import javax.swing.Box
|
||||||
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import javax.swing.JTextField
|
||||||
|
|
||||||
|
abstract class KotlinExtractSuperDialogBase(
|
||||||
|
protected val originalClass: KtClassOrObject,
|
||||||
|
protected val targetParent: PsiElement,
|
||||||
|
private val conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean,
|
||||||
|
private val isExtractInterface: Boolean,
|
||||||
|
refactoringName: String,
|
||||||
|
private val refactoring: (ExtractSuperInfo) -> Unit
|
||||||
|
) : JavaExtractSuperBaseDialog(originalClass.project, originalClass.toLightClass()!!, emptyList(), refactoringName) {
|
||||||
|
private lateinit var memberInfoModel: MemberInfoModelBase
|
||||||
|
|
||||||
|
val selectedMembers: List<KotlinMemberInfo>
|
||||||
|
get() = memberInfoModel.memberInfos.filter { it.isChecked }
|
||||||
|
|
||||||
|
private val fileNameField = JTextField()
|
||||||
|
|
||||||
|
open class MemberInfoModelBase(
|
||||||
|
val memberInfos: List<KotlinMemberInfo>
|
||||||
|
) : AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo>() {
|
||||||
|
override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedTargetParent: PsiElement
|
||||||
|
get() = if (targetParent is PsiDirectory) targetDirectory else targetParent
|
||||||
|
|
||||||
|
val targetFileName: String
|
||||||
|
get() = fileNameField.text
|
||||||
|
|
||||||
|
protected abstract fun createMemberInfoModel(): MemberInfoModelBase
|
||||||
|
|
||||||
|
override fun getDocCommentPanelName() = "KDoc for abstracts"
|
||||||
|
|
||||||
|
override fun checkConflicts() = conflictChecker(this)
|
||||||
|
|
||||||
|
override fun createActionComponent() = Box.createHorizontalBox()!!
|
||||||
|
|
||||||
|
override fun createDestinationRootPanel() = if (targetParent is PsiDirectory) super.createDestinationRootPanel() else null
|
||||||
|
|
||||||
|
override fun createNorthPanel(): JComponent? {
|
||||||
|
return super.createNorthPanel().apply {
|
||||||
|
if (targetParent !is PsiDirectory) {
|
||||||
|
myPackageNameLabel.parent.remove(myPackageNameLabel)
|
||||||
|
myPackageNameField.parent.remove(myPackageNameField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent? {
|
||||||
|
memberInfoModel = createMemberInfoModel().apply {
|
||||||
|
memberInfoChanged(MemberInfoChange(memberInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
return JPanel(BorderLayout()).apply {
|
||||||
|
val memberSelectionPanel = KotlinMemberSelectionPanel(
|
||||||
|
RefactoringBundle.message(if (isExtractInterface) "members.to.form.interface" else "members.to.form.superclass"),
|
||||||
|
memberInfoModel.memberInfos,
|
||||||
|
RefactoringBundle.message("make.abstract")
|
||||||
|
)
|
||||||
|
memberSelectionPanel.table.memberInfoModel = memberInfoModel
|
||||||
|
memberSelectionPanel.table.addMemberInfoChangeListener(memberInfoModel)
|
||||||
|
add(memberSelectionPanel, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
add(myDocCommentPanel, BorderLayout.EAST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
fileNameField.text = "$extractedSuperName.${KotlinFileType.EXTENSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun preparePackage() {
|
||||||
|
if (targetParent is PsiDirectory) super.preparePackage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isExtractSuperclass() = true
|
||||||
|
|
||||||
|
override fun validateName(name: String): String? {
|
||||||
|
return when {
|
||||||
|
!KotlinNameSuggester.isIdentifier(name.quoteIfNeeded()) -> RefactoringMessageUtil.getIncorrectIdentifierMessage(name)
|
||||||
|
name.unquote() == mySourceClass.name -> "Different name expected"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createProcessor() = null
|
||||||
|
|
||||||
|
override fun executeRefactoring() {
|
||||||
|
val extractInfo = ExtractSuperInfo(
|
||||||
|
mySourceClass.unwrapped as KtClassOrObject,
|
||||||
|
selectedMembers,
|
||||||
|
if (targetParent is PsiDirectory) targetDirectory else targetParent,
|
||||||
|
targetFileName,
|
||||||
|
extractedSuperName.quoteIfNeeded(),
|
||||||
|
isExtractInterface,
|
||||||
|
DocCommentPolicy<PsiComment>(docCommentPolicy)
|
||||||
|
)
|
||||||
|
refactoring(extractInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-142
@@ -16,81 +16,39 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui
|
||||||
|
|
||||||
import com.intellij.psi.PsiComment
|
|
||||||
import com.intellij.psi.PsiDirectory
|
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.refactoring.HelpID
|
import com.intellij.refactoring.HelpID
|
||||||
import com.intellij.refactoring.JavaRefactoringSettings
|
import com.intellij.refactoring.JavaRefactoringSettings
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
|
||||||
import com.intellij.refactoring.classMembers.MemberInfoChange
|
|
||||||
import com.intellij.refactoring.extractSuperclass.ExtractSuperBaseDialog
|
|
||||||
import com.intellij.refactoring.extractSuperclass.JavaExtractSuperBaseDialog
|
|
||||||
import com.intellij.refactoring.util.DocCommentPolicy
|
|
||||||
import com.intellij.refactoring.util.RefactoringMessageUtil
|
|
||||||
import com.intellij.ui.components.JBLabel
|
|
||||||
import com.intellij.util.ui.FormBuilder
|
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
|
||||||
import org.jetbrains.kotlin.asJava.unwrapped
|
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
|
||||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
|
||||||
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
|
||||||
import org.jetbrains.kotlin.idea.core.unquote
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassInfo
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
import java.awt.BorderLayout
|
|
||||||
import javax.swing.*
|
|
||||||
|
|
||||||
class KotlinExtractSuperclassDialog(
|
class KotlinExtractSuperclassDialog(
|
||||||
originalClass: KtClassOrObject,
|
originalClass: KtClassOrObject,
|
||||||
private val targetParent: PsiElement,
|
targetParent: PsiElement,
|
||||||
private val conflictChecker: (KotlinExtractSuperclassDialog) -> Boolean,
|
conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean,
|
||||||
private val refactoring: (ExtractSuperclassInfo) -> Unit
|
refactoring: (ExtractSuperInfo) -> Unit
|
||||||
) : JavaExtractSuperBaseDialog(
|
) : KotlinExtractSuperDialogBase(originalClass, targetParent, conflictChecker, false, KotlinExtractSuperclassHandler.REFACTORING_NAME, refactoring) {
|
||||||
originalClass.project,
|
|
||||||
originalClass.toLightClass()!!,
|
|
||||||
emptyList(),
|
|
||||||
KotlinExtractSuperclassHandler.REFACTORING_NAME
|
|
||||||
) {
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractSuperclassDialog.RECENT_KEYS"
|
private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractSuperclassDialog.RECENT_KEYS"
|
||||||
}
|
}
|
||||||
|
|
||||||
val kotlinMemberInfos = extractClassMembers(originalClass)
|
|
||||||
|
|
||||||
val selectedMembers: List<KotlinMemberInfo>
|
|
||||||
get() = kotlinMemberInfos.filter { it.isChecked }
|
|
||||||
|
|
||||||
private val fileNameField = JTextField()
|
|
||||||
|
|
||||||
private val memberInfoModel = object : AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo>() {
|
|
||||||
override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true
|
|
||||||
|
|
||||||
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
|
||||||
val member = memberInfo.member
|
|
||||||
return member is KtNamedFunction || member is KtProperty
|
|
||||||
}
|
|
||||||
}.apply {
|
|
||||||
memberInfoChanged(MemberInfoChange(kotlinMemberInfos))
|
|
||||||
}
|
|
||||||
|
|
||||||
val selectedTargetParent: PsiElement
|
|
||||||
get() = if (targetParent is PsiDirectory) targetDirectory else targetParent
|
|
||||||
|
|
||||||
val targetFileName: String
|
|
||||||
get() = fileNameField.text
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
init()
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
fileNameField.text = "$extractedSuperName.${KotlinFileType.EXTENSION}"
|
override fun createMemberInfoModel(): MemberInfoModelBase {
|
||||||
|
return object : MemberInfoModelBase(extractClassMembers(originalClass)) {
|
||||||
|
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||||
|
val member = memberInfo.member
|
||||||
|
return member is KtNamedFunction || member is KtProperty
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY
|
override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY
|
||||||
@@ -109,94 +67,7 @@ class KotlinExtractSuperclassDialog(
|
|||||||
JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC = policy
|
JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDocCommentPanelName() = "KDoc for abstracts"
|
|
||||||
|
|
||||||
override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.superclass.name.specified")!!
|
override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.superclass.name.specified")!!
|
||||||
|
|
||||||
override fun getHelpId() = HelpID.EXTRACT_SUPERCLASS
|
override fun getHelpId() = HelpID.EXTRACT_SUPERCLASS
|
||||||
|
|
||||||
override fun validateName(name: String): String? {
|
|
||||||
return when {
|
|
||||||
!KotlinNameSuggester.isIdentifier(name.quoteIfNeeded()) -> RefactoringMessageUtil.getIncorrectIdentifierMessage(name)
|
|
||||||
name.unquote() == mySourceClass.name -> "Different name expected"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkConflicts() = conflictChecker(this)
|
|
||||||
|
|
||||||
override fun createActionComponent() = Box.createHorizontalBox()!!
|
|
||||||
|
|
||||||
override fun createDestinationRootPanel(): JPanel? {
|
|
||||||
if (targetParent !is PsiDirectory) return null
|
|
||||||
|
|
||||||
val targetDirectoryPanel = super.createDestinationRootPanel()
|
|
||||||
val targetFileNamePanel = JPanel(BorderLayout()).apply {
|
|
||||||
border = BorderFactory.createEmptyBorder(10, 0, 0, 0)
|
|
||||||
val label = JBLabel("Target file name:")
|
|
||||||
add(label, BorderLayout.NORTH)
|
|
||||||
label.labelFor = fileNameField
|
|
||||||
add(fileNameField, BorderLayout.CENTER)
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormBuilder
|
|
||||||
.createFormBuilder()
|
|
||||||
.addComponent(targetDirectoryPanel)
|
|
||||||
.addComponent(targetFileNamePanel)
|
|
||||||
.panel
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createNorthPanel(): JComponent? {
|
|
||||||
return super.createNorthPanel().apply {
|
|
||||||
if (targetParent !is PsiDirectory) {
|
|
||||||
myPackageNameLabel.parent.remove(myPackageNameLabel)
|
|
||||||
myPackageNameField.parent.remove(myPackageNameField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent? {
|
|
||||||
return JPanel(BorderLayout()).apply {
|
|
||||||
val memberSelectionPanel = KotlinMemberSelectionPanel(
|
|
||||||
RefactoringBundle.message("members.to.form.superclass"),
|
|
||||||
kotlinMemberInfos,
|
|
||||||
RefactoringBundle.message("make.abstract")
|
|
||||||
)
|
|
||||||
memberSelectionPanel.table.memberInfoModel = memberInfoModel
|
|
||||||
memberSelectionPanel.table.addMemberInfoChangeListener(memberInfoModel)
|
|
||||||
add(memberSelectionPanel, BorderLayout.CENTER)
|
|
||||||
|
|
||||||
add(myDocCommentPanel, BorderLayout.EAST)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isExtractSuperclass() = true
|
|
||||||
|
|
||||||
override fun preparePackage() {
|
|
||||||
if (targetParent !is PsiDirectory) return
|
|
||||||
|
|
||||||
super.preparePackage()
|
|
||||||
|
|
||||||
val fileName = targetFileName
|
|
||||||
if (!fileName.endsWith(".${KotlinFileType.EXTENSION}")) {
|
|
||||||
throw ExtractSuperBaseDialog.OperationFailedException("Invalid Kotlin file name: $fileName")
|
|
||||||
}
|
|
||||||
RefactoringMessageUtil.checkCanCreateFile(myTargetDirectory, fileName)?.let {
|
|
||||||
throw ExtractSuperBaseDialog.OperationFailedException(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createProcessor() = null
|
|
||||||
|
|
||||||
override fun executeRefactoring() {
|
|
||||||
val extractInfo = ExtractSuperclassInfo(
|
|
||||||
mySourceClass.unwrapped as KtClassOrObject,
|
|
||||||
selectedMembers,
|
|
||||||
if (targetParent is PsiDirectory) targetDirectory else targetParent,
|
|
||||||
targetFileName,
|
|
||||||
extractedSuperName.quoteIfNeeded(),
|
|
||||||
DocCommentPolicy<PsiComment>(docCommentPolicy)
|
|
||||||
)
|
|
||||||
refactoring(extractInfo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// NAME: X
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface T {}
|
||||||
|
|
||||||
|
open class A
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class <caret>B : A(), T {
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// NAME: X
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface T {}
|
||||||
|
|
||||||
|
open class A
|
||||||
|
|
||||||
|
interface X : T {
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class B : A(), X {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// NAME: X
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface T {}
|
||||||
|
|
||||||
|
open class A
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class <caret>B : A(), T {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// NAME: X
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface T {}
|
||||||
|
|
||||||
|
open class A
|
||||||
|
|
||||||
|
interface X : T {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class B : A(), X {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
override fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// NAME: B
|
||||||
|
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface I<T>
|
||||||
|
|
||||||
|
open class J<T>
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class <caret>A<T, U : List<T>, V, W, X> : J<X>(), I<W> {
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
fun foo(u: U) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// NAME: B
|
||||||
|
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface I<T>
|
||||||
|
|
||||||
|
open class J<T>
|
||||||
|
|
||||||
|
interface B<T, U : List<T>, W> : I<W> {
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
fun foo(u: U) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class A<T, U : List<T>, V, W, X> : J<X>(), B<T, U, W> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// NAME: B
|
||||||
|
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface I<T>
|
||||||
|
|
||||||
|
open class J<T>
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class <caret>A<T, U : List<T>, V, W, X> : J<X>(), I<W> {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
fun foo() {
|
||||||
|
val u: U
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// NAME: B
|
||||||
|
|
||||||
|
// INFO: {checked: "true"}
|
||||||
|
interface I<T>
|
||||||
|
|
||||||
|
open class J<T>
|
||||||
|
|
||||||
|
interface B<W> : I<W> {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIBLING:
|
||||||
|
class A<T, U : List<T>, V, W, X> : J<X>(), B<W> {
|
||||||
|
// INFO: {checked: "true", toAbstract: "true"}
|
||||||
|
override fun foo() {
|
||||||
|
val u: U
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// NAME: B
|
||||||
|
// SIBLING:
|
||||||
|
annotation class <caret>A
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Interface cannot be extracted from an annotation class
|
||||||
+11
-6
@@ -42,8 +42,8 @@ import org.jetbrains.kotlin.idea.KotlinFileType
|
|||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||||
import org.jetbrains.kotlin.idea.refactoring.chooseMembers
|
import org.jetbrains.kotlin.idea.refactoring.chooseMembers
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassInfo
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassRefactoring
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperRefactoring
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||||
@@ -354,7 +354,7 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun doExtractSuperclassTest(path: String) {
|
protected fun doExtractSuperTest(path: String, isInterface: Boolean) {
|
||||||
doTest(path) { file ->
|
doTest(path) { file ->
|
||||||
file as KtFile
|
file as KtFile
|
||||||
|
|
||||||
@@ -366,21 +366,26 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
|
|||||||
val editor = fixture.editor
|
val editor = fixture.editor
|
||||||
val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType<KtClassOrObject>()!!
|
val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType<KtClassOrObject>()!!
|
||||||
val memberInfos = chooseMembers(extractClassMembers(originalClass))
|
val memberInfos = chooseMembers(extractClassMembers(originalClass))
|
||||||
val conflicts = ExtractSuperclassRefactoring.collectConflicts(originalClass, memberInfos, targetParent, className)
|
val conflicts = ExtractSuperRefactoring.collectConflicts(originalClass, memberInfos, targetParent, className, isInterface)
|
||||||
project.checkConflictsInteractively(conflicts) {
|
project.checkConflictsInteractively(conflicts) {
|
||||||
val extractInfo = ExtractSuperclassInfo(
|
val extractInfo = ExtractSuperInfo(
|
||||||
originalClass,
|
originalClass,
|
||||||
memberInfos,
|
memberInfos,
|
||||||
targetParent,
|
targetParent,
|
||||||
"$className.${KotlinFileType.EXTENSION}",
|
"$className.${KotlinFileType.EXTENSION}",
|
||||||
className,
|
className,
|
||||||
|
isInterface,
|
||||||
DocCommentPolicy<PsiComment>(DocCommentPolicy.ASIS)
|
DocCommentPolicy<PsiComment>(DocCommentPolicy.ASIS)
|
||||||
)
|
)
|
||||||
ExtractSuperclassRefactoring(extractInfo).performRefactoring()
|
ExtractSuperRefactoring(extractInfo).performRefactoring()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun doExtractSuperclassTest(path: String) = doExtractSuperTest(path, false)
|
||||||
|
|
||||||
|
protected fun doExtractInterfaceTest(path: String) = doExtractSuperTest(path, true)
|
||||||
|
|
||||||
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
|
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
|
||||||
val mainFile = File(path)
|
val mainFile = File(path)
|
||||||
val afterFile = File("$path.after")
|
val afterFile = File("$path.after")
|
||||||
|
|||||||
+39
@@ -4258,4 +4258,43 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
|||||||
doExtractSuperclassTest(fileName);
|
doExtractSuperclassTest(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/refactoring/extractInterface")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class ExtractInterface extends AbstractExtractionTest {
|
||||||
|
@TestMetadata("addInterface.kt")
|
||||||
|
public void testAddInterface() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addInterface.kt");
|
||||||
|
doExtractInterfaceTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("addInterfaceWithAbstract.kt")
|
||||||
|
public void testAddInterfaceWithAbstract() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt");
|
||||||
|
doExtractInterfaceTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("addTypeParameters.kt")
|
||||||
|
public void testAddTypeParameters() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addTypeParameters.kt");
|
||||||
|
doExtractInterfaceTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("addTypeParametersWithAbstract.kt")
|
||||||
|
public void testAddTypeParametersWithAbstract() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt");
|
||||||
|
doExtractInterfaceTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInExtractInterface() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractInterface"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("annotation.kt")
|
||||||
|
public void testAnnotation() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/annotation.kt");
|
||||||
|
doExtractInterfaceTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user