mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] add conflicts detection when java files are moved together with non-classes (IDEA-277808)
GitOrigin-RevId: f28d9f997fff4671662f65f49e190470b14914cb
This commit is contained in:
committed by
intellij-monorepo-bot
parent
69f5144576
commit
bd40d80a9a
+38
-20
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.refactoring.move.moveClassesOrPackages;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
@@ -176,7 +176,7 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
protected UsageInfo @NotNull [] findUsages() {
|
||||
final List<UsageInfo> allUsages = new ArrayList<>();
|
||||
final List<UsageInfo> usagesToSkip = new ArrayList<>();
|
||||
myConflicts = new MultiMap<>();
|
||||
|
||||
for (PsiElement element : myElementsToMove) {
|
||||
String newName = getNewQName(element);
|
||||
UsageInfo[] usages = MoveClassesOrPackagesUtil.findUsages(
|
||||
@@ -195,17 +195,25 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
myMoveDestination.analyzeModuleConflicts(Arrays.asList(myElementsToMove), myConflicts,
|
||||
allUsages.toArray(UsageInfo.EMPTY_ARRAY));
|
||||
final UsageInfo[] usageInfos = allUsages.toArray(UsageInfo.EMPTY_ARRAY);
|
||||
detectPackageLocalsMoved(usageInfos, myConflicts);
|
||||
detectPackageLocalsUsed(myConflicts, myElementsToMove, myTargetPackage);
|
||||
myConflicts = new MultiMap<>();
|
||||
detectConflicts(usageInfos, myConflicts, myElementsToMove, myTargetPackage, myMoveDestination);
|
||||
myModuleInfoUsageDetector.detectModuleStatementsUsed(allUsages, myConflicts);
|
||||
detectMoveToDefaultPackage(usageInfos, myConflicts, myTargetPackage);
|
||||
allUsages.removeAll(usagesToSkip);
|
||||
return UsageViewUtil.removeDuplicatedUsages(allUsages.toArray(UsageInfo.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
public static void detectConflicts(UsageInfo[] usageInfos,
|
||||
MultiMap<PsiElement, @Nls String> conflicts,
|
||||
PsiElement @NotNull[] elementsToMove,
|
||||
@NotNull PackageWrapper targetPackage,
|
||||
@NotNull MoveDestination moveDestination) {
|
||||
moveDestination.analyzeModuleConflicts(Arrays.asList(elementsToMove), conflicts, usageInfos);
|
||||
detectPackageLocalsMoved(usageInfos, conflicts, targetPackage, elementsToMove);
|
||||
detectPackageLocalsUsed(conflicts, elementsToMove, targetPackage);
|
||||
detectMoveToDefaultPackage(usageInfos, conflicts, targetPackage);
|
||||
}
|
||||
|
||||
private static void detectMoveToDefaultPackage(UsageInfo[] infos,
|
||||
MultiMap<PsiElement, @Nls String> conflicts,
|
||||
PackageWrapper aPackage) {
|
||||
@@ -266,8 +274,8 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
return showConflicts(myConflicts, usages);
|
||||
}
|
||||
|
||||
private boolean isInsideMoved(PsiElement place) {
|
||||
for (PsiElement element : myElementsToMove) {
|
||||
private static boolean isInsideMoved(PsiElement place, PsiElement[] elementsToMove) {
|
||||
for (PsiElement element : elementsToMove) {
|
||||
if (element instanceof PsiClass) {
|
||||
if (PsiTreeUtil.isAncestor(element, place, false)) return true;
|
||||
}
|
||||
@@ -287,7 +295,10 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private void detectPackageLocalsMoved(final UsageInfo[] usages, final MultiMap<PsiElement, String> conflicts) {
|
||||
private static void detectPackageLocalsMoved(final UsageInfo[] usages,
|
||||
final MultiMap<PsiElement, String> conflicts,
|
||||
@NotNull PackageWrapper targetPackage,
|
||||
PsiElement[] elementsToMove) {
|
||||
Set<PsiClass> movedClasses = new HashSet<>();
|
||||
Map<PsiClass,Set<PsiElement>> reportedClassToContainers = new HashMap<>();
|
||||
for (UsageInfo usage : usages) {
|
||||
@@ -305,11 +316,11 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
if (!reported.contains(container)) {
|
||||
reported.add(container);
|
||||
PsiFile containingFile = element.getContainingFile();
|
||||
if (containingFile != null && !isInsideMoved(element)) {
|
||||
if (containingFile != null && !isInsideMoved(element, elementsToMove)) {
|
||||
PsiDirectory directory = containingFile.getContainingDirectory();
|
||||
if (directory != null) {
|
||||
PsiPackage usagePackage = JavaDirectoryService.getInstance().getPackage(directory);
|
||||
if (usagePackage != null && !myTargetPackage.equalToPackage(usagePackage)) {
|
||||
if (usagePackage != null && !targetPackage.equalToPackage(usagePackage)) {
|
||||
final String message = JavaRefactoringBundle.message("a.package.local.class.0.will.no.longer.be.accessible.from.1",
|
||||
CommonRefactoringUtil.htmlEmphasize(aClass.getName()),
|
||||
RefactoringUIUtil.getDescription(
|
||||
@@ -323,7 +334,7 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
final MyClassInstanceReferenceVisitor instanceReferenceVisitor = new MyClassInstanceReferenceVisitor(conflicts);
|
||||
final MyClassInstanceReferenceVisitor instanceReferenceVisitor = new MyClassInstanceReferenceVisitor(targetPackage, elementsToMove, conflicts);
|
||||
for (final PsiClass aClass : movedClasses) {
|
||||
String visibility = VisibilityUtil.getVisibilityModifier(aClass.getModifierList());
|
||||
if (PsiModifier.PACKAGE_LOCAL.equals(visibility)) {
|
||||
@@ -828,13 +839,19 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
return JavaRefactoringBundle.message("move.classes.command", elements, target);
|
||||
}
|
||||
|
||||
private class MyClassInstanceReferenceVisitor implements ClassInstanceScanner.ClassInstanceReferenceVisitor {
|
||||
private static class MyClassInstanceReferenceVisitor implements ClassInstanceScanner.ClassInstanceReferenceVisitor {
|
||||
private final MultiMap<PsiElement, String> myConflicts;
|
||||
private final Map<PsiModifierListOwner,HashSet<PsiElement>> myReportedElementToContainer = new HashMap<>();
|
||||
private final Map<PsiClass, RefactoringUtil.IsDescendantOf> myIsDescendantOfCache = new HashMap<>();
|
||||
private final @NotNull PackageWrapper myTargetPackage;
|
||||
private final PsiElement[] myElementsToMove;
|
||||
|
||||
MyClassInstanceReferenceVisitor(MultiMap<PsiElement, String> conflicts) {
|
||||
MyClassInstanceReferenceVisitor(@NotNull PackageWrapper targetPackage,
|
||||
PsiElement[] elementsToMove,
|
||||
MultiMap<PsiElement, String> conflicts) {
|
||||
myConflicts = conflicts;
|
||||
myTargetPackage = targetPackage;
|
||||
myElementsToMove = elementsToMove;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -857,14 +874,14 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
|
||||
private synchronized void visitMemberReference(final PsiModifierListOwner member, PsiReferenceExpression qualified, final RefactoringUtil.IsDescendantOf descendantOf) {
|
||||
if (member.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
|
||||
visitPackageLocalMemberReference(qualified, member);
|
||||
visitPackageLocalMemberReference(qualified, member, myElementsToMove);
|
||||
} else if (member.hasModifierProperty(PsiModifier.PROTECTED)) {
|
||||
final PsiExpression qualifier = qualified.getQualifierExpression();
|
||||
if (qualifier != null && !(qualifier instanceof PsiThisExpression) && !(qualifier instanceof PsiSuperExpression)) {
|
||||
visitPackageLocalMemberReference(qualified, member);
|
||||
visitPackageLocalMemberReference(qualified, member, myElementsToMove);
|
||||
} else {
|
||||
if (!isInInheritor(qualified, descendantOf)) {
|
||||
visitPackageLocalMemberReference(qualified, member);
|
||||
visitPackageLocalMemberReference(qualified, member, myElementsToMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,13 +896,14 @@ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void visitPackageLocalMemberReference(PsiJavaCodeReferenceElement qualified, PsiModifierListOwner member) {
|
||||
private void visitPackageLocalMemberReference(PsiJavaCodeReferenceElement qualified,
|
||||
PsiModifierListOwner member, PsiElement[] elementsToMove) {
|
||||
PsiElement container = ConflictsUtil.getContainer(qualified);
|
||||
Set<PsiElement> reportedContainers = myReportedElementToContainer.computeIfAbsent(member, __ -> new HashSet<>());
|
||||
|
||||
if (!reportedContainers.contains(container)) {
|
||||
reportedContainers.add(container);
|
||||
if (!isInsideMoved(container)) {
|
||||
if (!isInsideMoved(container, elementsToMove)) {
|
||||
PsiFile containingFile = container.getContainingFile();
|
||||
if (containingFile != null) {
|
||||
PsiDirectory directory = containingFile.getContainingDirectory();
|
||||
|
||||
+16
-15
@@ -1,18 +1,4 @@
|
||||
/*
|
||||
* Copyright 2000-2011 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.
|
||||
*/
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package com.intellij.refactoring.move.moveClassesOrPackages;
|
||||
|
||||
@@ -22,10 +8,12 @@ import com.intellij.openapi.roots.JavaProjectRootsUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler;
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -65,6 +53,19 @@ public class MoveJavaFileHandler extends MoveFileHandler {
|
||||
return result.isEmpty() ? null : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectConflicts(MultiMap<PsiElement, String> conflicts,
|
||||
PsiElement[] elementsToMove,
|
||||
UsageInfo[] usages,
|
||||
PsiDirectory targetDirectory) {
|
||||
PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory);
|
||||
if (aPackage != null) {
|
||||
PackageWrapper packageWrapper = new PackageWrapper(aPackage);
|
||||
MoveClassesOrPackagesProcessor.detectConflicts(usages, conflicts, elementsToMove, packageWrapper, new SingleSourceRootMoveDestination(packageWrapper,
|
||||
targetDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retargetUsages(List<UsageInfo> usageInfos, Map<PsiElement, PsiElement> oldToNewMap) {
|
||||
for (UsageInfo usage : usageInfos) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package p1;
|
||||
public class A {
|
||||
B b;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
class B {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package p1;
|
||||
public class A {
|
||||
B b;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package p1;
|
||||
class B {}
|
||||
@@ -3,6 +3,7 @@ package com.intellij.refactoring;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import org.junit.Assert;
|
||||
|
||||
/**
|
||||
* @author ven
|
||||
@@ -14,6 +15,15 @@ public class MoveJavaFileTest extends MoveFileTestCase {
|
||||
}
|
||||
|
||||
public void testPackageInfo() { doTest("pack2", "pack1/package-info.java"); }
|
||||
public void testConflict() {
|
||||
try {
|
||||
doTest("p2", "p1/B.java");
|
||||
fail("Conflict not detected!");
|
||||
}
|
||||
catch (BaseRefactoringProcessor.ConflictsInTestsException e) {
|
||||
Assert.assertEquals("Package-local class <b><code>B</code></b> will no longer be accessible from field <b><code>A.b</code></b>", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static class BranchTest extends MoveJavaFileTest {
|
||||
@Override
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
/*
|
||||
* Copyright 2000-2009 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.
|
||||
*/
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.refactoring;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -65,7 +51,20 @@ public interface MoveDestination {
|
||||
@Nullable @NlsContexts.DialogMessage
|
||||
String verify(PsiPackage source);
|
||||
|
||||
void analyzeModuleConflicts(@NotNull Collection<? extends PsiElement> elements, @NotNull MultiMap<PsiElement,String> conflicts, final UsageInfo[] usages);
|
||||
/**
|
||||
* Searches for conflicts which arise when elements are moved from one module to another
|
||||
*
|
||||
* E.g. when target module has no dependency on required library,
|
||||
* or when usage in the third module has no dependency on the target module
|
||||
*
|
||||
* Impl note: do nothing if elements are kept in the same module
|
||||
*/
|
||||
void analyzeModuleConflicts(@NotNull Collection<? extends PsiElement> elements,
|
||||
@NotNull MultiMap<PsiElement,String> conflicts,
|
||||
final UsageInfo[] usages);
|
||||
|
||||
/**
|
||||
* @return true if runtime scope of {@code place}'s module contains target destination
|
||||
*/
|
||||
boolean isTargetAccessible(@NotNull Project project, @NotNull VirtualFile place);
|
||||
}
|
||||
|
||||
+29
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package com.intellij.refactoring.move.moveFilesOrDirectories;
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -53,6 +54,21 @@ public abstract class MoveFileHandler {
|
||||
@Nullable
|
||||
public abstract List<UsageInfo> findUsages(PsiFile psiFile, PsiDirectory newParent, boolean searchInComments, boolean searchInNonJavaFiles);
|
||||
|
||||
/**
|
||||
* Finds conflicts which may arise when file is moved,
|
||||
* e.g. missing module dependency or visibility which makes moved element not accessible
|
||||
*
|
||||
* {@link #detectConflicts(PsiElement[], UsageInfo[], PsiDirectory, MultiMap)} passes all elements which were moved
|
||||
* as well as all found usages for each handler. It's the responsibility of handler to ignore inappropriate elements/usages.
|
||||
*
|
||||
* @param elementsToMove all elements which were moved during refactoring, including those which won't be normally processed by the handler
|
||||
* @param usages all usages found during refactoring
|
||||
*/
|
||||
public void detectConflicts(MultiMap<PsiElement, String> conflicts,
|
||||
PsiElement[] elementsToMove,
|
||||
UsageInfo[] usages,
|
||||
PsiDirectory targetDirectory) {}
|
||||
|
||||
/**
|
||||
* After a file has been moved, updates the references to the file so that they point to the new location of the file.
|
||||
*
|
||||
@@ -69,6 +85,18 @@ public abstract class MoveFileHandler {
|
||||
*/
|
||||
public abstract void updateMovedFile(PsiFile file) throws IncorrectOperationException;
|
||||
|
||||
/**
|
||||
* Fills {@code conflicts} based on elements which were moved, target directory and already calculated usages.
|
||||
*/
|
||||
public static void detectConflicts(PsiElement[] elementsToMove,
|
||||
UsageInfo[] usageInfos,
|
||||
PsiDirectory targetDirectory,
|
||||
MultiMap<PsiElement, String> conflicts) {
|
||||
for (MoveFileHandler handler : EP_NAME.getExtensionList()) {
|
||||
handler.detectConflicts(conflicts, elementsToMove, usageInfos, targetDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MoveFileHandler forElement(PsiFile element) {
|
||||
for(MoveFileHandler processor: EP_NAME.getExtensionList()) {
|
||||
@@ -106,6 +134,4 @@ public abstract class MoveFileHandler {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+16
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.refactoring.move.moveFilesOrDirectories;
|
||||
|
||||
import com.intellij.ide.util.EditorHelper;
|
||||
@@ -6,11 +6,14 @@ import com.intellij.lang.FileASTNode;
|
||||
import com.intellij.model.BranchableUsageInfo;
|
||||
import com.intellij.model.ModelBranch;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.paths.PsiDynaReference;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
@@ -28,6 +31,7 @@ import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewDescriptor;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import one.util.streamex.EntryStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -273,6 +277,17 @@ public class MoveFilesOrDirectoriesProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
|
||||
MultiMap<PsiElement, String> conflicts = new MultiMap<>();
|
||||
UsageInfo[] usages = refUsages.get();
|
||||
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ReadAction.run(() -> MoveFileHandler.detectConflicts(myElementsToMove, usages, myNewParent, conflicts)),
|
||||
RefactoringBundle.message("detecting.possible.conflicts"), true, myProject)) {
|
||||
return false;
|
||||
}
|
||||
return showConflicts(conflicts, usages);
|
||||
}
|
||||
|
||||
private void afterMove(@Nullable ModelBranch branch, Set<SmartPsiElementPointer<PsiFile>> movedFiles, List<Runnable> notifyListeners) {
|
||||
notifyListeners.forEach(Runnable::run);
|
||||
if (myMoveCallback != null) {
|
||||
|
||||
Reference in New Issue
Block a user