Refactoring: Extract callable hierarchy logic from JetChangeSignature
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-115
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.quickfix.QuickFixUtil
|
|||||||
import com.intellij.CommonBundle
|
import com.intellij.CommonBundle
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import org.jetbrains.kotlin.resolve.OverrideResolver
|
import org.jetbrains.kotlin.resolve.OverrideResolver
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
|
||||||
|
|
||||||
public trait JetChangeSignatureConfiguration {
|
public trait JetChangeSignatureConfiguration {
|
||||||
fun configure(changeSignatureData: JetChangeSignatureData, bindingContext: BindingContext)
|
fun configure(changeSignatureData: JetChangeSignatureData, bindingContext: BindingContext)
|
||||||
@@ -53,79 +54,30 @@ public fun runChangeSignature(project: Project,
|
|||||||
JetChangeSignature(project, functionDescriptor, configuration, bindingContext, defaultValueContext, commandName).run()
|
JetChangeSignature(project, functionDescriptor, configuration, bindingContext, defaultValueContext, commandName).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
public class JetChangeSignature(val project: Project,
|
public class JetChangeSignature(project: Project,
|
||||||
val functionDescriptor: FunctionDescriptor,
|
functionDescriptor: FunctionDescriptor,
|
||||||
val configuration: JetChangeSignatureConfiguration,
|
val configuration: JetChangeSignatureConfiguration,
|
||||||
val bindingContext: BindingContext,
|
bindingContext: BindingContext,
|
||||||
val defaultValueContext: PsiElement,
|
val defaultValueContext: PsiElement,
|
||||||
val commandName: String?) {
|
commandName: String?): CallableRefactoring<FunctionDescriptor>(project, functionDescriptor, bindingContext, commandName) {
|
||||||
|
|
||||||
private val LOG = Logger.getInstance(javaClass<JetChangeSignature>())
|
private val LOG = Logger.getInstance(javaClass<JetChangeSignature>())
|
||||||
|
|
||||||
public fun run() {
|
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
|
||||||
if (functionDescriptor.getKind() == SYNTHESIZED) {
|
assert (descriptorsForChange.all { it is FunctionDescriptor }) {
|
||||||
LOG.error("Change signature refactoring should not be called for synthesized member " + functionDescriptor)
|
"Function descriptors expected: " + descriptorsForChange.joinToString(separator = "\n")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val closestModifiableDescriptors = getClosestModifiableDescriptors()
|
[suppress("UNCHECKED_CAST")]
|
||||||
assert(!closestModifiableDescriptors.isEmpty(), "Should contain functionDescriptor itself or some of its super declarations")
|
val dialog = createChangeSignatureDialog(descriptorsForChange as Collection<FunctionDescriptor>)
|
||||||
val deepestSuperDeclarations = OverrideResolver.getDeepestSuperDeclarations(functionDescriptor)
|
if (dialog == null) return
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
val affectedFunctions = dialog.getMethodDescriptor().affectedFunctions.map { it.getElement() }.filterNotNull()
|
val affectedFunctions = dialog.getMethodDescriptor().affectedFunctions.map { it.getElement() }.filterNotNull()
|
||||||
|
|
||||||
if (affectedFunctions.any { !checkModifiable(it) }) {
|
if (affectedFunctions.any { !checkModifiable(it) }) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.performSilently(affectedFunctions)
|
if (configuration.performSilently(affectedFunctions)
|
||||||
|| ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
|| ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
||||||
performRefactoringSilently(dialog)
|
performRefactoringSilently(dialog)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -150,24 +102,6 @@ public class JetChangeSignature(val project: Project,
|
|||||||
return JetChangeSignatureDialog(project, changeSignatureData, defaultValueContext, commandName)
|
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) {
|
private fun performRefactoringSilently(dialog: JetChangeSignatureDialog) {
|
||||||
ApplicationManager.getApplication()!!.runWriteAction {
|
ApplicationManager.getApplication()!!.runWriteAction {
|
||||||
dialog.createRefactoringProcessor().run()
|
dialog.createRefactoringProcessor().run()
|
||||||
@@ -185,42 +119,6 @@ public class JetChangeSignature(val project: Project,
|
|||||||
//choose at random
|
//choose at random
|
||||||
return descriptorsForSignatureChange.first()
|
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,
|
TestOnly public fun getChangeSignatureDialog(project: Project,
|
||||||
|
|||||||
Reference in New Issue
Block a user