From 1b783070b5e6b5359d9673e6522c8d5d23265848 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Mon, 26 Mar 2018 11:58:04 +0300 Subject: [PATCH] 182: generic `PopupChooserBuilder` compilation fix --- ...KotlinGenerateTestSupportActionBase.kt.182 | 261 +++++ .../ImplementAbstractMemberIntention.kt.182 | 268 +++++ .../refactoring/kotlinRefactoringUtil.kt.182 | 993 ++++++++++++++++++ 3 files changed, 1522 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateTestSupportActionBase.kt.182 create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt.182 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.182 diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateTestSupportActionBase.kt.182 b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateTestSupportActionBase.kt.182 new file mode 100644 index 00000000000..aa3d4ba524b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateTestSupportActionBase.kt.182 @@ -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().firstOrNull { !it.isLocal } + } + + private fun chooseAndPerform(editor: Editor, frameworks: List, consumer: (TestFramework) -> Unit) { + frameworks.ifEmpty { return } + frameworks.singleOrNull()?.let { return consumer(it) } + + if (ApplicationManager.getApplication().isUnitTestMode) return consumer(frameworks.first()) + + val list = JBList(*frameworks.toTypedArray()) + list.cellRenderer = TestFrameworkListCellRenderer() + + PopupChooserBuilder(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) + } + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt.182 b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt.182 new file mode 100644 index 00000000000..1a8aff86794 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt.182 @@ -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::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 { + 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() + .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) { + 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() { + private val psiClassRenderer = PsiClassListCellRenderer() + + override fun getComparator(): Comparator { + 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(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) + } + .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) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.182 b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.182 new file mode 100644 index 00000000000..06c609e032d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.182 @@ -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 { + val containers = ArrayList() + + 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 { + 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() + 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, + 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, + declaration: PsiElement, + message: (renderedDeclaration: String) -> String +) { + conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize())) +} + +fun getPsiElementPopup( + editor: Editor, + elements: List, + renderer: PsiElementListCellRenderer, + 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(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() + + 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 chooseContainerElement( + containers: List, + editor: Editor, + title: String, + highlightSelection: Boolean, + toPsi: (T) -> PsiElement, + onSelect: (T) -> Unit) { + return getPsiElementPopup( + editor, + containers, + object : PsiElementListCellRenderer() { + 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()?.renderName() ?: ""}" + } + return (this as? PsiNamedElement)?.name ?: "" + } + + 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 chooseContainerElementIfNecessary( + containers: List, + 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 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 replaceListPsiAndKeepDelimiters( + originalList: ListType, + newList: ListType, + @Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType }, + itemsFun: ListType.() -> List +): 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 Pass(body: (T) -> Unit) = object : Pass() { + 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() + 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?, + actionString: String +): List { + fun getClassDescriptions(overriddenElementsToDescriptor: Map): List { + 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 + ): List { + 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() + 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, + actionString: String, + editor: Editor, + action: (List) -> 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(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 { + 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 +} \ No newline at end of file