Refactoring: Extract callable hierarchy logic from JetChangeSignature

This commit is contained in:
Alexey Sedunov
2015-02-02 16:50:30 +03:00
parent 3ca51a516e
commit 15ebbdb349
2 changed files with 183 additions and 115 deletions
@@ -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<T: CallableDescriptor>(
val project: Project,
val callableDescriptor: T,
val bindingContext: BindingContext,
val commandName: String?) {
private val LOG = Logger.getInstance(javaClass<CallableRefactoring<*>>())
private val kind = (callableDescriptor as? CallableMemberDescriptor)?.getKind() ?: CallableMemberDescriptor.Kind.DECLARATION
private fun getClosestModifiableDescriptors(): Set<CallableDescriptor> {
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<CallableDescriptor>,
callableFromEditor: CallableDescriptor,
options: List<String>): 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<CallableDescriptor>)
public fun run() {
fun buttonPressed(code: Int, dialogButtons: List<String>, button: String): Boolean {
return code == dialogButtons indexOf button && button in dialogButtons
}
fun performForWholeHierarchy(dialogButtons: List<String>, code: Int): Boolean {
return buttonPressed(code, dialogButtons, Messages.YES_BUTTON) || buttonPressed(code, dialogButtons, Messages.OK_BUTTON)
}
fun performForSelectedFunctionOnly(dialogButtons: List<String>, code: Int): Boolean {
return buttonPressed(code, dialogButtons, Messages.NO_BUTTON)
}
fun buildDialogOptions(isSingleFunctionSelected: Boolean): List<String> {
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
}
}
}
}
@@ -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<FunctionDescriptor>(project, functionDescriptor, bindingContext, commandName) {
private val LOG = Logger.getInstance(javaClass<JetChangeSignature>())
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<CallableDescriptor>) {
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<FunctionDescriptor> {
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<FunctionDescriptor>) {
val dialog = createChangeSignatureDialog(descriptorsForSignatureChange)
if (dialog == null) {
return
}
[suppress("UNCHECKED_CAST")]
val dialog = createChangeSignatureDialog(descriptorsForChange as Collection<FunctionDescriptor>)
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<String> {
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<String>, code: Int): Boolean {
return buttonPressed(code, dialogButtons, Messages.YES_BUTTON) || buttonPressed(code, dialogButtons, Messages.OK_BUTTON)
}
private fun performForSelectedFunctionOnly(dialogButtons: List<String>, code: Int): Boolean {
return buttonPressed(code, dialogButtons, Messages.NO_BUTTON)
}
private fun buttonPressed(code: Int, dialogButtons: List<String>, button: String): Boolean {
return code == dialogButtons indexOf button && button in dialogButtons
}
private fun showSuperFunctionWarningDialog(superFunctions: Collection<FunctionDescriptor>,
functionFromEditor: FunctionDescriptor,
options: List<String>): 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,