From 15ebbdb349cda41f8b4f730b56a9263d602e12b6 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 2 Feb 2015 16:50:30 +0300 Subject: [PATCH] Refactoring: Extract callable hierarchy logic from JetChangeSignature --- .../idea/refactoring/CallableRefactoring.kt | 170 ++++++++++++++++++ .../changeSignature/JetChangeSignature.kt | 128 ++----------- 2 files changed, 183 insertions(+), 115 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt new file mode 100644 index 00000000000..42d8c0d77ed --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt @@ -0,0 +1,170 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.resolve.BindingContext +import com.intellij.psi.PsiElement +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED +import org.jetbrains.kotlin.resolve.OverrideResolver +import com.intellij.openapi.application.ApplicationManager +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION +import java.util.Collections +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE +import org.jetbrains.kotlin.idea.quickfix.QuickFixUtil +import com.intellij.refactoring.RefactoringBundle +import com.intellij.ide.IdeBundle +import com.intellij.openapi.ui.Messages +import com.intellij.CommonBundle +import org.jetbrains.kotlin.idea.JetBundle +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.psi.JetDeclaration +import java.util.HashSet +import com.intellij.usageView.UsageInfo +import org.jetbrains.kotlin.psi.JetNamedFunction +import org.jetbrains.kotlin.asJava.LightClassUtil +import com.intellij.psi.search.searches.OverridingMethodsSearch +import org.jetbrains.kotlin.asJava.KotlinLightMethod +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JetFunctionDefinitionUsage +import com.intellij.refactoring.changeSignature.OverriderUsageInfo +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToDeclarationUtil +import org.jetbrains.kotlin.asJava.toLightMethods +import org.jetbrains.kotlin.asJava.namedUnwrappedElement + +public abstract class CallableRefactoring( + val project: Project, + val callableDescriptor: T, + val bindingContext: BindingContext, + val commandName: String?) { + private val LOG = Logger.getInstance(javaClass>()) + + private val kind = (callableDescriptor as? CallableMemberDescriptor)?.getKind() ?: CallableMemberDescriptor.Kind.DECLARATION + + private fun getClosestModifiableDescriptors(): Set { + return when (kind) { + DECLARATION -> { + Collections.singleton(callableDescriptor) + } + DELEGATION, FAKE_OVERRIDE -> { + OverrideResolver.getDirectlyOverriddenDeclarations(callableDescriptor as CallableMemberDescriptor) + } + else -> { + throw IllegalStateException("Unexpected callable kind: ${kind}") + } + } + } + + private fun showSuperFunctionWarningDialog(superCallables: Collection, + callableFromEditor: CallableDescriptor, + options: List): Int { + val superString = superCallables.map { + it.getContainingDeclaration().getName().asString() + }.joinToString(prefix = "\n ", separator = ",\n ", postfix = ".\n\n") + val message = JetBundle.message("x.overrides.y.in.class.list", + DescriptorRenderer.COMPACT.render(callableFromEditor), + callableFromEditor.getContainingDeclaration().getName().asString(), superString, + "refactor") + val title = IdeBundle.message("title.warning")!! + val icon = Messages.getQuestionIcon()!! + return Messages.showDialog(message, title, options.copyToArray(), 0, icon) + } + + protected fun checkModifiable(element: PsiElement): Boolean { + if (QuickFixUtil.canModifyElement(element)) { + return true + } + + val unmodifiableFile = element.getContainingFile()?.getVirtualFile()?.getPresentableUrl() + if (unmodifiableFile != null) { + val message = RefactoringBundle.message("refactoring.cannot.be.performed") + "\n" + + IdeBundle.message("error.message.cannot.modify.file.0", unmodifiableFile) + Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle()!!) + } + else { + LOG.error("Could not find file for Psi element: " + element.getText()) + } + + return false + } + + protected abstract fun performRefactoring(descriptorsForChange: Collection) + + public fun run() { + fun buttonPressed(code: Int, dialogButtons: List, button: String): Boolean { + return code == dialogButtons indexOf button && button in dialogButtons + } + + fun performForWholeHierarchy(dialogButtons: List, code: Int): Boolean { + return buttonPressed(code, dialogButtons, Messages.YES_BUTTON) || buttonPressed(code, dialogButtons, Messages.OK_BUTTON) + } + + fun performForSelectedFunctionOnly(dialogButtons: List, code: Int): Boolean { + return buttonPressed(code, dialogButtons, Messages.NO_BUTTON) + } + + fun buildDialogOptions(isSingleFunctionSelected: Boolean): List { + if (isSingleFunctionSelected) { + return arrayListOf(Messages.YES_BUTTON, Messages.NO_BUTTON, Messages.CANCEL_BUTTON) + } + else { + return arrayListOf(Messages.OK_BUTTON, Messages.CANCEL_BUTTON) + } + } + + if (kind == SYNTHESIZED) { + LOG.error("Change signature refactoring should not be called for synthesized member " + callableDescriptor) + return + } + + val closestModifiableDescriptors = getClosestModifiableDescriptors() + assert(!closestModifiableDescriptors.isEmpty(), "Should contain original declaration or some of its super declarations") + val deepestSuperDeclarations = + (callableDescriptor as? CallableMemberDescriptor)?.let { OverrideResolver.getDeepestSuperDeclarations(it) } + ?: Collections.singletonList(callableDescriptor) + if (ApplicationManager.getApplication()!!.isUnitTestMode()) { + performRefactoring(deepestSuperDeclarations) + return + } + + if (closestModifiableDescriptors.size() == 1 && deepestSuperDeclarations == closestModifiableDescriptors) { + performRefactoring(closestModifiableDescriptors) + return + } + + val isSingleFunctionSelected = closestModifiableDescriptors.size() == 1 + val selectedFunction = if (isSingleFunctionSelected) closestModifiableDescriptors.first() else callableDescriptor + val optionsForDialog = buildDialogOptions(isSingleFunctionSelected) + val code = showSuperFunctionWarningDialog(deepestSuperDeclarations, selectedFunction, optionsForDialog) + when { + performForWholeHierarchy(optionsForDialog, code) -> { + performRefactoring(deepestSuperDeclarations) + } + performForSelectedFunctionOnly(optionsForDialog, code) -> { + performRefactoring(closestModifiableDescriptors) + } + else -> { + //do nothing + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt index 5a575f34f2d..7dc6055e06b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.quickfix.QuickFixUtil import com.intellij.CommonBundle import com.intellij.refactoring.RefactoringBundle import org.jetbrains.kotlin.resolve.OverrideResolver +import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring public trait JetChangeSignatureConfiguration { fun configure(changeSignatureData: JetChangeSignatureData, bindingContext: BindingContext) @@ -53,79 +54,30 @@ public fun runChangeSignature(project: Project, JetChangeSignature(project, functionDescriptor, configuration, bindingContext, defaultValueContext, commandName).run() } -public class JetChangeSignature(val project: Project, - val functionDescriptor: FunctionDescriptor, +public class JetChangeSignature(project: Project, + functionDescriptor: FunctionDescriptor, val configuration: JetChangeSignatureConfiguration, - val bindingContext: BindingContext, + bindingContext: BindingContext, val defaultValueContext: PsiElement, - val commandName: String?) { + commandName: String?): CallableRefactoring(project, functionDescriptor, bindingContext, commandName) { private val LOG = Logger.getInstance(javaClass()) - public fun run() { - if (functionDescriptor.getKind() == SYNTHESIZED) { - LOG.error("Change signature refactoring should not be called for synthesized member " + functionDescriptor) - return + override fun performRefactoring(descriptorsForChange: Collection) { + assert (descriptorsForChange.all { it is FunctionDescriptor }) { + "Function descriptors expected: " + descriptorsForChange.joinToString(separator = "\n") } - val closestModifiableDescriptors = getClosestModifiableDescriptors() - assert(!closestModifiableDescriptors.isEmpty(), "Should contain functionDescriptor itself or some of its super declarations") - val deepestSuperDeclarations = OverrideResolver.getDeepestSuperDeclarations(functionDescriptor) - if (ApplicationManager.getApplication()!!.isUnitTestMode()) { - showChangeSignatureDialog(deepestSuperDeclarations) - return - } - - if (closestModifiableDescriptors.size() == 1 && deepestSuperDeclarations == closestModifiableDescriptors) { - showChangeSignatureDialog(closestModifiableDescriptors) - return - } - - val isSingleFunctionSelected = closestModifiableDescriptors.size() == 1 - val selectedFunction = if (isSingleFunctionSelected) closestModifiableDescriptors.first() else functionDescriptor - val optionsForDialog = buildDialogOptions(isSingleFunctionSelected) - val code = showSuperFunctionWarningDialog(deepestSuperDeclarations, selectedFunction, optionsForDialog) - when { - performForWholeHierarchy(optionsForDialog, code) -> { - showChangeSignatureDialog(deepestSuperDeclarations) - } - performForSelectedFunctionOnly(optionsForDialog, code) -> { - showChangeSignatureDialog(closestModifiableDescriptors) - } - else -> { - //do nothing - } - } - } - - private fun getClosestModifiableDescriptors(): Set { - return when (functionDescriptor.getKind()) { - DECLARATION -> { - Collections.singleton(functionDescriptor) - } - DELEGATION, FAKE_OVERRIDE -> { - OverrideResolver.getDirectlyOverriddenDeclarations(functionDescriptor) - } - else -> { - throw IllegalStateException("Unexpected callable kind: ${functionDescriptor.getKind()}") - } - } - } - - private fun showChangeSignatureDialog(descriptorsForSignatureChange: Collection) { - val dialog = createChangeSignatureDialog(descriptorsForSignatureChange) - if (dialog == null) { - return - } + [suppress("UNCHECKED_CAST")] + val dialog = createChangeSignatureDialog(descriptorsForChange as Collection) + if (dialog == null) return val affectedFunctions = dialog.getMethodDescriptor().affectedFunctions.map { it.getElement() }.filterNotNull() - if (affectedFunctions.any { !checkModifiable(it) }) { - return - } + if (affectedFunctions.any { !checkModifiable(it) }) return if (configuration.performSilently(affectedFunctions) - || ApplicationManager.getApplication()!!.isUnitTestMode()) { + || ApplicationManager.getApplication()!!.isUnitTestMode()) { performRefactoringSilently(dialog) } else { @@ -150,24 +102,6 @@ public class JetChangeSignature(val project: Project, return JetChangeSignatureDialog(project, changeSignatureData, defaultValueContext, commandName) } - private fun checkModifiable(function: PsiElement): Boolean { - if (QuickFixUtil.canModifyElement(function)) { - return true - } - - val unmodifiableFile = function.getContainingFile()?.getVirtualFile()?.getPresentableUrl() - if (unmodifiableFile != null) { - val message = RefactoringBundle.message("refactoring.cannot.be.performed") + "\n" + - IdeBundle.message("error.message.cannot.modify.file.0", unmodifiableFile) - Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle()!!) - } - else { - LOG.error("Could not find file for Psi element: " + function.getText()) - } - - return false - } - private fun performRefactoringSilently(dialog: JetChangeSignatureDialog) { ApplicationManager.getApplication()!!.runWriteAction { dialog.createRefactoringProcessor().run() @@ -185,42 +119,6 @@ public class JetChangeSignature(val project: Project, //choose at random return descriptorsForSignatureChange.first() } - - private fun buildDialogOptions(isSingleFunctionSelected: Boolean): List { - if (isSingleFunctionSelected) { - return arrayListOf(Messages.YES_BUTTON, Messages.NO_BUTTON, Messages.CANCEL_BUTTON) - } - else { - return arrayListOf(Messages.OK_BUTTON, Messages.CANCEL_BUTTON) - } - } - - private fun performForWholeHierarchy(dialogButtons: List, code: Int): Boolean { - return buttonPressed(code, dialogButtons, Messages.YES_BUTTON) || buttonPressed(code, dialogButtons, Messages.OK_BUTTON) - } - - private fun performForSelectedFunctionOnly(dialogButtons: List, code: Int): Boolean { - return buttonPressed(code, dialogButtons, Messages.NO_BUTTON) - } - - private fun buttonPressed(code: Int, dialogButtons: List, button: String): Boolean { - return code == dialogButtons indexOf button && button in dialogButtons - } - - private fun showSuperFunctionWarningDialog(superFunctions: Collection, - functionFromEditor: FunctionDescriptor, - options: List): Int { - val superString = superFunctions.map { - it.getContainingDeclaration().getName().asString() - }.joinToString(prefix = "\n ", separator = ",\n ", postfix = ".\n\n") - val message = JetBundle.message("x.overrides.y.in.class.list", - DescriptorRenderer.COMPACT.render(functionFromEditor), - functionFromEditor.getContainingDeclaration().getName().asString(), superString, - "refactor") - val title = IdeBundle.message("title.warning")!! - val icon = Messages.getQuestionIcon()!! - return Messages.showDialog(message, title, options.copyToArray(), 0, icon) - } } TestOnly public fun getChangeSignatureDialog(project: Project,