diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index dda66a7fef4..41587bccdf6 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -367,6 +367,10 @@ id="kotlin.moveTopLevelDeclarations" implementation="org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsHandler" order="first,before kotlin.moveFilesOrDirectories"/> + , + conflicts: MultiMap, + usages: Array + ) { + 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()) { + (it as? PsiPackage)?.directories?.toList() ?: emptyList() + } + val projectScope = project.projectScope() + val filesToProcess = elements.flatMapTo(LinkedHashSet()) { + if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList() + } + + val extraElementsForReferenceSearch = LinkedHashSet() + 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() + 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) {} + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareJavaMoveClassesOrPackagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareJavaMoveClassesOrPackagesHandler.kt new file mode 100644 index 00000000000..dd150fd8752 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareJavaMoveClassesOrPackagesHandler.kt @@ -0,0 +1,30 @@ +/* + * 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.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.refactoring.move.MoveCallback +import com.intellij.refactoring.move.moveClassesOrPackages.JavaMoveClassesOrPackagesHandler + +class KotlinAwareJavaMoveClassesOrPackagesHandler : JavaMoveClassesOrPackagesHandler() { + override fun createMoveClassesOrPackagesToNewDirectoryDialog( + directory: PsiDirectory, + elementsToMove: Array, + moveCallback: MoveCallback? + ) = KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog(directory, elementsToMove, moveCallback) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog.kt new file mode 100644 index 00000000000..4a822f93164 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog.kt @@ -0,0 +1,35 @@ +/* + * 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.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiPackage +import com.intellij.refactoring.MoveDestination +import com.intellij.refactoring.move.MoveCallback +import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesToNewDirectoryDialog + +class KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog( + directory: PsiDirectory, + elementsToMove: Array, + moveCallback: MoveCallback? +) : MoveClassesOrPackagesToNewDirectoryDialog(directory, elementsToMove, moveCallback) { + override fun createDestination(aPackage: PsiPackage, directory: PsiDirectory): MoveDestination { + val delegate = super.createDestination(aPackage, directory) + return KotlinAwareDelegatingMoveDestination(delegate, aPackage, directory) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt index a89a14a3e0c..f0d55dbef4a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.refactoring.getUsageContext +import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.contains @@ -396,3 +397,32 @@ class MoveConflictChecker( checkInternalMemberUsages(conflicts) } } + +fun analyzeConflictsInFile( + file: KtFile, + usages: Collection, + moveTarget: KotlinMoveTarget, + allElementsToMove: Collection, + conflicts: MultiMap, + onUsageUpdate: (List) -> Unit +) { + val elementsToMove = file.declarations + if (elementsToMove.isEmpty()) return + + val (internalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal } + val internalUsageSet = internalUsages.toMutableSet() + val externalUsageSet = externalUsages.toMutableSet() + + val conflictChecker = MoveConflictChecker( + file.project, + elementsToMove, + moveTarget, + elementsToMove.first(), + allElementsToMove = allElementsToMove + ) + conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts) + + if (externalUsageSet.size != externalUsages.size || internalUsageSet.size != internalUsages.size) { + onUsageUpdate((externalUsageSet + internalUsageSet).toList()) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveDirectoryWithClassesHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveDirectoryWithClassesHelper.kt index 6084aa22c95..0597c6695d2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveDirectoryWithClassesHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveDirectoryWithClassesHelper.kt @@ -29,10 +29,9 @@ import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.quoteIfNeeded import org.jetbrains.kotlin.idea.refactoring.invokeOnceOnCommandFinish -import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget -import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveConflictChecker import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor +import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import java.util.* @@ -84,23 +83,8 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() { for ((index, usageInfo) in infos.withIndex()) { if (usageInfo !is FileUsagesWrapper) continue - val elementsToMove = usageInfo.psiFile.declarations - if (elementsToMove.isEmpty()) continue - - val (internalUsages, externalUsages) = usageInfo.usages.partition { it is KotlinMoveUsage && it.isInternal } - val internalUsageSet = internalUsages.toMutableSet() - val externalUsageSet = externalUsages.toMutableSet() - - val conflictChecker = MoveConflictChecker( - project, - elementsToMove, - moveTarget, - elementsToMove.first(), - allElementsToMove = files - ) - conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts) - if (externalUsageSet.size != externalUsages.size || internalUsageSet.size != internalUsages.size) { - infos[index] = usageInfo.copy(usages = (externalUsageSet + internalUsageSet).toList()) + analyzeConflictsInFile(usageInfo.psiFile, usageInfo.usages, moveTarget, files, conflicts) { + infos[index] = usageInfo.copy(usages = it) } } } diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/A.iml b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/dummy.txt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Bar.kt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Bar.kt new file mode 100644 index 00000000000..f04f0e11eb7 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Bar.kt @@ -0,0 +1,8 @@ +package test.pack + +import test2.J + +class Bar { + internal val foo = Foo() + val j = J() +} \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Foo.kt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Foo.kt new file mode 100644 index 00000000000..8a6d20a89fe --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/A/src/test/pack/Foo.kt @@ -0,0 +1,7 @@ +package test.pack + +import test2.J + +internal class Foo { + val j = J() +} diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/B.iml b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/B.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/B.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/src/test2/J.java b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/src/test2/J.java new file mode 100644 index 00000000000..a995515eddc --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/after/B/src/test2/J.java @@ -0,0 +1,7 @@ +package test2; + +import test.pack.Foo; + +public class J { + Foo foo = new Foo(); +} diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/A/A.iml b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/A/src/test/dummy.txt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/A/src/test/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/B.iml b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/B.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/B.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/J.java b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/J.java new file mode 100644 index 00000000000..7c0a0b3cb19 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/J.java @@ -0,0 +1,7 @@ +package test2; + +import test2.pack.Foo; + +public class J { + Foo foo = new Foo(); +} diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Bar.kt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Bar.kt new file mode 100644 index 00000000000..d2ff5df67b6 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Bar.kt @@ -0,0 +1,8 @@ +package test2.pack + +import test2.J + +class Bar { + internal val foo = Foo() + val j = J() +} \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Foo.kt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Foo.kt new file mode 100644 index 00000000000..be238234078 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/before/B/src/test2/pack/Foo.kt @@ -0,0 +1,7 @@ +package test2.pack + +import test2.J + +internal class Foo { + val j = J() +} diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/conflicts.txt b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/conflicts.txt new file mode 100644 index 00000000000..c451f5d01c5 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/conflicts.txt @@ -0,0 +1,6 @@ +Class test2.J, referenced in property test2.pack.Bar.j, will not be accessible in module A +Class test2.J, referenced in property test2.pack.Foo.j, will not be accessible in module A +Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B +Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B +Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B +Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B \ No newline at end of file diff --git a/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test new file mode 100644 index 00000000000..2ee4b499c91 --- /dev/null +++ b/idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test @@ -0,0 +1,6 @@ +{ + "mainFile": "B/src/test2/pack/Bar.kt", + "type": "MOVE_PACKAGES", + "sourcePackage": "test2.pack", + "targetPackage": "test" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt index 44c503e918d..bbe8f4684d7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt @@ -22,6 +22,7 @@ import com.intellij.codeInsight.TargetElementUtil import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil @@ -44,6 +45,7 @@ import org.jetbrains.kotlin.idea.jsonUtils.getNullableString import org.jetbrains.kotlin.idea.jsonUtils.getString import org.jetbrains.kotlin.idea.refactoring.createKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring +import org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareDelegatingMoveDestination import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.refactoring.rename.loadTestConfiguration import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory @@ -185,13 +187,30 @@ enum class MoveAction { MOVE_PACKAGES { override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List, config: JsonObject) { val project = mainFile.project - val sourcePackage = config.getString("sourcePackage") - val targetPackage = config.getString("targetPackage") + val sourcePackageName = config.getString("sourcePackage") + val targetPackageName = config.getString("targetPackage") + + val sourcePackage = JavaPsiFacade.getInstance(project).findPackage(sourcePackageName)!! + val targetPackage = JavaPsiFacade.getInstance(project).findPackage(targetPackageName) + val targetDirectory = targetPackage?.directories?.first() + + val targetPackageWrapper = PackageWrapper(mainFile.manager, targetPackageName) + val moveDestination = if (targetDirectory != null) { + val targetSourceRoot = ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(targetDirectory.virtualFile)!! + KotlinAwareDelegatingMoveDestination( + AutocreatingSingleSourceRootMoveDestination(targetPackageWrapper, targetSourceRoot), + targetPackage, + targetDirectory + ) + } + else { + MultipleRootsMoveDestination(targetPackageWrapper) + } MoveClassesOrPackagesProcessor( project, - arrayOf(JavaPsiFacade.getInstance(project).findPackage(sourcePackage)!!), - MultipleRootsMoveDestination(PackageWrapper(mainFile.manager, targetPackage)), + arrayOf(sourcePackage), + moveDestination, /* searchInComments = */ false, /* searchInNonJavaFiles = */ true, /* moveCallback = */ null diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MultiModuleMoveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MultiModuleMoveTestGenerated.java index fbcf4ae9e2e..42215e7042c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MultiModuleMoveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MultiModuleMoveTestGenerated.java @@ -78,6 +78,12 @@ public class MultiModuleMoveTestGenerated extends AbstractMultiModuleMoveTest { doTest(fileName); } + @TestMetadata("movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test") + public void testMovePackageToUnrelatedModuleConflict_MovePackageToUnrelatedModuleConflict() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test"); + doTest(fileName); + } + @TestMetadata("moveToModuleWithoutLibConflict/moveToModuleWithoutLibConflict.test") public void testMoveToModuleWithoutLibConflict_MoveToModuleWithoutLibConflict() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/moveMultiModule/moveToModuleWithoutLibConflict/moveToModuleWithoutLibConflict.test");