Move: Make MoveKotlinFileHandler stateless to prevent memory leaks. MoveKotlinTopLevelDeclarationsProcessor is made part of transient state, PackageNameInfo is recomputed based on file user data and container

This commit is contained in:
Alexey Sedunov
2015-07-24 17:41:25 +03:00
parent 978fb2c1cf
commit 4f12dcc069
@@ -17,14 +17,15 @@
package org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories package org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories
import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.psi.PsiCompiledElement import com.intellij.psi.*
import com.intellij.psi.PsiDirectory import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.idea.codeInsight.shorten.runWithElementsToShortenIsEmptyIgnored import org.jetbrains.kotlin.idea.codeInsight.shorten.runWithElementsToShortenIsEmptyIgnored
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.packageMatchesDirectory
import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo
import org.jetbrains.kotlin.idea.refactoring.move.getInternalReferencesToUpdateOnPackageNameChange import org.jetbrains.kotlin.idea.refactoring.move.getInternalReferencesToUpdateOnPackageNameChange
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.DeferredJetFileKotlinMoveTarget import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.DeferredJetFileKotlinMoveTarget
@@ -36,28 +37,37 @@ import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetNamedDeclaration import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.idea.core.packageMatchesDirectory
public class MoveKotlinFileHandler : MoveFileHandler() { public class MoveKotlinFileHandler : MoveFileHandler() {
private data class MoveContext( // This is special 'PsiElement' whose purpose is to wrap MoveKotlinTopLevelDeclarationsProcessor
val packageNameInfo: PackageNameInfo, // so that it can be kept in the transition map
class MoveContext(
psiManager: PsiManager,
val declarationMoveProcessor: MoveKotlinTopLevelDeclarationsProcessor val declarationMoveProcessor: MoveKotlinTopLevelDeclarationsProcessor
) ): LightElement(psiManager, JetLanguage.INSTANCE) {
override fun toString() = ""
}
private var moveContext: MoveContext? = null private fun JetFile.getPackageNameInfo(newParent: PsiDirectory, clearUserData: Boolean): PackageNameInfo? {
val shouldUpdatePackageDirective = updatePackageDirective ?: packageMatchesDirectory()
private fun initMoveContext(psiFile: PsiFile, newParent: PsiDirectory): MoveContext? { updatePackageDirective = if (clearUserData) null else shouldUpdatePackageDirective
this.moveContext = null
if (psiFile !is JetFile || !(psiFile.updatePackageDirective ?: psiFile.packageMatchesDirectory())) return null
if (!shouldUpdatePackageDirective) return null
val newPackage = newParent.getPackage() ?: return null val newPackage = newParent.getPackage() ?: return null
val packageNameInfo = PackageNameInfo(psiFile.getPackageFqName(), FqName(newPackage.getQualifiedName())) val oldPackageName = getPackageFqName()
if (packageNameInfo.oldPackageName == packageNameInfo.newPackageName) return null val newPackageName = FqName(newPackage.getQualifiedName())
val project = psiFile.getProject() if (oldPackageName == newPackageName) return null
return PackageNameInfo(oldPackageName, newPackageName)
}
private fun initMoveProcessor(psiFile: PsiFile, newParent: PsiDirectory): MoveKotlinTopLevelDeclarationsProcessor? {
if (psiFile !is JetFile) return null
val packageNameInfo = psiFile.getPackageNameInfo(newParent, false) ?: return null
val project = psiFile.getProject()
val declarationMoveProcessor = MoveKotlinTopLevelDeclarationsProcessor( val declarationMoveProcessor = MoveKotlinTopLevelDeclarationsProcessor(
project, project,
MoveKotlinTopLevelDeclarationsOptions( MoveKotlinTopLevelDeclarationsOptions(
@@ -70,10 +80,7 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
), ),
Mover.Idle Mover.Idle
) )
return declarationMoveProcessor
val moveContext = MoveContext(packageNameInfo, declarationMoveProcessor)
this.moveContext = moveContext
return moveContext
} }
override fun canProcessElement(element: PsiFile?): Boolean { override fun canProcessElement(element: PsiFile?): Boolean {
@@ -81,41 +88,31 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
return !JavaProjectRootsUtil.isOutsideJavaSourceRoot(element) return !JavaProjectRootsUtil.isOutsideJavaSourceRoot(element)
} }
override fun findUsages( override fun findUsages(psiFile: PsiFile, newParent: PsiDirectory, searchInComments: Boolean, searchInNonJavaFiles: Boolean) =
psiFile: PsiFile, initMoveProcessor(psiFile, newParent)?.findUsages()?.toList() ?: emptyList()
newParent: PsiDirectory,
searchInComments: Boolean,
searchInNonJavaFiles: Boolean
): List<UsageInfo> {
val moveContext = initMoveContext(psiFile, newParent) ?: return emptyList()
return moveContext.declarationMoveProcessor.findUsages().toList()
}
override fun prepareMovedFile(file: PsiFile, moveDestination: PsiDirectory, oldToNewMap: Map<PsiElement, PsiElement>) { override fun prepareMovedFile(file: PsiFile, moveDestination: PsiDirectory, oldToNewMap: MutableMap<PsiElement, PsiElement>) {
initMoveContext(file, moveDestination) val moveProcessor = initMoveProcessor(file, moveDestination) ?: return
val moveContext = MoveContext(file.getManager(), moveProcessor)
oldToNewMap[moveContext] = moveContext
} }
override fun updateMovedFile(file: PsiFile) { override fun updateMovedFile(file: PsiFile) {
if (file !is JetFile) return if (file !is JetFile) return
val moveContext = this.moveContext ?: return
file.updatePackageDirective = null val newDirectory = file.getParent() ?: return
val packageNameInfo = moveContext.packageNameInfo val packageNameInfo = file.getPackageNameInfo(newDirectory, true) ?: return
val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo) val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo)
file.getPackageDirective()?.setFqName(packageNameInfo.newPackageName) file.getPackageDirective()?.setFqName(packageNameInfo.newPackageName)
postProcessMoveUsages(internalUsages) postProcessMoveUsages(internalUsages)
} }
override fun retargetUsages(usageInfos: List<UsageInfo>?, oldToNewMap: Map<PsiElement, PsiElement>?) { override fun retargetUsages(usageInfos: List<UsageInfo>?, oldToNewMap: Map<PsiElement, PsiElement>) {
val moveContext = this.moveContext ?: return val moveContext = oldToNewMap.keySet().firstIsInstanceOrNull<MoveContext>() ?: return
val processor = moveContext.declarationMoveProcessor val processor = moveContext.declarationMoveProcessor
processor.project.runWithElementsToShortenIsEmptyIgnored { processor.project.runWithElementsToShortenIsEmptyIgnored {
try { usageInfos?.let { processor.execute(it) }
usageInfos?.let { processor.execute(it) }
} finally {
this.moveContext = null
}
} }
} }
} }