move to inner: rebind imports first in order to prevent fqns (IDEA-39290)
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / move / moveClassesOrPackages / MoveClassToInnerProcessor.java
blobeb7d8e2779ad3bfeaa35976c9ccec926cf80a791
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.refactoring.move.moveClassesOrPackages;
18 import com.intellij.codeInsight.ChangeContextUtil;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.Key;
24 import com.intellij.openapi.util.Ref;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.psi.*;
27 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
28 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
29 import com.intellij.psi.search.searches.ReferencesSearch;
30 import com.intellij.psi.util.PsiElementFilter;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import com.intellij.refactoring.BaseRefactoringProcessor;
34 import com.intellij.refactoring.PackageWrapper;
35 import com.intellij.refactoring.RefactoringBundle;
36 import com.intellij.refactoring.move.MoveCallback;
37 import com.intellij.refactoring.move.MoveClassesOrPackagesCallback;
38 import com.intellij.refactoring.rename.RenameUtil;
39 import com.intellij.refactoring.util.*;
40 import com.intellij.usageView.UsageInfo;
41 import com.intellij.usageView.UsageViewDescriptor;
42 import com.intellij.util.IncorrectOperationException;
43 import com.intellij.util.Processor;
44 import com.intellij.util.VisibilityUtil;
45 import com.intellij.util.containers.MultiMap;
46 import org.jetbrains.annotations.NotNull;
48 import java.util.*;
50 /**
51 * @author yole
53 public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
54 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassToInnerProcessor");
56 private PsiClass[] myClassesToMove;
57 private final PsiClass myTargetClass;
58 private PsiPackage[] mySourcePackage;
59 private final PsiPackage myTargetPackage;
60 private String[] mySourceVisibility;
61 private final boolean mySearchInComments;
62 private final boolean mySearchInNonJavaFiles;
63 private NonCodeUsageInfo[] myNonCodeUsages;
64 private static final Key<List<NonCodeUsageInfo>> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage");
65 private final MoveCallback myMoveCallback;
67 public MoveClassToInnerProcessor(Project project,
68 final PsiClass[] classesToMove,
69 @NotNull final PsiClass targetClass,
70 boolean searchInComments,
71 boolean searchInNonJavaFiles,
72 MoveCallback moveCallback) {
73 super(project);
74 setClassesToMove(classesToMove);
75 myTargetClass = targetClass;
76 mySearchInComments = searchInComments;
77 mySearchInNonJavaFiles = searchInNonJavaFiles;
78 myMoveCallback = moveCallback;
79 myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory());
82 private void setClassesToMove(final PsiClass[] classesToMove) {
83 myClassesToMove = classesToMove;
84 mySourcePackage = new PsiPackage[classesToMove.length];
85 mySourceVisibility = new String[classesToMove.length];
86 for (int i = 0; i < classesToMove.length; i++) {
87 PsiClass psiClass = classesToMove[i];
88 mySourceVisibility[i] = VisibilityUtil.getVisibilityModifier(psiClass.getModifierList());
89 mySourcePackage[i] = JavaDirectoryService.getInstance().getPackage(psiClass.getContainingFile().getContainingDirectory());
93 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
94 return new MoveClassesOrPackagesViewDescriptor(myClassesToMove,
95 mySearchInComments, mySearchInNonJavaFiles,
96 myTargetClass.getQualifiedName());
99 @NotNull
100 public UsageInfo[] findUsages() {
101 final List<UsageInfo> usages = new ArrayList<UsageInfo>();
102 for (PsiClass classToMove : myClassesToMove) {
103 final String newName = myTargetClass.getQualifiedName() + "." + classToMove.getName();
104 Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments,
105 mySearchInNonJavaFiles, newName));
107 return usages.toArray(new UsageInfo[usages.size()]);
110 protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
111 return showConflicts(getConflicts(refUsages.get()));
114 protected void refreshElements(final PsiElement[] elements) {
115 ApplicationManager.getApplication().runReadAction(new Runnable() {
116 public void run() {
117 final PsiClass[] classesToMove = new PsiClass[elements.length];
118 for (int i = 0; i < classesToMove.length; i++) {
119 classesToMove[i] = (PsiClass)elements[i];
121 setClassesToMove(classesToMove);
126 protected void performRefactoring(UsageInfo[] usages) {
127 if (!prepareWritable(usages)) return;
128 final List<PsiElement> importStatements = new ArrayList<PsiElement>();
129 if (!CodeStyleSettingsManager.getSettings(myProject).INSERT_INNER_CLASS_IMPORTS) {
130 usages = filterUsagesInImportStatements(usages, importStatements);
131 } else {
132 //rebind imports first
133 Arrays.sort(usages, new Comparator<UsageInfo>() {
134 public int compare(UsageInfo o1, UsageInfo o2) {
135 return PsiUtil.BY_POSITION.compare(o1.getElement(), o2.getElement());
139 saveNonCodeUsages(usages);
140 final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
141 try {
142 for (PsiClass classToMove : myClassesToMove) {
143 ChangeContextUtil.encodeContextInfo(classToMove, true);
144 PsiClass newClass = (PsiClass)myTargetClass.addBefore(classToMove, myTargetClass.getRBrace());
145 PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
146 newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
147 oldToNewElementsMapping.put(classToMove, newClass);
150 myNonCodeUsages = MoveClassesOrPackagesProcessor.retargetUsages(usages, oldToNewElementsMapping);
151 retargetNonCodeUsages(oldToNewElementsMapping);
153 retargetClassRefsInMoved(oldToNewElementsMapping);
155 JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)myTargetClass.getContainingFile());
156 for (PsiClass classToMove : myClassesToMove) {
157 classToMove.delete();
160 for (PsiElement element: importStatements) {
161 if (element.isValid()) {
162 element.delete();
166 catch (IncorrectOperationException e) {
167 LOG.error(e);
171 private boolean prepareWritable(final UsageInfo[] usages) {
172 Set<PsiElement> elementsToMakeWritable = new HashSet<PsiElement>();
173 Collections.addAll(elementsToMakeWritable, myClassesToMove);
174 elementsToMakeWritable.add(myTargetClass);
175 for(UsageInfo usage: usages) {
176 PsiElement element = usage.getElement();
177 if (element != null) {
178 elementsToMakeWritable.add(element);
181 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, elementsToMakeWritable.toArray(new PsiElement[elementsToMakeWritable.size()]))) {
182 return false;
184 return true;
187 private void saveNonCodeUsages(final UsageInfo[] usages) {
188 for (PsiClass classToMove : myClassesToMove) {
189 for(UsageInfo usageInfo: usages) {
190 if (usageInfo instanceof NonCodeUsageInfo) {
191 final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo;
192 PsiElement element = nonCodeUsage.getElement();
193 if (element != null && PsiTreeUtil.isAncestor(classToMove, element, false)) {
194 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
195 if (list == null) {
196 list = new ArrayList<NonCodeUsageInfo>();
197 element.putCopyableUserData(ourNonCodeUsageKey, list);
199 list.add(nonCodeUsage);
206 private void retargetNonCodeUsages(final Map<PsiElement, PsiElement> oldToNewElementMap) {
207 for (PsiElement newClass : oldToNewElementMap.values()) {
208 newClass.accept(new PsiRecursiveElementVisitor() {
209 @Override public void visitElement(final PsiElement element) {
210 super.visitElement(element);
211 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
212 if (list != null) {
213 for(NonCodeUsageInfo info: list) {
214 for(int i=0; i<myNonCodeUsages.length; i++) {
215 if (myNonCodeUsages [i] == info) {
216 myNonCodeUsages [i] = info.replaceElement(element);
217 break;
221 element.putCopyableUserData(ourNonCodeUsageKey, null);
228 protected void performPsiSpoilingRefactoring() {
229 if (myNonCodeUsages != null) {
230 RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
232 if (myMoveCallback != null) {
233 if (myMoveCallback instanceof MoveClassesOrPackagesCallback) {
234 ((MoveClassesOrPackagesCallback) myMoveCallback).classesMovedToInner(myTargetClass);
236 myMoveCallback.refactoringCompleted();
240 private static void retargetClassRefsInMoved(final Map<PsiElement, PsiElement> oldToNewElementsMapping) {
241 for (final PsiElement newClass : oldToNewElementsMapping.values()) {
242 newClass.accept(new JavaRecursiveElementVisitor() {
243 @Override public void visitReferenceElement(final PsiJavaCodeReferenceElement reference) {
244 PsiElement element = reference.resolve();
245 if (element instanceof PsiClass) {
246 for (PsiElement oldClass : oldToNewElementsMapping.keySet()) {
247 if (PsiTreeUtil.isAncestor(oldClass, element, false)) {
248 PsiClass newInnerClass = findMatchingClass((PsiClass)oldClass, (PsiClass)oldToNewElementsMapping.get(oldClass), (PsiClass)element);
249 try {
250 reference.bindToElement(newInnerClass);
251 return;
253 catch (IncorrectOperationException ex) {
254 LOG.error(ex);
259 super.visitReferenceElement(reference);
265 private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
266 if (classToMove == innerClass) {
267 return newClass;
269 PsiClass parentClass = findMatchingClass(classToMove, newClass, innerClass.getContainingClass());
270 PsiClass newInnerClass = parentClass.findInnerClassByName(innerClass.getName(), false);
271 assert newInnerClass != null;
272 return newInnerClass;
275 private static UsageInfo[] filterUsagesInImportStatements(final UsageInfo[] usages, final List<PsiElement> importStatements) {
276 List<UsageInfo> remainingUsages = new ArrayList<UsageInfo>();
277 for(UsageInfo usage: usages) {
278 PsiElement element = usage.getElement();
279 if (element == null) continue;
280 PsiImportStatement stmt = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
281 if (stmt != null) {
282 importStatements.add(stmt);
284 else {
285 remainingUsages.add(usage);
288 return remainingUsages.toArray(new UsageInfo[remainingUsages.size()]);
291 protected String getCommandName() {
292 return RefactoringBundle.message("move.class.to.inner.command.name",
293 myClassesToMove[0].getQualifiedName(),
294 myTargetClass.getQualifiedName());
297 @NotNull
298 protected Collection<? extends PsiElement> getElementsToWrite(@NotNull final UsageViewDescriptor descriptor) {
299 List<PsiElement> result = new ArrayList<PsiElement>();
300 result.addAll(super.getElementsToWrite(descriptor));
301 result.add(myTargetClass);
302 return result;
305 public MultiMap<PsiElement, String> getConflicts(final UsageInfo[] usages) {
306 MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
308 for (PsiClass classToMove : myClassesToMove) {
309 final PsiClass innerClass = myTargetClass.findInnerClassByName(classToMove.getName(), false);
310 if (innerClass != null) {
311 conflicts.putValue(innerClass, RefactoringBundle.message("move.to.inner.duplicate.inner.class",
312 CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()),
313 CommonRefactoringUtil.htmlEmphasize(classToMove.getName())));
317 for (int i = 0; i < myClassesToMove.length; i++) {
318 PsiClass classToMove = myClassesToMove[i];
319 String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(classToMove.getModifierList());
320 String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList());
322 boolean moveToOtherPackage = !Comparing.equal(mySourcePackage[i], myTargetPackage);
323 if (moveToOtherPackage) {
324 classToMove.accept(new PackageLocalsUsageCollector(myClassesToMove, new PackageWrapper(myTargetPackage), conflicts));
327 ConflictsCollector collector = new ConflictsCollector(classToMove, conflicts);
328 if ((moveToOtherPackage &&
329 (classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) ||
330 targetClassVisibility.equals(PsiModifier.PRIVATE)) {
331 detectInaccessibleClassUsages(usages, collector, mySourceVisibility[i]);
333 if (moveToOtherPackage) {
334 detectInaccessibleMemberUsages(collector);
338 return conflicts;
341 private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector, final String visibility) {
342 for(UsageInfo usage: usages) {
343 if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) {
344 PsiElement element = usage.getElement();
345 if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue;
346 if (isInaccessibleFromTarget(element, visibility)) {
347 collector.addConflict(collector.getClassToMove(), element);
353 private boolean isInaccessibleFromTarget(final PsiElement element, final String visibility) {
354 final PsiPackage elementPackage = JavaDirectoryService.getInstance().getPackage(element.getContainingFile().getContainingDirectory());
355 return !PsiUtil.isAccessible(myTargetClass, element, null) ||
356 (visibility.equals(PsiModifier.PACKAGE_LOCAL) && !Comparing.equal(elementPackage, myTargetPackage));
359 private void detectInaccessibleMemberUsages(final ConflictsCollector collector) {
360 PsiElement[] members = collectPackageLocalMembers(collector.getClassToMove());
361 for(PsiElement member: members) {
362 ReferencesSearch.search(member).forEach(new Processor<PsiReference>() {
363 public boolean process(final PsiReference psiReference) {
364 PsiElement element = psiReference.getElement();
365 if (isInaccessibleFromTarget(element, PsiModifier.PACKAGE_LOCAL)) {
366 collector.addConflict(psiReference.resolve(), element);
368 return true;
374 private static PsiElement[] collectPackageLocalMembers(PsiElement classToMove) {
375 return PsiTreeUtil.collectElements(classToMove, new PsiElementFilter() {
376 public boolean isAccepted(final PsiElement element) {
377 if (element instanceof PsiMember) {
378 PsiMember member = (PsiMember) element;
379 if (VisibilityUtil.getVisibilityModifier(member.getModifierList()) == PsiModifier.PACKAGE_LOCAL) {
380 return true;
383 return false;
388 private static class ConflictsCollector {
389 private final PsiClass myClassToMove;
390 private final MultiMap<PsiElement, String> myConflicts;
391 private final Set<PsiElement> myReportedContainers = new HashSet<PsiElement>();
393 public ConflictsCollector(PsiClass classToMove, final MultiMap<PsiElement, String> conflicts) {
394 myClassToMove = classToMove;
395 myConflicts = conflicts;
398 public void addConflict(final PsiElement targetElement, final PsiElement sourceElement) {
399 PsiElement container = ConflictsUtil.getContainer(sourceElement);
400 if (!myReportedContainers.contains(container)) {
401 myReportedContainers.add(container);
402 String targetDescription = (targetElement == myClassToMove)
403 ? "Class " + CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName())
404 : StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true));
405 final String message = RefactoringBundle.message("element.will.no.longer.be.accessible",
406 targetDescription,
407 RefactoringUIUtil.getDescription(container, true));
408 myConflicts.putValue(targetElement, message);
412 public PsiElement getClassToMove() {
413 return myClassToMove;