move selected classes to inner in one step (IDEA-39729 )

This commit is contained in:
anna
2010-01-26 16:55:29 +03:00
parent d74e50a66d
commit 53c3a8b5dc
15 changed files with 211 additions and 118 deletions
@@ -53,11 +53,11 @@ import java.util.*;
public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassToInnerProcessor");
private PsiClass myClassToMove;
private PsiClass[] myClassesToMove;
private final PsiClass myTargetClass;
private PsiPackage mySourcePackage;
private PsiPackage[] mySourcePackage;
private final PsiPackage myTargetPackage;
private String mySourceVisibility;
private String[] mySourceVisibility;
private final boolean mySearchInComments;
private final boolean mySearchInNonJavaFiles;
private NonCodeUsageInfo[] myNonCodeUsages;
@@ -65,13 +65,13 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
private final MoveCallback myMoveCallback;
public MoveClassToInnerProcessor(Project project,
final PsiClass classToMove,
final PsiClass[] classesToMove,
@NotNull final PsiClass targetClass,
boolean searchInComments,
boolean searchInNonJavaFiles,
MoveCallback moveCallback) {
super(project);
setClassToMove(classToMove);
setClassesToMove(classesToMove);
myTargetClass = targetClass;
mySearchInComments = searchInComments;
mySearchInNonJavaFiles = searchInNonJavaFiles;
@@ -79,29 +79,30 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory());
}
private void setClassToMove(final PsiClass classToMove) {
myClassToMove = classToMove;
mySourceVisibility = VisibilityUtil.getVisibilityModifier(myClassToMove.getModifierList());
mySourcePackage = JavaDirectoryService.getInstance().getPackage(myClassToMove.getContainingFile().getContainingDirectory());
private void setClassesToMove(final PsiClass[] classesToMove) {
myClassesToMove = classesToMove;
mySourcePackage = new PsiPackage[classesToMove.length];
mySourceVisibility = new String[classesToMove.length];
for (int i = 0; i < classesToMove.length; i++) {
PsiClass psiClass = classesToMove[i];
mySourceVisibility[i] = VisibilityUtil.getVisibilityModifier(psiClass.getModifierList());
mySourcePackage[i] = JavaDirectoryService.getInstance().getPackage(psiClass.getContainingFile().getContainingDirectory());
}
}
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new MoveClassesOrPackagesViewDescriptor(new PsiElement[] { myClassToMove },
return new MoveClassesOrPackagesViewDescriptor(myClassesToMove,
mySearchInComments, mySearchInNonJavaFiles,
myTargetClass.getQualifiedName());
}
@NotNull
public UsageInfo[] findUsages() {
List<UsageInfo> usages = new ArrayList<UsageInfo>();
String newName = myTargetClass.getQualifiedName() + "." + myClassToMove.getName();
Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(myClassToMove, mySearchInComments,
mySearchInNonJavaFiles, newName));
for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext();) {
UsageInfo usageInfo = iterator.next();
if (!(usageInfo instanceof NonCodeUsageInfo) && PsiTreeUtil.isAncestor(myClassToMove, usageInfo.getElement(), false)) {
iterator.remove();
}
final List<UsageInfo> usages = new ArrayList<UsageInfo>();
for (PsiClass classToMove : myClassesToMove) {
final String newName = myTargetClass.getQualifiedName() + "." + classToMove.getName();
Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments,
mySearchInNonJavaFiles, newName));
}
return usages.toArray(new UsageInfo[usages.size()]);
}
@@ -111,40 +112,45 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
protected void refreshElements(final PsiElement[] elements) {
assert elements.length == 1;
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
setClassToMove((PsiClass)elements[0]);
final PsiClass[] classesToMove = new PsiClass[elements.length];
for (int i = 0; i < classesToMove.length; i++) {
classesToMove[i] = (PsiClass)elements[i];
}
setClassesToMove(classesToMove);
}
});
}
protected void performRefactoring(UsageInfo[] usages) {
if (!prepareWritable(usages)) return;
final List<PsiElement> importStatements = new ArrayList<PsiElement>();
if (!CodeStyleSettingsManager.getSettings(myProject).INSERT_INNER_CLASS_IMPORTS) {
usages = filterUsagesInImportStatements(usages, importStatements);
}
saveNonCodeUsages(usages);
final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
try {
saveNonCodeUsages(usages);
ChangeContextUtil.encodeContextInfo(myClassToMove, true);
PsiClass newClass = (PsiClass)myTargetClass.addBefore(myClassToMove, myTargetClass.getRBrace());
PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
retargetClassRefs(myClassToMove, newClass);
final List<PsiElement> importStatements = new ArrayList<PsiElement>();
if (!CodeStyleSettingsManager.getSettings(myProject).INSERT_INNER_CLASS_IMPORTS) {
usages = filterUsagesInImportStatements(usages, importStatements);
for (PsiClass classToMove : myClassesToMove) {
ChangeContextUtil.encodeContextInfo(classToMove, true);
PsiClass newClass = (PsiClass)myTargetClass.addBefore(classToMove, myTargetClass.getRBrace());
PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
oldToNewElementsMapping.put(classToMove, newClass);
}
Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
oldToNewElementsMapping.put(myClassToMove, newClass);
myNonCodeUsages = MoveClassesOrPackagesProcessor.retargetUsages(usages, oldToNewElementsMapping);
retargetNonCodeUsages(newClass);
retargetNonCodeUsages(oldToNewElementsMapping);
JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)newClass.getContainingFile());
retargetClassRefsInMoved(oldToNewElementsMapping);
myClassToMove.delete();
for(PsiElement element: importStatements) {
JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)myTargetClass.getContainingFile());
for (PsiClass classToMove : myClassesToMove) {
classToMove.delete();
}
for (PsiElement element: importStatements) {
if (element.isValid()) {
element.delete();
}
@@ -157,7 +163,7 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
private boolean prepareWritable(final UsageInfo[] usages) {
Set<PsiElement> elementsToMakeWritable = new HashSet<PsiElement>();
elementsToMakeWritable.add(myClassToMove);
Collections.addAll(elementsToMakeWritable, myClassesToMove);
elementsToMakeWritable.add(myTargetClass);
for(UsageInfo usage: usages) {
PsiElement element = usage.getElement();
@@ -172,40 +178,44 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
private void saveNonCodeUsages(final UsageInfo[] usages) {
for(UsageInfo usageInfo: usages) {
if (usageInfo instanceof NonCodeUsageInfo) {
final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo;
PsiElement element = nonCodeUsage.getElement();
if (element != null && PsiTreeUtil.isAncestor(myClassToMove, element, false)) {
List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
if (list == null) {
list = new ArrayList<NonCodeUsageInfo>();
element.putCopyableUserData(ourNonCodeUsageKey, list);
for (PsiClass classToMove : myClassesToMove) {
for(UsageInfo usageInfo: usages) {
if (usageInfo instanceof NonCodeUsageInfo) {
final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo;
PsiElement element = nonCodeUsage.getElement();
if (element != null && PsiTreeUtil.isAncestor(classToMove, element, false)) {
List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
if (list == null) {
list = new ArrayList<NonCodeUsageInfo>();
element.putCopyableUserData(ourNonCodeUsageKey, list);
}
list.add(nonCodeUsage);
}
list.add(nonCodeUsage);
}
}
}
}
private void retargetNonCodeUsages(final PsiClass newClass) {
newClass.accept(new PsiRecursiveElementVisitor() {
@Override public void visitElement(final PsiElement element) {
super.visitElement(element);
List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
if (list != null) {
for(NonCodeUsageInfo info: list) {
for(int i=0; i<myNonCodeUsages.length; i++) {
if (myNonCodeUsages [i] == info) {
myNonCodeUsages [i] = info.replaceElement(element);
break;
private void retargetNonCodeUsages(final Map<PsiElement, PsiElement> oldToNewElementMap) {
for (PsiElement newClass : oldToNewElementMap.values()) {
newClass.accept(new PsiRecursiveElementVisitor() {
@Override public void visitElement(final PsiElement element) {
super.visitElement(element);
List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
if (list != null) {
for(NonCodeUsageInfo info: list) {
for(int i=0; i<myNonCodeUsages.length; i++) {
if (myNonCodeUsages [i] == info) {
myNonCodeUsages [i] = info.replaceElement(element);
break;
}
}
}
element.putCopyableUserData(ourNonCodeUsageKey, null);
}
element.putCopyableUserData(ourNonCodeUsageKey, null);
}
}
});
});
}
}
protected void performPsiSpoilingRefactoring() {
@@ -220,24 +230,29 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
}
private static void retargetClassRefs(final PsiClass classToMove, final PsiClass newClass) {
newClass.accept(new JavaRecursiveElementVisitor() {
@Override public void visitReferenceElement(final PsiJavaCodeReferenceElement reference) {
PsiElement element = reference.resolve();
if (element instanceof PsiClass && PsiTreeUtil.isAncestor(classToMove, element, false)) {
PsiClass newInnerClass = findMatchingClass(classToMove, newClass, (PsiClass) element);
try {
reference.bindToElement(newInnerClass);
private static void retargetClassRefsInMoved(final Map<PsiElement, PsiElement> oldToNewElementsMapping) {
for (final PsiElement newClass : oldToNewElementsMapping.values()) {
newClass.accept(new JavaRecursiveElementVisitor() {
@Override public void visitReferenceElement(final PsiJavaCodeReferenceElement reference) {
PsiElement element = reference.resolve();
if (element instanceof PsiClass) {
for (PsiElement oldClass : oldToNewElementsMapping.keySet()) {
if (PsiTreeUtil.isAncestor(oldClass, element, false)) {
PsiClass newInnerClass = findMatchingClass((PsiClass)oldClass, (PsiClass)oldToNewElementsMapping.get(oldClass), (PsiClass)element);
try {
reference.bindToElement(newInnerClass);
return;
}
catch (IncorrectOperationException ex) {
LOG.error(ex);
}
}
}
}
catch(IncorrectOperationException ex) {
LOG.error(ex);
}
}
else {
super.visitReferenceElement(reference);
}
}
});
});
}
}
private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
@@ -268,7 +283,7 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
protected String getCommandName() {
return RefactoringBundle.message("move.class.to.inner.command.name",
myClassToMove.getQualifiedName(),
myClassesToMove[0].getQualifiedName(),
myTargetClass.getQualifiedName());
}
@@ -283,42 +298,46 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
public MultiMap<PsiElement, String> getConflicts(final UsageInfo[] usages) {
MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final PsiClass innerClass = myTargetClass.findInnerClassByName(myClassToMove.getName(), false);
if (innerClass != null) {
conflicts.putValue(innerClass, RefactoringBundle.message("move.to.inner.duplicate.inner.class",
CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()),
CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName())));
for (PsiClass classToMove : myClassesToMove) {
final PsiClass innerClass = myTargetClass.findInnerClassByName(classToMove.getName(), false);
if (innerClass != null) {
conflicts.putValue(innerClass, RefactoringBundle.message("move.to.inner.duplicate.inner.class",
CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()),
CommonRefactoringUtil.htmlEmphasize(classToMove.getName())));
}
}
String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(myClassToMove.getModifierList());
String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList());
for (int i = 0; i < myClassesToMove.length; i++) {
PsiClass classToMove = myClassesToMove[i];
String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(classToMove.getModifierList());
String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList());
boolean moveToOtherPackage = !Comparing.equal(mySourcePackage, myTargetPackage);
if (moveToOtherPackage) {
PsiElement[] elementsToMove = new PsiElement[] { myClassToMove };
myClassToMove.accept(new PackageLocalsUsageCollector(elementsToMove, new PackageWrapper(myTargetPackage), conflicts));
}
boolean moveToOtherPackage = !Comparing.equal(mySourcePackage[i], myTargetPackage);
if (moveToOtherPackage) {
classToMove.accept(new PackageLocalsUsageCollector(myClassesToMove, new PackageWrapper(myTargetPackage), conflicts));
}
ConflictsCollector collector = new ConflictsCollector(conflicts);
if ((moveToOtherPackage &&
(classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) ||
targetClassVisibility.equals(PsiModifier.PRIVATE)) {
detectInaccessibleClassUsages(usages, collector);
}
if (moveToOtherPackage) {
detectInaccessibleMemberUsages(collector);
ConflictsCollector collector = new ConflictsCollector(classToMove, conflicts);
if ((moveToOtherPackage &&
(classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) ||
targetClassVisibility.equals(PsiModifier.PRIVATE)) {
detectInaccessibleClassUsages(usages, collector, mySourceVisibility[i]);
}
if (moveToOtherPackage) {
detectInaccessibleMemberUsages(collector);
}
}
return conflicts;
}
private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector) {
private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector, final String visibility) {
for(UsageInfo usage: usages) {
if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) {
PsiElement element = usage.getElement();
if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue;
if (isInaccessibleFromTarget(element, mySourceVisibility)) {
collector.addConflict(myClassToMove, element);
if (isInaccessibleFromTarget(element, visibility)) {
collector.addConflict(collector.getClassToMove(), element);
}
}
}
@@ -331,7 +350,7 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
private void detectInaccessibleMemberUsages(final ConflictsCollector collector) {
PsiElement[] members = collectPackageLocalMembers();
PsiElement[] members = collectPackageLocalMembers(collector.getClassToMove());
for(PsiElement member: members) {
ReferencesSearch.search(member).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference psiReference) {
@@ -345,8 +364,8 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
}
private PsiElement[] collectPackageLocalMembers() {
return PsiTreeUtil.collectElements(myClassToMove, new PsiElementFilter() {
private static PsiElement[] collectPackageLocalMembers(PsiElement classToMove) {
return PsiTreeUtil.collectElements(classToMove, new PsiElementFilter() {
public boolean isAccepted(final PsiElement element) {
if (element instanceof PsiMember) {
PsiMember member = (PsiMember) element;
@@ -359,11 +378,13 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
});
}
private class ConflictsCollector {
private static class ConflictsCollector {
private final PsiClass myClassToMove;
private final MultiMap<PsiElement, String> myConflicts;
private final Set<PsiElement> myReportedContainers = new HashSet<PsiElement>();
public ConflictsCollector(final MultiMap<PsiElement, String> conflicts) {
public ConflictsCollector(PsiClass classToMove, final MultiMap<PsiElement, String> conflicts) {
myClassToMove = classToMove;
myConflicts = conflicts;
}
@@ -380,5 +401,9 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
myConflicts.putValue(targetElement, message);
}
}
public PsiElement getClassToMove() {
return myClassToMove;
}
}
}
@@ -423,17 +423,17 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
private void invokeMoveToInner() {
saveRefactoringSettings();
PsiClass targetClass = findTargetClass();
final PsiClass targetClass = findTargetClass();
final PsiClass[] classesToMove = new PsiClass[myElementsToMove.length];
for (int i = 0; i < myElementsToMove.length; i++) {
PsiClass psiClass = (PsiClass)myElementsToMove[i];
// fire callback after last element has been processed
invokeRefactoring(createMoveToInnerProcessor(targetClass, psiClass, i == myElementsToMove.length - 1 ? myMoveCallback : null));
classesToMove[i] = (PsiClass)myElementsToMove[i];
}
invokeRefactoring(createMoveToInnerProcessor(targetClass, classesToMove, myMoveCallback));
}
//for scala plugin
protected MoveClassToInnerProcessor createMoveToInnerProcessor(PsiClass destination, @NotNull PsiClass psiClass, @Nullable final MoveCallback callback) {
return new MoveClassToInnerProcessor(getProject(), psiClass, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback);
protected MoveClassToInnerProcessor createMoveToInnerProcessor(PsiClass destination, @NotNull PsiClass[] classesToMove, @Nullable final MoveCallback callback) {
return new MoveClassToInnerProcessor(getProject(), classesToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback);
}
protected final boolean isSearchInNonJavaFiles() {
@@ -0,0 +1,17 @@
package pack2;
public class A {
/**
* pack2.A.Class0
*/
public static class Class1 {
Class0 cl0;
}
/**
* pack2.A.Class1
*/
public static class Class0 {
Class1 cl1;
}
}
@@ -0,0 +1,12 @@
package packUser;
import pack2.A;
/**
* pack2.A.Class1
* pack2.A.Class0
*/
public class Class2 {
A.Class1 myClass1;
A.Class0 myClass0;
}
@@ -0,0 +1,10 @@
package pack0;
import pack1.*;
/**
* pack1.Class1
*/
public class Class0 {
Class1 cl1;
}
@@ -0,0 +1,10 @@
package pack1;
import pack0.*;
/**
* pack0.Class0
*/
public class Class1 {
Class0 cl0;
}
@@ -0,0 +1,4 @@
package pack2;
public class A {
}
@@ -0,0 +1,13 @@
package packUser;
import pack1.Class1;
import pack0.Class0;
/**
* pack1.Class1
* pack0.Class0
*/
public class Class2 {
Class1 myClass1;
Class0 myClass0;
}
@@ -35,6 +35,10 @@ public class MoveClassToInnerTest extends CodeInsightTestCase {
doTest(new String[] { "pack1.Class1" }, "pack2.A");
}
public void testSimultaneousMove() throws Exception {
doTest(new String[] { "pack1.Class1", "pack0.Class0" }, "pack2.A");
}
public void testMoveMultiple1() throws Exception {
doTest(new String[] { "pack1.Class1", "pack1.Class2" }, "pack2.A");
}
@@ -112,7 +116,7 @@ public class MoveClassToInnerTest extends CodeInsightTestCase {
prepareTest();
PsiClass classToMove = myJavaFacade.findClass(className, ProjectScope.getAllScope(myProject));
PsiClass targetClass = myJavaFacade.findClass(targetClassName, ProjectScope.getAllScope(myProject));
MoveClassToInnerProcessor processor = new MoveClassToInnerProcessor(myProject, classToMove, targetClass, true, true, null);
MoveClassToInnerProcessor processor = new MoveClassToInnerProcessor(myProject, new PsiClass[]{classToMove}, targetClass, true, true, null);
UsageInfo[] usages = processor.findUsages();
MultiMap<PsiElement,String> conflicts = processor.getConflicts(usages);
assertSameElements(conflicts.values() , expectedConflicts);
@@ -129,9 +133,7 @@ public class MoveClassToInnerTest extends CodeInsightTestCase {
PsiClass targetClass = myJavaFacade.findClass(targetClassName, ProjectScope.getAllScope(myProject));
assertNotNull(targetClass);
for(PsiClass psiClass: classes) {
new MoveClassToInnerProcessor(myProject, psiClass, targetClass, true, true, null).run();
}
new MoveClassToInnerProcessor(myProject, classes, targetClass, true, true, null).run();
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
FileDocumentManager.getInstance().saveAllDocuments();
}