181: compilation fix when access progressIndicator in ProgressManager by adding !!

This commit is contained in:
Nicolay Mitropolsky
2017-12-11 11:28:57 +03:00
committed by Nikolay Krasko
parent 395a817ada
commit f291873cf8
8 changed files with 1872 additions and 0 deletions
@@ -0,0 +1,434 @@
/*
* 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.debugger.evaluate
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
import com.sun.jdi.*
import org.jetbrains.annotations.TestOnly
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.refactoring.j2k
import org.jetbrains.kotlin.idea.refactoring.j2kText
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.makeNullable
import java.util.concurrent.atomic.AtomicReference
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val contextElement = getWrappedContextElement(project, context)
if (contextElement == null) {
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}")
}
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) {
KtExpressionCodeFragment(
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
} else {
KtBlockCodeFragment(
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
}
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if (debuggerSession == null || debuggerContext.suspendContext == null) {
null
} else {
val semaphore = Semaphore()
semaphore.down()
val nameRef = AtomicReference<KotlinType>()
val worker = object : KotlinRuntimeTypeEvaluator(
null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!!
) {
override fun typeCalculationFinished(type: KotlinType?) {
nameRef.set(type)
semaphore.up()
}
}
debuggerContext.debugProcess?.managerThread?.invoke(worker)
for (i in 0..50) {
ProgressManager.checkCanceled()
if (semaphore.waitFor(20)) break
}
nameRef.get()
}
})
if (contextElement != null && contextElement !is KtElement) {
codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, {
val emptyFile = createFakeFileWithJavaContextElement("", contextElement)
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) {
LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint")
return@putCopyableUserData emptyFile
}
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
if (frameDescriptor == null) {
LOG.warn("Couldn't get info about 'this' and local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}")
return@putCopyableUserData emptyFile
}
val receiverTypeReference =
frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
val kotlinVariablesText =
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}"
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull()
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
})
}
return codeFragment
}
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
val semaphore = Semaphore()
semaphore.down()
var frameInfo: FrameInfo? = null
val worker = object : DebuggerCommandImpl() {
override fun action() {
try {
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame
else
debuggerContext.frameProxy?.stackFrame
val visibleVariables = frame?.let {
val values = it.getValues(it.visibleVariables())
values.filterValues { it != null }
} ?: emptyMap()
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
} catch (ignored: AbsentInformationException) {
// Debug info unavailable
} finally {
semaphore.up()
}
}
}
debuggerContext.debugProcess?.managerThread?.invoke(worker)
for (i in 0..50) {
if (semaphore.waitFor(20)) break
}
return frameInfo
}
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
private fun initImports(imports: String?): String? {
if (imports != null && !imports.isEmpty()) {
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
.mapNotNull { fixImportIfNeeded(it) }
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
}
return null
}
private fun fixImportIfNeeded(import: String): String? {
// skip arrays
if (import.endsWith("[]")) {
return fixImportIfNeeded(import.removeSuffix("[]").trim())
}
// skip primitive types
if (PsiTypesUtil.boxIfPossible(import) != import) {
return null
}
return import
}
private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? {
val newContext = getContextElement(context)
if (newContext !is KtElement) return newContext
return wrapContextIfNeeded(project, context, newContext)
}
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val kotlinCodeFragment = createCodeFragment(item, context, project)
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
val javaExpression = try {
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
} catch (e: IncorrectOperationException) {
null
}
val importList = try {
kotlinCodeFragment.importsAsImportList()?.let {
(PsiFileFactory.getInstance(project).createFileFromText(
"dummy.java", JavaFileType.INSTANCE, it.text
) as? PsiJavaFile)?.importList
}
} catch (e: IncorrectOperationException) {
null
}
if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) {
var convertedFragment: KtExpressionCodeFragment? = null
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
try {
val newText = javaExpression.j2kText()
val newImports = importList?.j2kText()
if (newText != null) {
convertedFragment = KtExpressionCodeFragment(
project,
kotlinCodeFragment.name,
newText,
newImports,
kotlinCodeFragment.context
)
AfterConversionPass(project, J2kPostProcessor(formatCode = false)).run(convertedFragment!!, range = null)
}
} catch (e: Throwable) {
// ignored because text can be invalid
LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e)
}
}
return convertedFragment ?: kotlinCodeFragment
}
}
return kotlinCodeFragment
}
override fun isContextAccepted(contextElement: PsiElement?): Boolean {
return when {
// PsiCodeBlock -> DummyHolder -> originalElement
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
contextElement == null -> false
contextElement.language == KotlinFileType.INSTANCE.language -> true
contextElement.language == JavaFileType.INSTANCE.language -> {
getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
}
else -> false
}
}
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
override fun getEvaluatorBuilder() = KotlinEvaluationBuilder
companion object {
private val LOG = Logger.getInstance(this::class.java)
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
@TestOnly
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
fun getContextElement(elementAt: PsiElement?): PsiElement? {
if (elementAt == null) return null
if (elementAt is PsiCodeBlock) {
return getContextElement(elementAt.context?.context)
}
if (elementAt is KtLightClass) {
return getContextElement(elementAt.kotlinOrigin)
}
val containingFile = elementAt.containingFile
if (containingFile is PsiJavaFile) return elementAt
if (containingFile !is KtFile) return null
// elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window)
val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) {
PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset
?: elementAt.textOffset
} else {
elementAt.textOffset
}
fun KtElement.takeIfAcceptedAsCodeFragmentContext() = takeIf { KotlinEditorTextProvider.isAcceptedAsCodeFragmentContext(it) }
PsiTreeUtil.findElementOfClassAtOffset(containingFile, lineStartOffset, KtExpression::class.java, false)
?.takeIfAcceptedAsCodeFragmentContext()
?.let { return CodeInsightUtils.getTopmostElementAtOffset(it, lineStartOffset, KtExpression::class.java) }
KotlinEditorTextProvider.findExpressionInner(elementAt, true)
?.takeIfAcceptedAsCodeFragmentContext()
?.let { return it }
return containingFile
}
//internal for tests
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
@Suppress("UNCHECKED_CAST")
val variables = markupMap.entries.associate {
val (value, markup) = it
"${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value
}.filterValues { it != null } as Map<String, Value>
return variables.kotlinVariablesAsText(project) to variables
}
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
val sb = StringBuilder()
val psiNameHelper = PsiNameHelper.getInstance(project)
for ((variableName, variableValue) in entries) {
if (!psiNameHelper.isIdentifier(variableName)) continue
val variableTypeName = variableValue.type()?.name() ?: continue
val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue
sb.append("${kotlinProperty.text}\n")
}
sb.append("val _debug_context_val = 1\n")
return sb.toString()
}
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? {
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false)
}
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
val className = variableTypeName.replace("$", ".").substringBefore("[]")
val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project))
val type = (if (value !is PrimitiveValue && classType.resolve() == null)
CommonClassNames.JAVA_LANG_OBJECT
else
className).addArraySuffix()
val field = PsiElementFactory.SERVICE.getInstance(project)
.createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
val ktField = field.j2k() as? KtProperty
ktField?.modifierList?.delete()
return ktField
}
}
private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? {
val markupMap: Map<*, ValueMarkup>? =
if (ApplicationManager.getApplication().isUnitTestMode)
NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess)
else
(XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers
if (markupMap == null || markupMap.isEmpty()) return newContext
val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
if (text.isEmpty()) return newContext
return createWrappingContext(text, labels, newContext, project)
}
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
val javaFile = javaContext.containingFile as? PsiJavaFile
val sb = StringBuilder()
javaFile?.packageName?.takeUnless { it.isBlank() }?.let {
sb.append("package ").append(it.quoteIfNeeded()).append("\n")
}
javaFile?.importList?.let { sb.append(it.text).append("\n") }
sb.append(funWithLocalVariables)
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
}
// internal for test
private fun createWrappingContext(
newFragmentText: String,
labels: Map<String, Value>,
originalContext: KtElement?,
project: Project
): KtElement? {
val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext)
codeFragment.accept(object : KtTreeVisitorVoid() {
override fun visitProperty(property: KtProperty) {
val reference = labels[property.name]
if (reference != null) {
property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference)
}
}
})
return codeFragment.getContentElement().statements.lastOrNull()
}
}
@@ -0,0 +1,216 @@
/*
* 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
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.ide.scratch.ScratchFileService
import com.intellij.ide.scratch.ScratchRootType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileVisitor
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import java.io.IOException
import java.util.*
class JavaToKotlinAction : AnAction() {
companion object {
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
val ioFile = File(javaFile.path.replace('/', File.separatorChar))
var i = 0
while (true) {
val fileName = javaFile.nameWithoutExtension + (if (i > 0) i else "") + ".kt"
if (!ioFile.resolveSibling(fileName).exists()) return fileName
i++
}
}
val title = "Convert Java to Kotlin"
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
val result = ArrayList<VirtualFile>()
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
try {
val document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile)
if (document == null) {
MessagesEx.error(psiFile.project, "Failed to save conversion result: couldn't find document for " + psiFile.name).showLater()
continue
}
document.replaceString(0, document.textLength, text)
FileDocumentManager.getInstance().saveDocument(document)
val virtualFile = psiFile.virtualFile
if (ScratchRootType.getInstance().containsFile(virtualFile)) {
val mapping = ScratchFileService.getInstance().scratchesMapping
mapping.setMapping(virtualFile, KotlinFileType.INSTANCE.language)
}
else {
val fileName = uniqueKotlinFileName(virtualFile)
virtualFile.rename(this, fileName)
}
}
catch (e: IOException) {
MessagesEx.error(psiFile.project, e.message ?: "").showLater()
}
}
return result
}
fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, enableExternalCodeProcessing: Boolean = true): List<KtFile> {
var converterResult: JavaToKotlinConverter.FilesResult? = null
fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().progressIndicator!!)
}
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
::convert,
title,
true,
project)) return emptyList()
var externalCodeUpdate: (() -> Unit)? = null
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().progressIndicator!!)
}
},
title,
true,
project)
}
}
return project.executeWriteCommand("Convert files from Java to Kotlin", null) {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, converterResult!!.results)
externalCodeUpdate?.invoke()
PsiDocumentManager.getInstance(project).commitAllDocuments()
newFiles.singleOrNull()?.let {
FileEditorManager.getInstance(project).openFile(it, true)
}
newFiles.map { it.toPsiFile(project) as KtFile }
}
}
}
override fun actionPerformed(e: AnActionEvent) {
val javaFiles = selectedJavaFiles(e).filter { it.isWritable }.toList()
val project = CommonDataKeys.PROJECT.getData(e.dataContext)!!
val firstSyntaxError = javaFiles.asSequence().map { PsiTreeUtil.findChildOfType(it, PsiErrorElement::class.java) }.firstOrNull()
if (firstSyntaxError != null) {
val count = javaFiles.filter { PsiTreeUtil.hasErrorElements(it) }.count()
val question = firstSyntaxError.containingFile.name +
(if (count > 1) " and ${count - 1} more Java files" else " file") +
" contain syntax errors, the conversion result may be incorrect"
val okText = "Investigate Errors"
val cancelText = "Proceed with Conversion"
if (Messages.showOkCancelDialog(
project,
question,
title,
okText,
cancelText,
Messages.getWarningIcon()
) == Messages.OK) {
NavigationUtil.activateFileWithPsiElement(firstSyntaxError.navigationElement)
return
}
}
convertFiles(javaFiles, project)
}
override fun update(e: AnActionEvent) {
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return
val project = e.project ?: return
e.presentation.isEnabled = isAnyJavaFileSelected(project, virtualFiles)
}
private fun isAnyJavaFileSelected(project: Project, files: Array<VirtualFile>): Boolean {
val manager = PsiManager.getInstance(project)
if (files.any { manager.findFile(it) is PsiJavaFile && it.isWritable }) return true
return files.any { it.isDirectory && isAnyJavaFileSelected(project, it.children) }
}
private fun selectedJavaFiles(e: AnActionEvent): Sequence<PsiJavaFile> {
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf()
val project = e.project ?: return sequenceOf()
return allJavaFiles(virtualFiles, project)
}
private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<PsiJavaFile> {
val manager = PsiManager.getInstance(project)
return allFiles(filesOrDirs)
.asSequence()
.mapNotNull { manager.findFile(it) as? PsiJavaFile }
}
private fun allFiles(filesOrDirs: Array<VirtualFile>): Collection<VirtualFile> {
val result = ArrayList<VirtualFile>()
for (file in filesOrDirs) {
VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor<Unit>() {
override fun visitFile(file: VirtualFile): Boolean {
result.add(file)
return true
}
})
}
return result
}
}
@@ -0,0 +1,164 @@
/*
* 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.actions.internal
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.ProgressManager.progress
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
class SearchNotPropertyCandidatesAction : AnAction() {
override fun actionPerformed(e: AnActionEvent?) {
val project = e?.project!!
val psiFile = e.getData(CommonDataKeys.PSI_FILE) as? KtFile ?: return
val desc = psiFile.findModuleDescriptor()
val result = Messages.showInputDialog("Enter package FqName", "Search for Not Property candidates", Messages.getQuestionIcon())
val packageDesc = try {
val fqName = FqName.fromSegments(result!!.split('.'))
desc.getPackage(fqName)
}
catch (e: Exception) {
return
}
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
ProgressManager.getInstance().progressIndicator!!.isIndeterminate = true
processAllDescriptors(packageDesc, project)
}
},
"Searching for Not Property candidates",
true,
project)
}
private fun processAllDescriptors(desc: DeclarationDescriptor, project: Project) {
val processed = mutableSetOf<DeclarationDescriptor>()
var pFunctions = 0
val matchedDescriptors = mutableSetOf<FunctionDescriptor>()
fun recursive(desc: DeclarationDescriptor) {
if (desc in processed) return
progress("Step 1: Collecting ${processed.size}:$pFunctions:${matchedDescriptors.size}", "$desc")
when (desc) {
is ModuleDescriptor -> recursive(desc.getPackage(FqName("java")))
is ClassDescriptor -> desc.unsubstitutedMemberScope.getContributedDescriptors { true }.forEach(::recursive)
is PackageViewDescriptor -> desc.memberScope.getContributedDescriptors { true }.forEach(::recursive)
is FunctionDescriptor -> {
if (desc.isFromJava) {
val name = desc.fqNameUnsafe.shortName().asString()
if (name.length > 3 &&
((name.startsWith("get") && desc.valueParameters.isEmpty() && desc.returnType != null) ||
(name.startsWith("set") && desc.valueParameters.size == 1))) {
if (desc in matchedDescriptors) return
matchedDescriptors += desc
}
}
pFunctions++
return
}
}
processed += desc
}
recursive(desc)
val resultDescriptors = mutableSetOf<FunctionDescriptor>()
matchedDescriptors.flatMapTo(resultDescriptors) {
sequenceOf(it, *(it.overriddenDescriptors.toTypedArray())).asIterable()
}
println("Found ${resultDescriptors.size} accessors")
fun PsiMethod.isTrivial(): Boolean {
val t = this.text
val s = t.indexOf('{')
val e = t.lastIndexOf('}')
return if (s != e && s != -1) t.substring(s, e).lines().size <= 3 else true
}
val descriptorToPsiBinding = mutableMapOf<FunctionDescriptor, PsiMethod>()
var i = 0
resultDescriptors.forEach { desc ->
progress("Step 2: ${i++} of ${resultDescriptors.size}", "$desc")
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, desc)
.filterIsInstance<PsiMethod>()
.firstOrNull() ?: return@forEach
val abstract = source.modifierList.hasModifierProperty(PsiModifier.ABSTRACT)
if (!abstract) {
if (!source.isTrivial()) {
descriptorToPsiBinding[desc] = source
}
}
}
val nonTrivial = mutableSetOf<FunctionDescriptor>()
i = 0
descriptorToPsiBinding.forEach { t, u ->
progress("Step 3: ${i++} of ${descriptorToPsiBinding.size}", "$t")
val descriptors = t.overriddenDescriptors
var impl = false
descriptors.forEach {
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, it).filterIsInstance<PsiMethod>().firstOrNull()
if (source != null) {
if (source.body != null || source.hasModifierProperty(PsiModifier.ABSTRACT))
nonTrivial += it
impl = true
}
}
if (u.body != null)
if (!impl)
nonTrivial += t
}
nonTrivial.forEach(::println)
println("Non trivial count: ${nonTrivial.size}")
}
override fun update(e: AnActionEvent) {
if (!KotlinInternalMode.enabled) {
e.presentation.isVisible = false
e.presentation.isEnabled = false
}
else {
e.presentation.isVisible = true
e.presentation.isEnabled = e.getData(CommonDataKeys.PSI_FILE) is KtFile
}
}
}
@@ -0,0 +1,356 @@
/*
* 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.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.explicateReceiverOf
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type parameter to receiver"
) {
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val parameterToMove = functionTypeParameterList.parameters.getOrNull(data.typeParameterIndex) ?: return
val typeReferenceToMove = parameterToMove.typeReference ?: return
functionType.setReceiverTypeReference(typeReferenceToMove)
functionTypeParameterList.removeParameter(parameterToMove)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val argumentList = callExpression.valueArgumentList ?: return
val expressionToMove = argumentList.arguments.getOrNull(data.typeParameterIndex)?.getArgumentExpression() ?: return
val callWithReceiver = KtPsiFactory(callExpression).createExpressionByPattern("$0.$1", expressionToMove, callExpression) as KtQualifiedExpression
(callWithReceiver.selectorExpression as KtCallExpression).valueArgumentList!!.removeArgument(data.typeParameterIndex)
callExpression.replace(callWithReceiver)
}
}
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractProcessableUsageInfo<KtSimpleNameExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val lambdaType = data.lambdaType
val validator = CollectingNameValidator()
val parameterNames = lambdaType.arguments
.dropLast(1)
.map { KotlinNameSuggester.suggestNamesByType(it.type, validator, "p").first() }
val receiver = parameterNames.getOrNull(data.typeParameterIndex) ?: return
val arguments = parameterNames.filter { it != receiver }
val adapterLambda = KtPsiFactory(expression).createLambdaExpression(
parameterNames.joinToString(),
"$receiver.${expression.text}(${arguments.joinToString()})"
)
expression.replaced(adapterLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
}
class LambdaInfo(element: KtExpression) : AbstractProcessableUsageInfo<KtExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(expression)
if (expression is KtLambdaExpression || (expression !is KtSimpleNameExpression && expression !is KtCallableReferenceExpression)) {
expression.forEachDescendantOfType<KtThisExpression> {
if (it.getLabelName() != null) return@forEachDescendantOfType
val descriptor = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
it.replace(psiFactory.createExpression(explicateReceiverOf(descriptor)))
}
}
if (expression is KtLambdaExpression) {
expression.valueParameters.getOrNull(data.typeParameterIndex)?.let { parameterToConvert ->
val thisRefExpr = psiFactory.createThisExpression()
for (ref in ReferencesSearch.search(parameterToConvert, LocalSearchScope(expression))) {
(ref.element as? KtSimpleNameExpression)?.replace(thisRefExpr)
}
val lambda = expression.functionLiteral
lambda.valueParameterList!!.removeParameter(parameterToConvert)
if (lambda.valueParameters.isEmpty()) {
lambda.arrow?.delete()
}
}
return
}
val originalLambdaTypes = data.lambdaType
val originalParameterTypes = originalLambdaTypes.arguments.dropLast(1).map { it.type }
val calleeText = when (expression) {
is KtSimpleNameExpression -> expression.text
is KtCallableReferenceExpression -> "(${expression.text})"
else -> generateVariable(expression)
}
val parameterNameValidator = CollectingNameValidator(
if (expression !is KtCallableReferenceExpression) listOf(calleeText) else emptyList()
)
val parameterNamesWithReceiver = originalParameterTypes.mapIndexed { i, type ->
if (i != data.typeParameterIndex) KotlinNameSuggester.suggestNamesByType(type, parameterNameValidator, "p").first() else "this"
}
val parameterNames = parameterNamesWithReceiver.filter { it != "this" }
val body = psiFactory.createExpression(parameterNamesWithReceiver.joinToString(prefix = "$calleeText(", postfix = ")"))
val replacingLambda = psiFactory.buildExpression {
appendFixedText("{ ")
appendFixedText(parameterNames.joinToString())
appendFixedText(" -> ")
appendExpression(body)
appendFixedText(" }")
} as KtLambdaExpression
expression.replaced(replacingLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
private fun generateVariable(expression: KtExpression): String {
var baseCallee = ""
KotlinIntroduceVariableHandler.doRefactoring(project, null, expression, false, emptyList()) {
baseCallee = it.name!!
}
return baseCallee
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
usageLoop@ for (ref in callable.searchReferencesOrMethodReferences()) {
val refElement = ref.element ?: continue
when (ref) {
is KtSimpleReference<*> -> processExternalUsage(conflicts, refElement, usages)
is KtReference -> continue@usageLoop
else -> {
if (data.isFirstParameter) continue@usageLoop
conflicts.putValue(
refElement,
"Can't replace non-Kotlin reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
if (callable is KtFunction) {
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(
conflicts: MultiMap<PsiElement, String>,
refElement: PsiElement,
usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>
) {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (callElement != null) {
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = getArgumentExpressionToProcess(callElement, context) ?: return
if (!data.isFirstParameter
&& callElement is KtConstructorDelegationCall
&& expressionToProcess !is KtLambdaExpression
&& expressionToProcess !is KtSimpleNameExpression
&& expressionToProcess !is KtCallableReferenceExpression) {
conflicts.putValue(
expressionToProcess,
"Following expression won't be processed since refactoring can't preserve its semantics: ${expressionToProcess.text}"
)
return
}
if (!checkThisExpressionsAreExplicatable(conflicts, context, expressionToProcess)) return
if (data.isFirstParameter && expressionToProcess !is KtLambdaExpression) return
usages += LambdaInfo(expressionToProcess)
return
}
if (data.isFirstParameter) return
val callableReference = refElement.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }
if (callableReference != null) {
conflicts.putValue(
refElement,
"Callable reference transformation is not supported: " + StringUtil.htmlEmphasize(callableReference.text)
)
return
}
}
private fun getArgumentExpressionToProcess(callElement: KtCallElement, context: BindingContext): KtExpression? {
return callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
}
private fun checkThisExpressionsAreExplicatable(conflicts: MultiMap<PsiElement, String>, context: BindingContext, expressionToProcess: KtExpression): Boolean {
for (thisExpr in expressionToProcess.collectDescendantsOfType<KtThisExpression>()) {
if (thisExpr.getLabelName() != null) continue
val descriptor = context[BindingContext.REFERENCE_TARGET, thisExpr.instanceReference] ?: continue
if (explicateReceiverOf(descriptor) == "this") {
conflicts.putValue(
thisExpr,
"Following expression won't be processed since refactoring can't preserve its semantics: ${thisExpr.text}"
)
return false
}
}
return true
}
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression }
if (callExpression != null) {
usages += ParameterCallInfo(callExpression)
}
else if (!data.isFirstParameter) {
usages += InternalReferencePassInfo(element)
}
}
}
}
}
class ConversionData(
val typeParameterIndex: Int,
val functionParameterIndex: Int,
val lambdaType: KotlinType,
val function: KtFunction
) {
val isFirstParameter: Boolean get() = typeParameterIndex == 0
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val parameter = parent as? KtParameter ?: return null
val functionType = parameter.getParentOfTypeAndBranch<KtFunctionType> { parameterList } ?: return null
if (functionType.receiverTypeReference != null) return null
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val typeParameterIndex = functionType.parameters.indexOf(parameter)
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
setReceiverTypeReference(element)
parameterList!!.removeParameter(data.typeParameterIndex)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -0,0 +1,222 @@
/*
* 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.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type receiver to parameter"
) {
class ConversionData(
val functionParameterIndex: Int,
val lambdaReceiverType: KotlinType,
val function: KtFunction
) {
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val functionTypeReceiver = functionType.receiverTypeReference ?: return
val parameterToAdd = KtPsiFactory(project).createFunctionTypeParameter(functionTypeReceiver)
functionTypeParameterList.addParameterBefore(parameterToAdd, functionTypeParameterList.parameters.firstOrNull())
functionType.setReceiverTypeReference(null)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() ?: return
val receiverExpression = qualifiedExpression.receiverExpression
val argumentList = callExpression.getOrCreateValueArgumentList()
argumentList.addArgumentBefore(KtPsiFactory(project).createArgument(receiverExpression), argumentList.arguments.firstOrNull())
qualifiedExpression.replace(callExpression)
}
}
class LambdaInfo(element: KtLambdaExpression) : AbstractProcessableUsageInfo<KtLambdaExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val lambda = element?.functionLiteral ?: return
val context = lambda.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(project)
val validator = CollectingNameValidator(
lambda.valueParameters.mapNotNull { it.name },
NewDeclarationNameValidator(lambda.bodyExpression!!, null, NewDeclarationNameValidator.Target.VARIABLES)
)
val newParameterName = KotlinNameSuggester.suggestNamesByType(data.lambdaReceiverType, validator, "p").first()
val newParameterRefExpression = psiFactory.createExpression(newParameterName)
lambda.forEachDescendantOfType<KtThisExpression> {
val thisTarget = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
if (DescriptorToSourceUtilsIde.getAnyDeclaration(project, thisTarget) == lambda) {
it.replace(newParameterRefExpression)
}
}
val lambdaParameterList = lambda.getOrCreateParameterList()
val parameterToAdd = psiFactory.createLambdaParameterList(newParameterName).parameters.first()
lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull())
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is KtFunction) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
for (ref in callable.searchReferencesOrMethodReferences()) {
if (ref !is KtSimpleReference<*>) continue
processExternalUsage(ref, usages)
}
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(ref: KtSimpleReference<*>, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val callElement = ref.element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
?: return
if (expressionToProcess is KtLambdaExpression) {
usages += LambdaInfo(expressionToProcess)
}
}
private fun processInternalUsages(callable: KtFunction, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression } ?: continue
usages += ParameterCallInfo(callExpression)
}
}
}
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val functionTypeReceiver = parent as? KtFunctionTypeReceiver ?: return null
val functionType = functionTypeReceiver.parent as? KtFunctionType ?: return null
val lambdaReceiverType = functionType
.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL))
?.getReceiverTypeFromFunctionType()
?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
parameterList!!.addParameterBefore(
KtPsiFactory(element).createFunctionTypeParameter(element),
parameterList!!.parameters.firstOrNull()
)
setReceiverTypeReference(null)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -0,0 +1,201 @@
/*
* 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.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
import java.util.*
class ConvertPropertyToFunctionIntention : SelfTargetingIntention<KtProperty>(KtProperty::class.java, "Convert property to function"), LowPriorityAction {
private inner class Converter(
project: Project,
descriptor: CallableDescriptor
): CallableRefactoring<CallableDescriptor>(project, descriptor, text) {
private val newName: String = JvmAbi.getterName(callableDescriptor.name.asString())
private fun convertProperty(originalProperty: KtProperty, psiFactory: KtPsiFactory) {
val property = originalProperty.copy() as KtProperty
val getter = property.getter
val sampleFunction = psiFactory.createFunction("fun foo() {\n\n}")
property.valOrVarKeyword.replace(sampleFunction.funKeyword!!)
property.addAfter(psiFactory.createParameterList("()"), property.nameIdentifier)
if (property.initializer == null) {
if (getter != null) {
val dropGetterTo = (getter.equalsToken ?: getter.bodyExpression)
?.siblings(forward = false, withItself = false)
?.firstOrNull { it !is PsiWhiteSpace }
getter.deleteChildRange(getter.firstChild, dropGetterTo)
val dropPropertyFrom = getter
.siblings(forward = false, withItself = false)
.first { it !is PsiWhiteSpace }
.nextSibling
property.deleteChildRange(dropPropertyFrom, getter.prevSibling)
}
}
property.setName(newName)
originalProperty.replace(psiFactory.createFunction(property.text))
}
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val propertyName = callableDescriptor.name.asString()
val nameChanged = propertyName != newName
val getterName = JvmAbi.getterName(callableDescriptor.name.asString())
val conflicts = MultiMap<PsiElement, String>()
val callables = getAffectedCallables(project, descriptorsForChange)
val kotlinRefsToReplaceWithCall = ArrayList<KtSimpleNameExpression>()
val refsToRename = ArrayList<PsiReference>()
val javaRefsToReplaceWithCall = ArrayList<PsiReferenceExpression>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
if (callable is KtProperty) {
callableDescriptor.getContainingScope()
?.findFunction(callableDescriptor.name, NoLookupLocation.FROM_IDE) { it.valueParameters.isEmpty() }
?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
else if (callable is PsiMethod) {
callable.containingClass
?.findMethodsByName(propertyName, true)
// as is necessary here: see KT-10386
?.firstOrNull { it.parameterList.parametersCount == 0 && !callables.contains(it.namedUnwrappedElement as PsiElement?) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
val usages = ReferencesSearch.search(callable)
for (usage in usages) {
if (usage is KtReference) {
if (usage is KtSimpleNameReference) {
val expression = usage.expression
if (expression.getCall(expression.analyze(BodyResolveMode.PARTIAL)) != null
&& expression.getStrictParentOfType<KtCallableReferenceExpression>() == null) {
kotlinRefsToReplaceWithCall.add(expression)
}
else if (nameChanged) {
refsToRename.add(usage)
}
}
else {
val refElement = usage.element
conflicts.putValue(
refElement,
"Unrecognized reference will be skipped: " + StringUtil.htmlEmphasize(refElement.text)
)
}
continue
}
val refElement = usage.element
if (refElement.text.endsWith(getterName)) continue
if (usage is PsiJavaReference) {
if (usage.resolve() is PsiField && usage is PsiReferenceExpression) {
javaRefsToReplaceWithCall.add(usage)
}
continue
}
conflicts.putValue(
refElement,
"Can't replace foreign reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val kotlinPsiFactory = KtPsiFactory(project)
val javaPsiFactory = PsiElementFactory.SERVICE.getInstance(project)
val newKotlinCallExpr = kotlinPsiFactory.createExpression("$newName()")
kotlinRefsToReplaceWithCall.forEach { it.replace(newKotlinCallExpr) }
refsToRename.forEach { it.handleElementRename(newName) }
javaRefsToReplaceWithCall.forEach {
val getterRef = it.handleElementRename(newName)
getterRef.replace(javaPsiFactory.createExpressionFromText("${getterRef.text}()", null))
}
callables.forEach {
when (it) {
is KtProperty -> convertProperty(it, kotlinPsiFactory)
is PsiMethod -> it.name = newName
}
}
}
}
}
}
override fun startInWriteAction(): Boolean = false
override fun isApplicableTo(element: KtProperty, caretOffset: Int): Boolean {
val identifier = element.nameIdentifier ?: return false
if (!identifier.textRange.containsOffset(caretOffset)) return false
return element.delegate == null && !element.isVar && !element.isLocal && (element.initializer == null || element.getter == null)
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val descriptor = element.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
Converter(element.project, descriptor).run()
}
}
@@ -0,0 +1,182 @@
/*
* Copyright 2010-2017 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.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
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.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashSet
import kotlin.collections.filter
class ChangeSuspendInHierarchyFix(
element: KtNamedFunction,
private val addModifier: Boolean
) : KotlinQuickFixAction<KtNamedFunction>(element) {
override fun getFamilyName(): String {
return if (addModifier) {
"Add 'suspend' modifier to all functions in hierarchy"
} else {
"Remove 'suspend' modifier from all functions in hierarchy"
}
}
override fun getText() = familyName
override fun startInWriteAction() = false
private fun findAllFunctionToProcess(project: Project): Set<KtNamedFunction> {
val result = LinkedHashSet<KtNamedFunction>()
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
val function = element ?: return emptySet()
val functionDescriptor = function.unsafeResolveToDescriptor() as FunctionDescriptor
val baseFunctionDescriptors = functionDescriptor.findTopMostOverriddables()
baseFunctionDescriptors.forEach { baseFunctionDescriptor ->
val baseClassDescriptor = baseFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return@forEach
val baseClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, baseClassDescriptor) ?: return@forEach
val name = (baseClass as? PsiNamedElement)?.name ?: return@forEach
progressIndicator.text = "Looking for class $name inheritors..."
val classes = listOf(baseClass) + HierarchySearchRequest(baseClass, baseClass.useScope).searchInheritors()
classes.mapNotNullTo(result) {
val subClass = it.unwrapped as? KtClassOrObject ?: return@mapNotNullTo null
val classDescriptor = subClass.unsafeResolveToDescriptor() as ClassDescriptor
val substitutor = getTypeSubstitutor(baseClassDescriptor.defaultType, classDescriptor.defaultType)
?: return@mapNotNullTo null
val signatureInSubClass = baseFunctionDescriptor.substitute(substitutor) as FunctionDescriptor
val subFunctionDescriptor = classDescriptor.findCallableMemberBySignature(signatureInSubClass, true)
?: return@mapNotNullTo null
subFunctionDescriptor.source.getPsi() as? KtNamedFunction
}
}
return result
}
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val functions = project.runSynchronouslyWithProgress("Analyzing class hierarchy...", true) {
runReadAction { findAllFunctionToProcess(project) }
} ?: return
runWriteAction {
functions.forEach {
if (addModifier) {
it.addModifier(KtTokens.SUSPEND_KEYWORD)
}
else {
it.removeModifier(KtTokens.SUSPEND_KEYWORD)
}
}
}
}
companion object : KotlinIntentionActionsFactory() {
fun FunctionDescriptor.findTopMostOverriddables(): List<FunctionDescriptor> {
val overridablesCache = HashMap<FunctionDescriptor, List<FunctionDescriptor>>()
fun FunctionDescriptor.getOverridables(): List<FunctionDescriptor> {
return overridablesCache.getOrPut(this) {
val classDescriptor = containingDeclaration as? ClassDescriptorWithResolutionScopes ?: return emptyList()
DescriptorUtils.getSuperclassDescriptors(classDescriptor).flatMap { superClassDescriptor ->
if (superClassDescriptor !is ClassDescriptorWithResolutionScopes) return@flatMap emptyList<FunctionDescriptor>()
val candidates = superClassDescriptor.unsubstitutedMemberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE)
val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, classDescriptor.defaultType)
?: return@flatMap emptyList<FunctionDescriptor>()
candidates.filter {
val signature = it.substitute(substitutor) as FunctionDescriptor
classDescriptor.findCallableMemberBySignature(signature, true) == this
}
}
}
}
return DFS.dfs(
listOf(this),
{ it?.getOverridables() ?: emptyList() },
object : DFS.CollectingNodeHandler<FunctionDescriptor, FunctionDescriptor, ArrayList<FunctionDescriptor>>(ArrayList()) {
override fun afterChildren(current: FunctionDescriptor) {
if (current.getOverridables().isEmpty()) {
result.add(current)
}
}
})
}
private fun Collection<DeclarationDescriptor>.getOverridables(
currentDescriptor: FunctionDescriptor
): List<DeclarationDescriptor> {
val currentClassDescriptor = currentDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
return filter {
if (it !is FunctionDescriptor || it == currentDescriptor) return@filter false
if (it.isSuspend == currentDescriptor.isSuspend) return@filter false
val containingClassDescriptor = it.containingDeclaration as? ClassDescriptor ?: return@filter false
if (!currentClassDescriptor.isSubclassOf(containingClassDescriptor)) return@filter false
val substitutor = getTypeSubstitutor(
containingClassDescriptor.defaultType,
currentClassDescriptor.defaultType
) ?: return@filter false
val signatureInCurrentClass = it.substitute(substitutor) ?: return@filter false
OverridingUtil.DEFAULT.isOverridableBy(signatureInCurrentClass, currentDescriptor, null).result ==
OverridingUtil.OverrideCompatibilityInfo.Result.CONFLICT
}
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val currentFunction = diagnostic.psiElement as? KtNamedFunction ?: return emptyList()
val currentDescriptor = currentFunction.unsafeResolveToDescriptor() as FunctionDescriptor
Errors.CONFLICTING_OVERLOADS.cast(diagnostic).a.getOverridables(currentDescriptor).ifEmpty { return emptyList() }
return listOf(
ChangeSuspendInHierarchyFix(currentFunction, true),
ChangeSuspendInHierarchyFix(currentFunction, false)
)
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.MoveDestination
import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.refactoring.move.createMoveUsageInfoIfPossible
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.stubindex.KotlinExactPackagesIndex
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
class KotlinAwareDelegatingMoveDestination(
private val delegate: MoveDestination,
private val targetPackage: PsiPackage?,
private val targetDirectory: PsiDirectory?
) : MoveDestination by delegate {
override fun analyzeModuleConflicts(
elements: MutableCollection<PsiElement>,
conflicts: MultiMap<PsiElement, String>,
usages: Array<out UsageInfo>
) {
delegate.analyzeModuleConflicts(elements, conflicts, usages)
if (targetPackage == null || targetDirectory == null) return
val project = targetDirectory.project
val moveTarget = KotlinDirectoryMoveTarget(FqName(targetPackage.qualifiedName), targetDirectory)
val packagesIndex = KotlinExactPackagesIndex.getInstance()
val directoriesToMove = elements.flatMapTo(LinkedHashSet<PsiDirectory>()) {
(it as? PsiPackage)?.directories?.toList() ?: emptyList()
}
val projectScope = project.projectScope()
val filesToProcess = elements.flatMapTo(LinkedHashSet<KtFile>()) {
if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList()
}
val extraElementsForReferenceSearch = LinkedHashSet<PsiElement>()
val extraElementCollector = object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is KtNamedDeclaration && element.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
element.parentsWithSelf.lastOrNull { it is KtNamedDeclaration }?.let { extraElementsForReferenceSearch += it }
stopWalking()
}
super.visitElement(element)
}
}
filesToProcess.flatMap {it.declarations}.forEach { it.accept(extraElementCollector) }
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
progressIndicator.pushState()
val extraUsages = ArrayList<UsageInfo>()
try {
progressIndicator.text = "Looking for Usages"
for ((index, element) in extraElementsForReferenceSearch.withIndex()) {
progressIndicator.fraction = (index + 1)/extraElementsForReferenceSearch.size.toDouble()
ReferencesSearch.search(element, projectScope).mapNotNullTo(extraUsages) { ref ->
createMoveUsageInfoIfPossible(ref, element, true, false)
}
}
}
finally {
progressIndicator.popState()
}
filesToProcess.forEach {
analyzeConflictsInFile(it, extraUsages, moveTarget, directoriesToMove, conflicts) {}
}
}
}