IDEA-84145 move inner class

This commit is contained in:
Maxim.Medvedev
2012-05-03 16:19:06 +04:00
parent 2fdb536ee9
commit a96cb93ed3
133 changed files with 1122 additions and 123 deletions
@@ -0,0 +1,157 @@
/*
* Copyright 2000-2012 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 com.intellij.refactoring.move.moveClassesOrPackages;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.NonCodeUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author Max Medvedev
*/
public class JavaMoveClassToInnerHandler implements MoveClassToInnerHandler {
private static final Logger LOG = Logger.getInstance(JavaMoveClassToInnerHandler.class);
@Override
public PsiClass moveClass(@NotNull PsiClass aClass, @NotNull PsiClass targetClass) {
if (aClass.getLanguage() != JavaLanguage.INSTANCE) {
return null;
}
ChangeContextUtil.encodeContextInfo(aClass, true);
PsiClass newClass = (PsiClass)targetClass.addBefore(aClass, targetClass.getRBrace());
if (targetClass.isInterface()) {
PsiUtil.setModifierProperty(newClass, PsiModifier.PACKAGE_LOCAL, true);
}
else {
PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
}
return (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
}
@Override
public List<PsiElement> filterImports(@NotNull List<UsageInfo> usageInfos, @NotNull Project project) {
final List<PsiElement> importStatements = new ArrayList<PsiElement>();
if (!CodeStyleSettingsManager.getSettings(project).INSERT_INNER_CLASS_IMPORTS) {
filterUsagesInImportStatements(usageInfos, importStatements);
}
else {
//rebind imports first
Collections.sort(usageInfos, new Comparator<UsageInfo>() {
public int compare(UsageInfo o1, UsageInfo o2) {
return PsiUtil.BY_POSITION.compare(o1.getElement(), o2.getElement());
}
});
}
return importStatements;
}
private static void filterUsagesInImportStatements(final List<UsageInfo> usages, final List<PsiElement> importStatements) {
for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext(); ) {
UsageInfo usage = iterator.next();
PsiElement element = usage.getElement();
if (element == null) continue;
PsiImportStatement stmt = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
if (stmt != null) {
importStatements.add(stmt);
iterator.remove();
}
}
}
public void retargetClassRefsInMoved(@NotNull final Map<PsiElement, PsiElement> oldToNewElementsMapping) {
for (final PsiElement newClass : oldToNewElementsMapping.values()) {
if (newClass.getLanguage() != JavaLanguage.INSTANCE) continue;
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);
}
}
}
}
super.visitReferenceElement(reference);
}
});
}
}
private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
if (classToMove == innerClass) {
return newClass;
}
PsiClass parentClass = findMatchingClass(classToMove, newClass, innerClass.getContainingClass());
PsiClass newInnerClass = parentClass.findInnerClassByName(innerClass.getName(), false);
assert newInnerClass != null;
return newInnerClass;
}
public void retargetNonCodeUsages(@NotNull final Map<PsiElement, PsiElement> oldToNewElementMap,
@NotNull final NonCodeUsageInfo[] nonCodeUsages) {
for (PsiElement newClass : oldToNewElementMap.values()) {
if (newClass.getLanguage() != JavaLanguage.INSTANCE) continue;
newClass.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(final PsiElement element) {
super.visitElement(element);
List<NonCodeUsageInfo> list = element.getCopyableUserData(MoveClassToInnerProcessor.ourNonCodeUsageKey);
if (list != null) {
for (NonCodeUsageInfo info : list) {
for (int i = 0; i < nonCodeUsages.length; i++) {
if (nonCodeUsages[i] == info) {
nonCodeUsages[i] = info.replaceElement(element);
break;
}
}
}
element.putCopyableUserData(MoveClassToInnerProcessor.ourNonCodeUsageKey, null);
}
}
});
}
}
@Override
public void removeRedundantImports(PsiFile targetClassFile) {
if (targetClassFile instanceof PsiJavaFile) {
JavaCodeStyleManager.getInstance(targetClassFile.getProject()).removeRedundantImports((PsiJavaFile)targetClassFile);
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2012 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 com.intellij.refactoring.move.moveClassesOrPackages;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.util.NonCodeUsageInfo;
import com.intellij.usageView.UsageInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* @author Max Medvedev
*/
public interface MoveClassToInnerHandler {
ExtensionPointName<MoveClassToInnerHandler> EP_NAME = new ExtensionPointName<MoveClassToInnerHandler>("com.intellij.refactoring.moveClassToInnerHandler");
@Nullable
PsiClass moveClass(@NotNull PsiClass aClass, @NotNull PsiClass targetClass);
/**
* filters out import usages from results. Returns all found import usages
*/
List<PsiElement> filterImports(@NotNull List<UsageInfo> usageInfos, @NotNull Project project);
void retargetClassRefsInMoved(@NotNull Map<PsiElement, PsiElement> mapping);
void retargetNonCodeUsages(@NotNull final Map<PsiElement, PsiElement> oldToNewElementMap, @NotNull NonCodeUsageInfo[] myNonCodeUsages);
void removeRedundantImports(PsiFile targetClassFile);
}
@@ -15,7 +15,6 @@
*/
package com.intellij.refactoring.move.moveClassesOrPackages;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -24,8 +23,6 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
@@ -55,6 +52,7 @@ import java.util.*;
*/
public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassToInnerProcessor");
public static final Key<List<NonCodeUsageInfo>> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage");
private PsiClass[] myClassesToMove;
private final PsiClass myTargetClass;
@@ -64,7 +62,6 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
private final boolean mySearchInComments;
private final boolean mySearchInNonJavaFiles;
private NonCodeUsageInfo[] myNonCodeUsages;
private static final Key<List<NonCodeUsageInfo>> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage");
private final MoveCallback myMoveCallback;
public MoveClassToInnerProcessor(Project project,
@@ -103,8 +100,7 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
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));
Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments, mySearchInNonJavaFiles, newName));
}
return usages.toArray(new UsageInfo[usages.size()]);
}
@@ -128,44 +124,48 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
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);
} else {
//rebind imports first
Arrays.sort(usages, new Comparator<UsageInfo>() {
public int compare(UsageInfo o1, UsageInfo o2) {
return PsiUtil.BY_POSITION.compare(o1.getElement(), o2.getElement());
}
});
MoveClassToInnerHandler[] handlers = MoveClassToInnerHandler.EP_NAME.getExtensions();
ArrayList<UsageInfo> usageList = new ArrayList<UsageInfo>(Arrays.asList(usages));
List<PsiElement> importStatements = new ArrayList<PsiElement>();
for (MoveClassToInnerHandler handler : handlers) {
importStatements.addAll(handler.filterImports(usageList, myProject));
}
usages = usageList.toArray(new UsageInfo[usageList.size()]);
saveNonCodeUsages(usages);
final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
try {
for (PsiClass classToMove : myClassesToMove) {
ChangeContextUtil.encodeContextInfo(classToMove, true);
PsiClass newClass = (PsiClass)myTargetClass.addBefore(classToMove, myTargetClass.getRBrace());
if (myTargetClass.isInterface()) {
PsiUtil.setModifierProperty(newClass, PsiModifier.PACKAGE_LOCAL, true);
PsiClass newClass = null;
for (MoveClassToInnerHandler handler : handlers) {
newClass = handler.moveClass(classToMove, myTargetClass);
if (newClass != null) break;
}
else {
PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
}
newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
LOG.assertTrue(newClass != null, "There is no appropriate MoveClassToInnerHandler!");
oldToNewElementsMapping.put(classToMove, newClass);
}
myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping);
retargetNonCodeUsages(oldToNewElementsMapping);
for (MoveClassToInnerHandler handler : handlers) {
handler.retargetNonCodeUsages(oldToNewElementsMapping, myNonCodeUsages);
}
retargetClassRefsInMoved(oldToNewElementsMapping);
for (MoveClassToInnerHandler handler : handlers) {
handler.retargetClassRefsInMoved(oldToNewElementsMapping);
}
for (MoveClassToInnerHandler handler : handlers) {
handler.removeRedundantImports(myTargetClass.getContainingFile());
}
JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)myTargetClass.getContainingFile());
for (PsiClass classToMove : myClassesToMove) {
classToMove.delete();
}
for (PsiElement element: importStatements) {
for (PsiElement element : importStatements) {
if (element.isValid()) {
element.delete();
}
@@ -211,28 +211,6 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
}
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);
}
}
});
}
}
protected void performPsiSpoilingRefactoring() {
if (myNonCodeUsages != null) {
RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
@@ -245,57 +223,6 @@ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
}
}
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);
}
}
}
}
super.visitReferenceElement(reference);
}
});
}
}
private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
if (classToMove == innerClass) {
return newClass;
}
PsiClass parentClass = findMatchingClass(classToMove, newClass, innerClass.getContainingClass());
PsiClass newInnerClass = parentClass.findInnerClassByName(innerClass.getName(), false);
assert newInnerClass != null;
return newInnerClass;
}
private static UsageInfo[] filterUsagesInImportStatements(final UsageInfo[] usages, final List<PsiElement> importStatements) {
List<UsageInfo> remainingUsages = new ArrayList<UsageInfo>();
for(UsageInfo usage: usages) {
PsiElement element = usage.getElement();
if (element == null) continue;
PsiImportStatement stmt = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
if (stmt != null) {
importStatements.add(stmt);
}
else {
remainingUsages.add(usage);
}
}
return remainingUsages.toArray(new UsageInfo[remainingUsages.size()]);
}
protected String getCommandName() {
return RefactoringBundle.message("move.class.to.inner.command.name",
(myClassesToMove.length > 1 ? "classes " : "class ") + StringUtil.join(myClassesToMove, new Function<PsiClass, String>() {
@@ -41,7 +41,10 @@ import com.intellij.refactoring.ui.ClassNameReferenceEditor;
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
import com.intellij.refactoring.ui.RefactoringDialog;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.*;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.RecentsManager;
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
import com.intellij.ui.ReferenceEditorWithBrowseButton;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ui.UIUtil;
@@ -68,7 +71,6 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
private JCheckBox myCbSearchTextOccurences;
private String myHelpID;
private final boolean mySearchTextOccurencesEnabled;
private PsiDirectory myInitialTargetDirectory;
private final PsiManager myManager;
private JPanel myMainPanel;
private JRadioButton myToPackageRadioButton;
@@ -229,7 +231,6 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
boolean searchInComments,
boolean searchForTextOccurences,
String helpID) {
myInitialTargetDirectory = initialTargetDirectory;
myTargetDirectoryFixed = isTargetDirectoryFixed;
if (targetPackageName.length() != 0) {
myWithBrowseButtonReference.prependItem(targetPackageName);
@@ -264,7 +265,7 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
myCbSearchInComments.setSelected(searchInComments);
myCbSearchTextOccurences.setSelected(searchForTextOccurences);
((DestinationFolderComboBox)myDestinationFolderCB).setData(myProject, myInitialTargetDirectory,
((DestinationFolderComboBox)myDestinationFolderCB).setData(myProject, initialTargetDirectory,
new Pass<String>() {
@Override
public void pass(String s) {
@@ -397,9 +398,7 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
//for scala plugin
protected MoveClassesOrPackagesProcessor createMoveToPackageProcessor(MoveDestination destination, final PsiElement[] elementsToMove,
final MoveCallback callback) {
return new MoveClassesOrPackagesProcessor(getProject(), elementsToMove, destination,
isSearchInComments(), isSearchInNonJavaFiles(),
callback);
return new MoveClassesOrPackagesProcessor(getProject(), elementsToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback);
}
private void saveRefactoringSettings() {
@@ -422,12 +421,8 @@ public class MoveClassesOrPackagesDialog extends RefactoringDialog {
}
final Language targetClassLanguage = targetClass.getLanguage();
if (!element.getLanguage().equals(targetClassLanguage)) {
return RefactoringBundle
.message("move.to.different.language", UsageViewUtil.getType(element), ((PsiClass)element).getQualifiedName(),
targetClass.getQualifiedName());
}
if (element.getLanguage().equals(Language.findLanguageByID("Groovy"))) {
return RefactoringBundle.message("dont.support.inner.classes", "Groovy");
return RefactoringBundle.message("move.to.different.language", UsageViewUtil.getType(element),
((PsiClass)element).getQualifiedName(), targetClass.getQualifiedName());
}
}
@@ -17,9 +17,8 @@ package com.intellij.refactoring.safeDelete;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiImportList;
import com.intellij.psi.PsiImportStatement;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.util.PsiTreeUtil;
/**
* @author Max Medvedev
@@ -29,7 +28,10 @@ public class JavaImportSearcher extends ImportSearcher {
public PsiElement findImport(PsiElement element) {
final PsiFile containingFile = element.getContainingFile();
if (containingFile instanceof PsiJavaFile) {
return PsiTreeUtil.getParentOfType(element, PsiImportList.class, true);
PsiElement parent = element.getParent();
if (parent instanceof PsiImportStatement) {
return parent;
}
}
return null;
}