182: generic PopupChooserBuilder<T> compilation fix

This commit is contained in:
Nicolay Mitropolsky
2018-03-26 11:58:04 +03:00
committed by Nikolay Krasko
parent fdb98fd4ab
commit 1b783070b5
3 changed files with 1522 additions and 0 deletions
@@ -0,0 +1,261 @@
/*
* 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.actions.generate
import com.intellij.codeInsight.generation.actions.GenerateActionPopupTemplateInjector
import com.intellij.codeInsight.hint.HintManager
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.fileTemplates.impl.AllFileTemplatesConfigurable
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.InputValidator
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiFile
import com.intellij.testIntegration.JavaTestFramework
import com.intellij.testIntegration.TestFramework
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
import com.intellij.ui.components.JBList
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.insertMember
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType
import org.jetbrains.kotlin.idea.core.overrideImplement.generateUnsupportedOrSuperCall
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
import org.jetbrains.kotlin.idea.refactoring.j2k
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.utils.ifEmpty
abstract class KotlinGenerateTestSupportActionBase(
private val methodKind : MethodKind
) : KotlinGenerateActionBase(), GenerateActionPopupTemplateInjector {
companion object {
private fun findTargetClass(editor: Editor, file: PsiFile): KtClassOrObject? {
val elementAtCaret = file.findElementAt(editor.caretModel.offset) ?: return null
return elementAtCaret.parentsWithSelf.filterIsInstance<KtClassOrObject>().firstOrNull { !it.isLocal }
}
private fun chooseAndPerform(editor: Editor, frameworks: List<TestFramework>, consumer: (TestFramework) -> Unit) {
frameworks.ifEmpty { return }
frameworks.singleOrNull()?.let { return consumer(it) }
if (ApplicationManager.getApplication().isUnitTestMode) return consumer(frameworks.first())
val list = JBList<TestFramework>(*frameworks.toTypedArray())
list.cellRenderer = TestFrameworkListCellRenderer()
PopupChooserBuilder<TestFramework>(list)
.setFilteringEnabled { (it as TestFramework).name }
.setTitle("Choose Framework")
.setItemChoosenCallback { consumer(list.selectedValue as TestFramework) }
.setMovable(true)
.createPopup()
.showInBestPositionFor(editor)
}
private val BODY_VAR = "\${BODY}"
private val NAME_VAR = "\${NAME}"
private val NAME_VALIDATOR = object : InputValidator {
override fun checkInput(inputString: String) = inputString.quoteIfNeeded().isIdentifier()
override fun canClose(inputString: String) = true
}
}
class SetUp : KotlinGenerateTestSupportActionBase(MethodKind.SET_UP) {
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
return framework.findSetUpMethod(targetClass.toLightClass()!!) == null
}
}
class Test : KotlinGenerateTestSupportActionBase(MethodKind.TEST) {
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject) = true
}
class Data : KotlinGenerateTestSupportActionBase(MethodKind.DATA) {
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
if (framework !is JavaTestFramework) return false
return framework.findParametersMethod(targetClass.toLightClass()) == null
}
}
class TearDown : KotlinGenerateTestSupportActionBase(MethodKind.TEAR_DOWN) {
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
return framework.findTearDownMethod(targetClass.toLightClass()!!) == null
}
}
override fun getTargetClass(editor: Editor, file: PsiFile): KtClassOrObject? {
return findTargetClass(editor, file)
}
override fun isValidForClass(targetClass: KtClassOrObject): Boolean {
return findSuitableFrameworks(targetClass).any { isApplicableTo(it, targetClass) }
}
protected abstract fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
val klass = findTargetClass(editor, file) ?: return
if (testFrameworkToUse != null) {
val frameworkToUse = findSuitableFrameworks(klass).first { it.name == testFrameworkToUse }
if (isApplicableTo(frameworkToUse, klass)) {
doGenerate(editor, file, klass, frameworkToUse)
}
}
else {
val frameworks = findSuitableFrameworks(klass)
.filter { methodKind.getFileTemplateDescriptor(it) != null && isApplicableTo(it, klass) }
chooseAndPerform(editor, frameworks) { doGenerate(editor, file, klass, it) }
}
}
var testFrameworkToUse: String? = null
private val DUMMY_NAME = "__KOTLIN_RULEZZZ__"
private fun doGenerate(editor: Editor, file: PsiFile, klass: KtClassOrObject, framework: TestFramework) {
val project = file.project
val commandName = "Generate test function"
val fileTemplateDescriptor = methodKind.getFileTemplateDescriptor(framework)
val fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(fileTemplateDescriptor.fileName)
var templateText = fileTemplate.text.replace(BODY_VAR, "")
var name: String? = null
if (templateText.contains(NAME_VAR)) {
name = if (templateText.contains("test$NAME_VAR")) "Name" else "name"
if (!ApplicationManager.getApplication().isUnitTestMode) {
name = Messages.showInputDialog("Choose test name: ", commandName, null, name, NAME_VALIDATOR)
?: return
}
templateText = fileTemplate.text.replace(NAME_VAR, DUMMY_NAME)
}
try {
var errorHint: String? = null
project.executeWriteCommand(commandName) {
PsiDocumentManager.getInstance(project).commitAllDocuments()
val factory = PsiElementFactory.SERVICE.getInstance(project)
val psiMethod = factory.createMethodFromText(templateText, null)
psiMethod.throwsList.referenceElements.forEach { it.delete() }
var function = psiMethod.j2k() as? KtNamedFunction ?: run {
errorHint = "Couldn't convert Java template to Kotlin"
return@executeWriteCommand
}
name?.let {
function = substituteNewName(function, it)
}
val functionInPlace = insertMember(editor, klass, function)
val functionDescriptor = functionInPlace.unsafeResolveToDescriptor() as FunctionDescriptor
val overriddenDescriptors = functionDescriptor.overriddenDescriptors
val bodyText = when (overriddenDescriptors.size) {
0 -> generateUnsupportedOrSuperCall(project, functionDescriptor, BodyType.EMPTY)
1 -> generateUnsupportedOrSuperCall(project, overriddenDescriptors.single(), BodyType.SUPER)
else -> generateUnsupportedOrSuperCall(project, overriddenDescriptors.first(), BodyType.QUALIFIED_SUPER)
}
functionInPlace.bodyExpression?.delete()
functionInPlace.add(KtPsiFactory(project).createBlock(bodyText))
if (overriddenDescriptors.isNotEmpty()) {
functionInPlace.addModifier(KtTokens.OVERRIDE_KEYWORD)
}
setupEditorSelection(editor, functionInPlace)
}
errorHint?.let { HintManager.getInstance().showErrorHint(editor, it) }
}
catch (e: IncorrectOperationException) {
HintManager.getInstance().showErrorHint(editor, "Cannot generate method: " + e.message)
}
}
private fun substituteNewName(function: KtNamedFunction, name: String): KtNamedFunction {
val psiFactory = KtPsiFactory(function)
// First replace all DUMMY_NAME occurrences in names as they need special treatment due to quotation
var function1 = function
function1.accept(
object : KtTreeVisitorVoid() {
private fun getNewId(currentId: String): String? {
if (!currentId.contains(DUMMY_NAME)) return null
return currentId.replace(DUMMY_NAME, name).quoteIfNeeded()
}
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
val nameIdentifier = declaration.nameIdentifier ?: return
val newId = getNewId(nameIdentifier.text) ?: return
declaration.setName(newId)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val newId = getNewId(expression.text) ?: return
expression.replace(psiFactory.createSimpleName(newId))
}
}
)
// Then text-replace remaining occurrences (if any)
val functionText = function1.text
if (functionText.contains(DUMMY_NAME)) {
function1 = psiFactory.createFunction(function1.text.replace(DUMMY_NAME, name))
}
return function1
}
override fun createEditTemplateAction(dataContext: DataContext): AnAction? {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return null
val editor = CommonDataKeys.EDITOR.getData(dataContext) ?: return null
val file = CommonDataKeys.PSI_FILE.getData(dataContext) ?: return null
val targetClass = getTargetClass(editor, file) ?: return null
val frameworks = findSuitableFrameworks(targetClass).ifEmpty { return null }
return object : AnAction("Edit Template") {
override fun actionPerformed(e: AnActionEvent) {
chooseAndPerform(editor, frameworks) {
val descriptor = methodKind.getFileTemplateDescriptor(it)
if (descriptor == null) {
HintManager.getInstance().showErrorHint(editor, "No template found for ${it.name}:${templatePresentation.text}")
return@chooseAndPerform
}
AllFileTemplatesConfigurable.editCodeTemplate(FileUtil.getNameWithoutExtension(descriptor.fileName), project)
}
}
}
}
}
@@ -0,0 +1,268 @@
/*
* 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.intentions
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.generation.OverrideImplementUtil
import com.intellij.ide.util.PsiClassListCellRenderer
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.ui.components.JBList
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject
import org.jetbrains.kotlin.idea.refactoring.isAbstract
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import java.util.*
import javax.swing.ListSelectionModel
abstract class ImplementAbstractMemberIntentionBase :
SelfTargetingRangeIntention<KtNamedDeclaration>(KtNamedDeclaration::class.java, "", "Implement abstract member") {
companion object {
private val LOG = Logger.getInstance("#${ImplementAbstractMemberIntentionBase::class.java.canonicalName}")
}
protected fun findExistingImplementation(
subClass: ClassDescriptor,
superMember: CallableMemberDescriptor
): CallableMemberDescriptor? {
val superClass = superMember.containingDeclaration as? ClassDescriptor ?: return null
val substitutor = getTypeSubstitutor(superClass.defaultType, subClass.defaultType) ?: TypeSubstitutor.EMPTY
val signatureInSubClass = superMember.substitute(substitutor) as? CallableMemberDescriptor ?: return null
val subMember = subClass.findCallableMemberBySignature(signatureInSubClass)
return if (subMember?.kind?.isReal == true) subMember else null
}
protected abstract fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean
private fun findClassesToProcess(member: KtNamedDeclaration): Sequence<PsiElement> {
val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence()
val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence()
fun acceptSubClass(subClass: PsiElement): Boolean {
val classDescriptor = when (subClass) {
is KtLightClass -> subClass.kotlinOrigin?.resolveToDescriptorIfAny()
is KtEnumEntry -> subClass.resolveToDescriptorIfAny()
is PsiClass -> subClass.getJavaClassDescriptor()
else -> null
} ?: return false
return acceptSubClass(classDescriptor, memberDescriptor)
}
if (baseClass.isEnum()) {
return baseClass.declarations
.asSequence()
.filterIsInstance<KtEnumEntry>()
.filter(::acceptSubClass)
}
return HierarchySearchRequest(baseClass, baseClass.useScope, false)
.searchInheritors()
.asSequence()
.filter(::acceptSubClass)
}
protected abstract fun computeText(element: KtNamedDeclaration): String?
override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
if (!element.isAbstract()) return null
text = computeText(element) ?: return null
if (!findClassesToProcess(element).any()) return null
return element.nameIdentifier?.textRange
}
protected abstract val preferConstructorParameters: Boolean
private fun implementInKotlinClass(editor: Editor?, member: KtNamedDeclaration, targetClass: KtClassOrObject) {
val subClassDescriptor = targetClass.resolveToDescriptorIfAny() ?: return
val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return
val superClassDescriptor = superMemberDescriptor.containingDeclaration as? ClassDescriptor ?: return
val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, subClassDescriptor.defaultType)
?: TypeSubstitutor.EMPTY
val descriptorToImplement = superMemberDescriptor.substitute(substitutor) as CallableMemberDescriptor
val chooserObject = OverrideMemberChooserObject.create(member.project,
descriptorToImplement,
descriptorToImplement,
OverrideMemberChooserObject.BodyType.EMPTY,
preferConstructorParameters)
OverrideImplementMembersHandler.generateMembers(editor, targetClass, listOf(chooserObject), false)
}
private fun implementInJavaClass(member: KtNamedDeclaration, targetClass: PsiClass) {
member.toLightMethods().forEach { OverrideImplementUtil.overrideOrImplement(targetClass, it) }
}
private fun implementInClass(member: KtNamedDeclaration, targetClasses: List<PsiElement>) {
val project = member.project
project.executeCommand(CodeInsightBundle.message("intention.implement.abstract.method.command.name")) {
if (!FileModificationService.getInstance().preparePsiElementsForWrite(targetClasses)) return@executeCommand
runWriteAction {
for (targetClass in targetClasses) {
try {
val descriptor = OpenFileDescriptor(project, targetClass.containingFile.virtualFile)
val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)!!
when (targetClass) {
is KtLightClass -> targetClass.kotlinOrigin?.let { implementInKotlinClass(targetEditor, member, it) }
is KtEnumEntry -> implementInKotlinClass(targetEditor, member, targetClass)
is PsiClass -> implementInJavaClass(member, targetClass)
}
}
catch(e: IncorrectOperationException) {
LOG.error(e)
}
}
}
}
}
private class ClassRenderer : PsiElementListCellRenderer<PsiElement>() {
private val psiClassRenderer = PsiClassListCellRenderer()
override fun getComparator(): Comparator<PsiElement> {
val baseComparator = psiClassRenderer.comparator
return Comparator { o1, o2 ->
when {
o1 is KtEnumEntry && o2 is KtEnumEntry -> o1.name!!.compareTo(o2.name!!)
o1 is KtEnumEntry -> -1
o2 is KtEnumEntry -> 1
o1 is PsiClass && o2 is PsiClass -> baseComparator.compare(o1, o2)
else -> 0
}
}
}
override fun getIconFlags() = 0
override fun getElementText(element: PsiElement?): String? {
return when (element) {
is KtEnumEntry -> element.name
is PsiClass -> psiClassRenderer.getElementText(element)
else -> null
}
}
override fun getContainerText(element: PsiElement?, name: String?): String? {
return when (element) {
is KtEnumEntry -> element.containingClassOrObject?.fqName?.asString()
is PsiClass -> PsiClassListCellRenderer.getContainerTextStatic(element)
else -> null
}
}
}
override fun applyTo(element: KtNamedDeclaration, editor: Editor?) {
if (editor == null) throw IllegalArgumentException("This intention requires an editor")
val project = element.project
val classesToProcess = project.runSynchronouslyWithProgress(
CodeInsightBundle.message("intention.implement.abstract.method.searching.for.descendants.progress"),
true
) { findClassesToProcess(element).toList() } ?: return
if (classesToProcess.isEmpty()) return
classesToProcess.singleOrNull()?.let { return implementInClass(element, listOf(it)) }
val renderer = ClassRenderer()
val sortedClasses = classesToProcess.sortedWith(renderer.comparator)
if (ApplicationManager.getApplication().isUnitTestMode) return implementInClass(element, sortedClasses)
val list = JBList(sortedClasses).apply {
selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
cellRenderer = renderer
}
val builder = PopupChooserBuilder<PsiElement>(list)
renderer.installSpeedSearch(builder)
builder
.setTitle(CodeInsightBundle.message("intention.implement.abstract.method.class.chooser.title"))
.setItemChoosenCallback {
val index = list.selectedIndex
if (index < 0) return@setItemChoosenCallback
@Suppress("UNCHECKED_CAST")
implementInClass(element, list.selectedValues.toList() as List<KtClassOrObject>)
}
.createPopup()
.showInBestPositionFor(editor)
}
}
class ImplementAbstractMemberIntention : ImplementAbstractMemberIntentionBase() {
override fun computeText(element: KtNamedDeclaration): String? {
return when(element) {
is KtProperty -> "Implement abstract property"
is KtNamedFunction -> "Implement abstract function"
else -> null
}
}
override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean {
return subClassDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(subClassDescriptor, memberDescriptor) == null
}
override val preferConstructorParameters: Boolean
get() = false
}
class ImplementAbstractMemberAsConstructorParameterIntention : ImplementAbstractMemberIntentionBase() {
override fun computeText(element: KtNamedDeclaration): String? {
if (element !is KtProperty) return null
return "Implement as constructor parameter"
}
override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean {
val kind = subClassDescriptor.kind
return (kind == ClassKind.CLASS || kind == ClassKind.ENUM_CLASS)
&& subClassDescriptor !is JavaClassDescriptor
&& findExistingImplementation(subClassDescriptor, memberDescriptor) == null
}
override val preferConstructorParameters: Boolean
get() = true
override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
if (element !is KtProperty) return null
return super.applicabilityRange(element)
}
}
@@ -0,0 +1,993 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.refactoring
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils
import com.intellij.codeInsight.unwrap.RangeSplitter
import com.intellij.codeInsight.unwrap.UnwrapHandler
import com.intellij.ide.IdeBundle
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandAdapter
import com.intellij.openapi.command.CommandEvent
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.*
import com.intellij.openapi.util.Pass
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.presentation.java.SymbolPresentationUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
import com.intellij.refactoring.listeners.RefactoringEventData
import com.intellij.refactoring.listeners.RefactoringEventListener
import com.intellij.refactoring.ui.ConflictsDialog
import com.intellij.refactoring.util.ConflictsUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.ui.components.JBList
import com.intellij.usageView.UsageViewTypeLocation
import com.intellij.util.VisibilityUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.getAccessorLightMethods
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.core.util.showYesNoCancelDialog
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
import java.io.File
import java.lang.annotation.Retention
import java.util.*
import javax.swing.Icon
val CHECK_SUPER_METHODS_YES_NO_DIALOG = "CHECK_SUPER_METHODS_YES_NO_DIALOG"
@JvmOverloads
fun getOrCreateKotlinFile(fileName: String,
targetDir: PsiDirectory,
packageName: String? = targetDir.getPackage()?.qualifiedName): KtFile? =
(targetDir.findFile(fileName) ?: createKotlinFile(fileName, targetDir, packageName)) as? KtFile
fun createKotlinFile(fileName: String,
targetDir: PsiDirectory,
packageName: String? = targetDir.getPackage()?.qualifiedName): KtFile {
targetDir.checkCreateFile(fileName)
val packageFqName = packageName?.let(::FqName) ?: FqName.ROOT
val file = PsiFileFactory.getInstance(targetDir.project).createFileFromText(
fileName, KotlinFileType.INSTANCE, if (!packageFqName.isRoot) "package ${packageFqName.quoteSegmentsIfNeeded()} \n\n" else ""
)
return targetDir.add(file) as KtFile
}
fun File.toVirtualFile(): VirtualFile? = LocalFileSystem.getInstance().findFileByIoFile(this)
fun File.toPsiFile(project: Project): PsiFile? = toVirtualFile()?.toPsiFile(project)
fun File.toPsiDirectory(project: Project): PsiDirectory? {
return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findDirectory(vfile) }
}
fun VirtualFile.toPsiFile(project: Project): PsiFile? = PsiManager.getInstance(project).findFile(this)
fun VirtualFile.toPsiDirectory(project: Project): PsiDirectory? = PsiManager.getInstance(project).findDirectory(this)
fun VirtualFile.toPsiFileOrDirectory(project: Project): PsiFileSystemItem? = if (isDirectory) toPsiDirectory(project) else toPsiFile(project)
fun PsiElement.getUsageContext(): PsiElement {
return when (this) {
is KtElement -> PsiTreeUtil.getParentOfType(
this,
KtPropertyAccessor::class.java,
KtProperty::class.java,
KtNamedFunction::class.java,
KtConstructor::class.java,
KtClassOrObject::class.java
) ?: containingFile
else -> ConflictsUtil.getContainer(this)
}
}
fun PsiElement.isInJavaSourceRoot(): Boolean =
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(containingFile)
fun KtFile.createTempCopy(text: String? = null): KtFile {
val tmpFile = KtPsiFactory(this).createAnalyzableFile(name, text ?: this.text ?: "", this)
tmpFile.originalFile = this
tmpFile.suppressDiagnosticsInDebugMode = suppressDiagnosticsInDebugMode
return tmpFile
}
fun PsiElement.getAllExtractionContainers(strict: Boolean = true): List<KtElement> {
val containers = ArrayList<KtElement>()
var objectOrNonInnerNestedClassFound = false
val parents = if (strict) parents else parentsWithSelf
for (element in parents) {
val isValidContainer = when (element) {
is KtFile -> true
is KtClassBody -> !objectOrNonInnerNestedClassFound || element.parent is KtObjectDeclaration
is KtBlockExpression -> !objectOrNonInnerNestedClassFound
else -> false
}
if (!isValidContainer) continue
containers.add(element as KtElement)
if (!objectOrNonInnerNestedClassFound) {
val bodyParent = (element as? KtClassBody)?.parent
objectOrNonInnerNestedClassFound =
(bodyParent is KtObjectDeclaration && !bodyParent.isObjectLiteral())
|| (bodyParent is KtClass && !bodyParent.isInner())
}
}
return containers
}
fun PsiElement.getExtractionContainers(strict: Boolean = true, includeAll: Boolean = false): List<KtElement> {
fun getEnclosingDeclaration(element: PsiElement, strict: Boolean): PsiElement? {
return (if (strict) element.parents else element.parentsWithSelf)
.filter {
(it is KtDeclarationWithBody && it !is KtFunctionLiteral && !(it is KtNamedFunction && it.name == null))
|| it is KtAnonymousInitializer
|| it is KtClassBody
|| it is KtFile
}
.firstOrNull()
}
if (includeAll) return getAllExtractionContainers(strict)
val enclosingDeclaration = getEnclosingDeclaration(this, strict)?.let {
if (it is KtDeclarationWithBody || it is KtAnonymousInitializer) getEnclosingDeclaration(it, true) else it
}
return when (enclosingDeclaration) {
is KtFile -> Collections.singletonList(enclosingDeclaration)
is KtClassBody -> getAllExtractionContainers(strict).filterIsInstance<KtClassBody>()
else -> {
val targetContainer = when (enclosingDeclaration) {
is KtDeclarationWithBody -> enclosingDeclaration.bodyExpression
is KtAnonymousInitializer -> enclosingDeclaration.body
else -> null
}
if (targetContainer is KtBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList()
}
}
}
fun Project.checkConflictsInteractively(
conflicts: MultiMap<PsiElement, String>,
onShowConflicts: () -> Unit = {},
onAccept: () -> Unit) {
if (!conflicts.isEmpty) {
if (ApplicationManager.getApplication()!!.isUnitTestMode) throw ConflictsInTestsException(conflicts.values())
val dialog = ConflictsDialog(this, conflicts) { onAccept() }
dialog.show()
if (!dialog.isOK) {
if (dialog.isShowConflicts) {
onShowConflicts()
}
return
}
}
onAccept()
}
fun reportDeclarationConflict(
conflicts: MultiMap<PsiElement, String>,
declaration: PsiElement,
message: (renderedDeclaration: String) -> String
) {
conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize()))
}
fun <T, E : PsiElement> getPsiElementPopup(
editor: Editor,
elements: List<T>,
renderer: PsiElementListCellRenderer<E>,
title: String?,
highlightSelection: Boolean,
toPsi: (T) -> E,
processor: (T) -> Boolean): JBPopup {
val highlighter = if (highlightSelection) SelectionAwareScopeHighlighter(editor) else null
val list = JBList(elements.map(toPsi))
list.cellRenderer = renderer
list.addListSelectionListener {
highlighter?.dropHighlight()
val index = list.selectedIndex
if (index >= 0) {
highlighter?.highlight(list.model!!.getElementAt(index) as PsiElement)
}
}
return with(PopupChooserBuilder<E>(list)) {
title?.let { setTitle(it) }
renderer.installSpeedSearch(this, true)
setItemChoosenCallback {
val index = list.selectedIndex
if (index >= 0) {
processor(elements[index])
}
}
addListener(object : JBPopupAdapter() {
override fun onClosed(event: LightweightWindowEvent?) {
highlighter?.dropHighlight()
}
})
createPopup()
}
}
class SelectionAwareScopeHighlighter(val editor: Editor) {
private val highlighters = ArrayList<RangeHighlighter>()
private fun addHighlighter(r: TextRange, attr: TextAttributes) {
highlighters.add(
editor.markupModel.addRangeHighlighter(
r.startOffset,
r.endOffset,
UnwrapHandler.HIGHLIGHTER_LEVEL,
attr,
HighlighterTargetArea.EXACT_RANGE
)
)
}
fun highlight(wholeAffected: PsiElement) {
dropHighlight()
val affectedRange = wholeAffected.textRange ?: return
val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!!
val selectedRange = with(editor.selectionModel) { TextRange(selectionStart, selectionEnd) }
for (r in RangeSplitter.split(affectedRange, Collections.singletonList(selectedRange))) {
addHighlighter(r, attributes)
}
}
fun dropHighlight() {
highlighters.forEach { it.dispose() }
highlighters.clear()
}
}
fun PsiFile.getLineStartOffset(line: Int): Int? {
val doc = PsiDocumentManager.getInstance(project).getDocument(this)
if (doc != null && line >= 0 && line < doc.lineCount) {
val startOffset = doc.getLineStartOffset(line)
val element = findElementAt(startOffset) ?: return startOffset
if (element is PsiWhiteSpace || element is PsiComment) {
return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset
}
return startOffset
}
return null
}
fun PsiFile.getLineEndOffset(line: Int): Int? {
return PsiDocumentManager.getInstance(project).getDocument(this)?.getLineEndOffset(line)
}
fun PsiElement.getLineNumber(start: Boolean = true): Int {
return PsiDocumentManager.getInstance(project).getDocument(this.containingFile)?.getLineNumber(if (start) this.startOffset else this.endOffset) ?: 0
}
fun PsiElement.getLineCount(): Int {
val doc = containingFile?.let { file -> PsiDocumentManager.getInstance(project).getDocument(file) }
if (doc != null) {
val spaceRange = textRange ?: TextRange.EMPTY_RANGE
if (spaceRange.endOffset <= doc.textLength) {
val startLine = doc.getLineNumber(spaceRange.startOffset)
val endLine = doc.getLineNumber(spaceRange.endOffset)
return endLine - startLine
}
}
return (text ?: "").count { it == '\n' } + 1
}
fun PsiElement.isMultiLine(): Boolean = getLineCount() > 1
class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) {
override fun toString() = ""
}
fun <T> chooseContainerElement(
containers: List<T>,
editor: Editor,
title: String,
highlightSelection: Boolean,
toPsi: (T) -> PsiElement,
onSelect: (T) -> Unit) {
return getPsiElementPopup(
editor,
containers,
object : PsiElementListCellRenderer<PsiElement>() {
private fun PsiElement.renderName(): String {
if (this is KtPropertyAccessor) {
return property.renderName() + if (isGetter) ".get" else ".set"
}
if (this is KtObjectDeclaration && this.isCompanion()) {
return "Companion object of ${getStrictParentOfType<KtClassOrObject>()?.renderName() ?: "<anonymous>"}"
}
return (this as? PsiNamedElement)?.name ?: "<anonymous>"
}
private fun PsiElement.renderDeclaration(): String? {
if (this is KtFunctionLiteral || isFunctionalExpression()) return renderText()
val descriptor = when {
this is KtFile -> name
this is KtElement -> analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
this is PsiMember -> getJavaMemberDescriptor()
else -> null
} ?: return null
val name = renderName()
val params = (descriptor as? FunctionDescriptor)?.valueParameters
?.map { DescriptorRenderer.Companion.SHORT_NAMES_IN_TYPES.renderType(it.type) }
?.joinToString(", ", "(", ")") ?: ""
return "$name$params"
}
private fun PsiElement.renderText(): String {
if (this is SeparateFileWrapper) return "Extract to separate file"
return StringUtil.shortenTextWithEllipsis(text!!.collapseSpaces(), 53, 0)
}
private fun PsiElement.getRepresentativeElement(): PsiElement {
return when (this) {
is KtBlockExpression -> (parent as? KtDeclarationWithBody) ?: this
is KtClassBody -> parent as KtClassOrObject
else -> this
}
}
override fun getElementText(element: PsiElement): String? {
val representativeElement = element.getRepresentativeElement()
return representativeElement.renderDeclaration() ?: representativeElement.renderText()
}
override fun getContainerText(element: PsiElement, name: String?): String? = null
override fun getIconFlags(): Int = 0
override fun getIcon(element: PsiElement): Icon? =
super.getIcon(element.getRepresentativeElement())
},
title,
highlightSelection,
toPsi,
{
onSelect(it)
true
}
).showInBestPositionFor(editor)
}
fun <T> chooseContainerElementIfNecessary(
containers: List<T>,
editor: Editor,
title: String,
highlightSelection: Boolean,
toPsi: (T) -> PsiElement,
onSelect: (T) -> Unit
) {
when {
containers.isEmpty() -> return
containers.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode -> onSelect(containers.first())
else -> chooseContainerElement(containers, editor, title, highlightSelection, toPsi, onSelect)
}
}
fun PsiElement.isTrueJavaMethod(): Boolean = this is PsiMethod && this !is KtLightMethod
fun PsiElement.canRefactor(): Boolean {
if (!this.isValid) return false
return when {
this is PsiPackage ->
directories.any { it.canRefactor() }
this is KtElement ||
this is PsiMember && language == JavaLanguage.INSTANCE ||
this is PsiDirectory ->
ProjectRootsUtil.isInProjectSource(this)
else ->
false
}
}
private fun copyModifierListItems(from: PsiModifierList, to: PsiModifierList, withPsiModifiers: Boolean = true) {
if (withPsiModifiers) {
for (modifier in PsiModifier.MODIFIERS) {
if (from.hasExplicitModifier(modifier)) {
to.setModifierProperty(modifier, true)
}
}
}
for (annotation in from.annotations) {
val annotationName = annotation.qualifiedName ?: continue
if (Retention::class.java.name != annotationName) {
to.addAnnotation(annotationName)
}
}
}
private fun <T> copyTypeParameters(
from: T,
to: T,
inserter: (T, PsiTypeParameterList) -> Unit
) where T : PsiTypeParameterListOwner, T : PsiNameIdentifierOwner {
val factory = PsiElementFactory.SERVICE.getInstance((from as PsiElement).project)
val templateTypeParams = from.typeParameterList?.typeParameters ?: PsiTypeParameter.EMPTY_ARRAY
if (templateTypeParams.isNotEmpty()) {
inserter(to, factory.createTypeParameterList())
val targetTypeParamList = to.typeParameterList
val newTypeParams = templateTypeParams.map {
factory.createTypeParameter(it.name, it.extendsList.referencedTypes)
}
ChangeSignatureUtil.synchronizeList(
targetTypeParamList,
newTypeParams,
{ it!!.typeParameters.toList() },
BooleanArray(newTypeParams.size)
)
}
}
fun createJavaMethod(function: KtFunction, targetClass: PsiClass): PsiMethod {
val template = LightClassUtil.getLightClassMethod(function)
?: throw AssertionError("Can't generate light method: ${function.getElementTextWithContext()}")
return createJavaMethod(template, targetClass)
}
fun createJavaMethod(template: PsiMethod, targetClass: PsiClass): PsiMethod {
val factory = PsiElementFactory.SERVICE.getInstance(template.project)
val methodToAdd = if (template.isConstructor) {
factory.createConstructor(template.name)
}
else {
factory.createMethod(template.name, template.returnType)
}
val method = targetClass.add(methodToAdd) as PsiMethod
copyModifierListItems(template.modifierList, method.modifierList)
if (targetClass.isInterface) {
method.modifierList.setModifierProperty(PsiModifier.FINAL, false)
}
copyTypeParameters(template, method) { method, typeParameterList ->
method.addAfter(typeParameterList, method.modifierList)
}
val targetParamList = method.parameterList
val newParams = template.parameterList.parameters.map {
val param = factory.createParameter(it.name!!, it.type)
copyModifierListItems(it.modifierList!!, param.modifierList!!)
param
}
ChangeSignatureUtil.synchronizeList(
targetParamList,
newParams,
{ it.parameters.toList() },
BooleanArray(newParams.size)
)
if (template.modifierList.hasModifierProperty(PsiModifier.ABSTRACT) || targetClass.isInterface) {
method.body!!.delete()
}
else if (!template.isConstructor) {
CreateFromUsageUtils.setupMethodBody(method)
}
return method
}
fun createJavaField(property: KtNamedDeclaration, targetClass: PsiClass): PsiField {
val accessorLightMethods = property.getAccessorLightMethods()
val template = accessorLightMethods.getter
?: throw AssertionError("Can't generate light method: ${property.getElementTextWithContext()}")
val factory = PsiElementFactory.SERVICE.getInstance(template.project)
val field = targetClass.add(factory.createField(property.name!!, template.returnType!!)) as PsiField
with(field.modifierList!!) {
val templateModifiers = template.modifierList
setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true)
if ((property as KtValVarKeywordOwner).valOrVarKeyword.toValVar() != KotlinValVar.Var || targetClass.isInterface) {
setModifierProperty(PsiModifier.FINAL, true)
}
copyModifierListItems(templateModifiers, this, false)
}
return field
}
fun createJavaClass(klass: KtClass, targetClass: PsiClass?, forcePlainClass: Boolean = false): PsiClass {
val kind = if (forcePlainClass) ClassKind.CLASS else (klass.unsafeResolveToDescriptor() as ClassDescriptor).kind
val factory = PsiElementFactory.SERVICE.getInstance(klass.project)
val className = klass.name!!
val javaClassToAdd = when (kind) {
ClassKind.CLASS -> factory.createClass(className)
ClassKind.INTERFACE -> factory.createInterface(className)
ClassKind.ANNOTATION_CLASS -> factory.createAnnotationType(className)
ClassKind.ENUM_CLASS -> factory.createEnum(className)
else -> throw AssertionError("Unexpected class kind: ${klass.getElementTextWithContext()}")
}
val javaClass = (targetClass?.add(javaClassToAdd) ?: javaClassToAdd) as PsiClass
val template = klass.toLightClass() ?: throw AssertionError("Can't generate light class: ${klass.getElementTextWithContext()}")
copyModifierListItems(template.modifierList!!, javaClass.modifierList!!)
if (template.isInterface) {
javaClass.modifierList!!.setModifierProperty(PsiModifier.ABSTRACT, false)
}
copyTypeParameters(template, javaClass) { klass, typeParameterList ->
klass.addAfter(typeParameterList, klass.nameIdentifier)
}
// Turning interface to class
if (!javaClass.isInterface && template.isInterface) {
val implementsList = factory.createReferenceListWithRole(
template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
PsiReferenceList.Role.IMPLEMENTS_LIST
)
implementsList?.let { javaClass.implementsList?.replace(it) }
}
else {
val extendsList = factory.createReferenceListWithRole(
template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
PsiReferenceList.Role.EXTENDS_LIST
)
extendsList?.let { javaClass.extendsList?.replace(it) }
val implementsList = factory.createReferenceListWithRole(
template.implementsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
PsiReferenceList.Role.IMPLEMENTS_LIST
)
implementsList?.let { javaClass.implementsList?.replace(it) }
}
for (method in template.methods) {
val hasParams = method.parameterList.parametersCount > 0
val needSuperCall = !template.isEnum &&
(template.superClass?.constructors ?: PsiMethod.EMPTY_ARRAY).all {
it.parameterList.parametersCount > 0
}
if (method.isConstructor && !(hasParams || needSuperCall)) continue
with(createJavaMethod(method, javaClass)) {
if (isConstructor && needSuperCall) {
body!!.add(factory.createStatementFromText("super();", this))
}
}
}
return javaClass
}
fun PsiElement.j2kText(): String? {
if (language != JavaLanguage.INSTANCE) return null
val j2kConverter = JavaToKotlinConverter(project,
ConverterSettings.Companion.defaultSettings,
IdeaJavaToKotlinServices)
return j2kConverter.elementsToKotlin(listOf(this)).results.single()?.text ?: return null //TODO: insert imports
}
fun PsiExpression.j2k(): KtExpression? {
val text = j2kText() ?: return null
return KtPsiFactory(project).createExpression(text)
}
fun PsiMember.j2k(): KtNamedDeclaration? {
val text = j2kText() ?: return null
return KtPsiFactory(project).createDeclaration(text)
}
fun (() -> Any).runRefactoringWithPostprocessing(
project: Project,
targetRefactoringId: String,
finishAction: () -> Unit
) {
val connection = project.messageBus.connect()
connection.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC,
object : RefactoringEventListener {
override fun undoRefactoring(refactoringId: String) {
}
override fun refactoringStarted(refactoringId: String, beforeData: RefactoringEventData?) {
}
override fun conflictsDetected(refactoringId: String, conflictsData: RefactoringEventData) {
}
override fun refactoringDone(refactoringId: String, afterData: RefactoringEventData?) {
if (refactoringId == targetRefactoringId) {
try {
finishAction()
}
finally {
connection.disconnect()
}
}
}
})
this()
}
@Throws(ConfigurationException::class) fun KtElement?.validateElement(errorMessage: String) {
if (this == null) throw ConfigurationException(errorMessage)
try {
AnalyzingUtils.checkForSyntacticErrors(this)
}
catch(e: Exception) {
throw ConfigurationException(errorMessage)
}
}
fun invokeOnceOnCommandFinish(action: () -> Unit) {
val commandProcessor = CommandProcessor.getInstance()
val listener = object : CommandAdapter() {
override fun beforeCommandFinished(event: CommandEvent?) {
action()
commandProcessor.removeCommandListener(this)
}
}
commandProcessor.addCommandListener(listener)
}
fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() }
fun FqName.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() }
fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) {
is KtClass -> isInterface()
is PsiClass -> isInterface
is KtPsiClassWrapper -> psiClass.isInterface
else -> false
}
fun KtNamedDeclaration.isAbstract(): Boolean {
if (hasModifier(KtTokens.ABSTRACT_KEYWORD)) return true
if (!(containingClassOrObject?.isInterfaceClass() ?: false)) return false
return when (this) {
is KtProperty -> initializer == null && delegate == null && accessors.isEmpty()
is KtNamedFunction -> !hasBody()
else -> false
}
}
fun KtNamedDeclaration.isConstructorDeclaredProperty() = this is KtParameter && ownerFunction is KtPrimaryConstructor && hasValOrVar()
fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
originalList: ListType,
newList: ListType,
@Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType },
itemsFun: ListType.() -> List<KtElement>
): ListType {
originalList.children.takeWhile { it is PsiErrorElement }.forEach { it.delete() }
val oldParameters = originalList.itemsFun().toMutableList()
val newParameters = newList.itemsFun()
val oldCount = oldParameters.size
val newCount = newParameters.size
val commonCount = Math.min(oldCount, newCount)
for (i in 0..commonCount - 1) {
oldParameters[i] = oldParameters[i].replace(newParameters[i]) as KtElement
}
if (commonCount == 0) return originalList.listReplacer(newList)
val lastOriginalParameter = oldParameters.last()
if (oldCount > commonCount) {
originalList.deleteChildRange(oldParameters[commonCount - 1].nextSibling, lastOriginalParameter)
}
else if (newCount > commonCount) {
val psiBeforeLastParameter = lastOriginalParameter.prevSibling
val withMultiline = (psiBeforeLastParameter is PsiWhiteSpace || psiBeforeLastParameter is PsiComment) && psiBeforeLastParameter.textContains('\n')
val extraSpace = if (withMultiline) KtPsiFactory(originalList).createNewLine() else null
originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, newParameters.last(), lastOriginalParameter)
if (extraSpace != null) {
val addedItems = originalList.itemsFun().subList(commonCount, newCount)
for (addedItem in addedItems) {
val elementBefore = addedItem.prevSibling
if ((elementBefore !is PsiWhiteSpace && elementBefore !is PsiComment) || !elementBefore.textContains('\n')) {
addedItem.parent.addBefore(extraSpace, addedItem)
}
}
}
}
return originalList
}
fun <T> Pass(body: (T) -> Unit) = object : Pass<T>() {
override fun pass(t: T) = body(t)
}
fun KtExpression.removeTemplateEntryBracesIfPossible(): KtExpression {
val parent = parent as? KtBlockStringTemplateEntry ?: return this
val newEntry = if (parent.canDropBraces()) parent.dropBraces() else parent
return newEntry.expression!!
}
fun dropOverrideKeywordIfNecessary(element: KtNamedDeclaration) {
val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
if (callableDescriptor.overriddenDescriptors.isEmpty()) {
element.removeModifier(KtTokens.OVERRIDE_KEYWORD)
}
}
fun dropOperatorKeywordIfNecessary(element: KtNamedDeclaration) {
val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
val diagnosticHolder = BindingTraceContext()
OperatorModifierChecker.check(element, callableDescriptor, diagnosticHolder, element.languageVersionSettings)
if (diagnosticHolder.bindingContext.diagnostics.any { it.factory == Errors.INAPPLICABLE_OPERATOR_MODIFIER }) {
element.removeModifier(KtTokens.OPERATOR_KEYWORD)
}
}
fun getQualifiedTypeArgumentList(initializer: KtExpression): KtTypeArgumentList? {
val call = initializer.resolveToCall() ?: return null
val typeArgumentMap = call.typeArguments
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
val renderedList = typeArguments.joinToString(prefix = "<", postfix = ">") {
IdeDescriptorRenderers.SOURCE_CODE_NOT_NULL_TYPE_APPROXIMATION.renderType(it)
}
return KtPsiFactory(initializer).createTypeArguments(renderedList)
}
fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeArgumentList) {
val context = expression.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
val call = expression.getCallWithAssert(context)
val callElement = call.callElement as? KtCallExpression ?: return
if (call.typeArgumentList != null) return
val callee = call.calleeExpression ?: return
if (context.diagnostics.forElement(callee).all { it.factory != Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }) return
callElement.addAfter(typeArgumentList, callElement.calleeExpression)
ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!)
}
internal fun DeclarationDescriptor.getThisLabelName(): String {
if (!name.isSpecial) return name.asString()
if (this is AnonymousFunctionDescriptor) {
val function = source.getPsi() as? KtFunction
val argument = function?.parent as? KtValueArgument
?: (function?.parent as? KtLambdaExpression)?.parent as? KtValueArgument
val callElement = argument?.getStrictParentOfType<KtCallElement>()
val callee = callElement?.calleeExpression as? KtSimpleNameExpression
if (callee != null) return callee.text
}
return ""
}
internal fun DeclarationDescriptor.explicateAsTextForReceiver(): String {
val labelName = getThisLabelName()
return if (labelName.isEmpty()) "this" else "this@$labelName"
}
internal fun ImplicitReceiver.explicateAsText(): String {
return declarationDescriptor.explicateAsTextForReceiver()
}
val PsiFile.isInjectedFragment: Boolean
get() = InjectedLanguageManager.getInstance(project).isInjectedFragment(this)
val PsiElement.isInsideInjectedFragment: Boolean
get() = containingFile.isInjectedFragment
fun checkSuperMethods(
declaration: KtDeclaration,
ignore: Collection<PsiElement>?,
actionString: String
): List<PsiElement> {
fun getClassDescriptions(overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>): List<String> {
return overriddenElementsToDescriptor.entries.map { entry ->
val (element, descriptor) = entry
val description = when (element) {
is KtNamedFunction, is KtProperty, is KtParameter -> formatClassDescriptor(descriptor.containingDeclaration)
is PsiMethod -> {
val psiClass = element.containingClass ?: error("Invalid element: ${element.getText()}")
formatPsiClass(psiClass, true, false)
}
else -> error("Unexpected element: ${element.getElementTextWithContext()}")
}
" $description\n"
}
}
fun askUserForMethodsToSearch(
declarationDescriptor: CallableDescriptor,
overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>
): List<PsiElement> {
val superClassDescriptions = getClassDescriptions(overriddenElementsToDescriptor)
val message = KotlinBundle.message(
"x.overrides.y.in.class.list",
DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor),
"\n${superClassDescriptions.joinToString(separator = "")}",
actionString
)
val exitCode = showYesNoCancelDialog(
CHECK_SUPER_METHODS_YES_NO_DIALOG,
declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon(), Messages.YES)
return when (exitCode) {
Messages.YES -> overriddenElementsToDescriptor.keys.toList()
Messages.NO -> listOf(declaration)
else -> emptyList()
}
}
val declarationDescriptor = declaration.unsafeResolveToDescriptor() as CallableDescriptor
if (declarationDescriptor is LocalVariableDescriptor) return listOf(declaration)
val project = declaration.project
val overriddenElementsToDescriptor = HashMap<PsiElement, CallableDescriptor>()
for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) {
val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue
if (overriddenDeclaration is KtNamedFunction || overriddenDeclaration is KtProperty || overriddenDeclaration is PsiMethod || overriddenDeclaration is KtParameter) {
overriddenElementsToDescriptor[overriddenDeclaration] = overriddenDescriptor
}
}
if (ignore != null) {
overriddenElementsToDescriptor.keys.removeAll(ignore)
}
if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration)
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor)
}
fun checkSuperMethodsWithPopup(
declaration: KtNamedDeclaration,
deepestSuperMethods: List<PsiElement>,
actionString: String,
editor: Editor,
action: (List<PsiElement>) -> Unit
) {
if (deepestSuperMethods.isEmpty()) return action(listOf(declaration))
val superMethod = deepestSuperMethods.first()
val (superClass, isAbstract) = when (superMethod) {
is PsiMember -> superMethod.containingClass to superMethod.hasModifierProperty(PsiModifier.ABSTRACT)
is KtNamedDeclaration -> superMethod.containingClassOrObject to superMethod.isAbstract()
else -> null
} ?: return action(listOf(declaration))
if (superClass == null) return action(listOf(declaration))
if (ApplicationManager.getApplication().isUnitTestMode) return action(deepestSuperMethods)
val kind = when (declaration) {
is KtNamedFunction -> "function"
is KtProperty, is KtParameter -> "property"
else -> return
}
val unwrappedSupers = deepestSuperMethods.mapNotNull { it.namedUnwrappedElement }
val hasJavaMethods = unwrappedSupers.any { it is PsiMethod }
val hasKtMembers = unwrappedSupers.any { it is KtNamedDeclaration }
val superKind = when {
hasJavaMethods && hasKtMembers -> "member"
hasJavaMethods -> "method"
else -> kind
}
val renameBase = actionString + " base $superKind" + (if (deepestSuperMethods.size > 1) "s" else "")
val renameCurrent = actionString + " only current $kind"
val title = buildString {
append(declaration.name)
append(if (isAbstract) " implements " else " overrides ")
append(ElementDescriptionUtil.getElementDescription(superMethod, UsageViewTypeLocation.INSTANCE))
append(" of ")
append(SymbolPresentationUtil.getSymbolPresentableText(superClass))
}
val list = JBList<String>(renameBase, renameCurrent)
JBPopupFactory.getInstance()
.createListPopupBuilder(list)
.setTitle(title)
.setMovable(false)
.setResizable(false)
.setRequestFocus(true)
.setItemChoosenCallback {
val value = list.selectedValue ?: return@setItemChoosenCallback
val chosenElements = if (value == renameBase) deepestSuperMethods + declaration else listOf(declaration)
action(chosenElements)
}
.createPopup()
.showInBestPositionFor(editor)
}
fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
}
internal fun KtDeclaration.withExpectedActuals(): List<KtDeclaration> {
val expect = liftToExpected() ?: return listOf(this)
val actuals = expect.actualsForExpected()
return listOf(expect) + actuals
}
internal fun KtDeclaration.resolveToExpectedDescriptorIfPossible(): DeclarationDescriptor {
val descriptor = unsafeResolveToDescriptor()
return descriptor.liftToExpected() ?: descriptor
}