move to inner: move to interface
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / move / moveClassesOrPackages / MoveClassToInnerProcessor.java
blobd6728cb2224c9c61efd0d5a31fdd4994f5e00538
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.Function;
43 import com.intellij.util.IncorrectOperationException;
44 import com.intellij.util.Processor;
45 import com.intellij.util.VisibilityUtil;
46 import com.intellij.util.containers.MultiMap;
47 import org.jetbrains.annotations.NotNull;
49 import java.util.*;
51 /**
52 * @author yole
54 public class MoveClassToInnerProcessor extends BaseRefactoringProcessor {
55 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassToInnerProcessor");
57 private PsiClass[] myClassesToMove;
58 private final PsiClass myTargetClass;
59 private PsiPackage[] mySourcePackage;
60 private final PsiPackage myTargetPackage;
61 private String[] mySourceVisibility;
62 private final boolean mySearchInComments;
63 private final boolean mySearchInNonJavaFiles;
64 private NonCodeUsageInfo[] myNonCodeUsages;
65 private static final Key<List<NonCodeUsageInfo>> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage");
66 private final MoveCallback myMoveCallback;
68 public MoveClassToInnerProcessor(Project project,
69 final PsiClass[] classesToMove,
70 @NotNull final PsiClass targetClass,
71 boolean searchInComments,
72 boolean searchInNonJavaFiles,
73 MoveCallback moveCallback) {
74 super(project);
75 setClassesToMove(classesToMove);
76 myTargetClass = targetClass;
77 mySearchInComments = searchInComments;
78 mySearchInNonJavaFiles = searchInNonJavaFiles;
79 myMoveCallback = moveCallback;
80 myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory());
83 private void setClassesToMove(final PsiClass[] classesToMove) {
84 myClassesToMove = classesToMove;
85 mySourcePackage = new PsiPackage[classesToMove.length];
86 mySourceVisibility = new String[classesToMove.length];
87 for (int i = 0; i < classesToMove.length; i++) {
88 PsiClass psiClass = classesToMove[i];
89 mySourceVisibility[i] = VisibilityUtil.getVisibilityModifier(psiClass.getModifierList());
90 mySourcePackage[i] = JavaDirectoryService.getInstance().getPackage(psiClass.getContainingFile().getContainingDirectory());
94 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
95 return new MoveClassesOrPackagesViewDescriptor(myClassesToMove,
96 mySearchInComments, mySearchInNonJavaFiles,
97 myTargetClass.getQualifiedName());
100 @NotNull
101 public UsageInfo[] findUsages() {
102 final List<UsageInfo> usages = new ArrayList<UsageInfo>();
103 for (PsiClass classToMove : myClassesToMove) {
104 final String newName = myTargetClass.getQualifiedName() + "." + classToMove.getName();
105 Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments,
106 mySearchInNonJavaFiles, newName));
108 return usages.toArray(new UsageInfo[usages.size()]);
111 protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
112 return showConflicts(getConflicts(refUsages.get()));
115 protected void refreshElements(final PsiElement[] elements) {
116 ApplicationManager.getApplication().runReadAction(new Runnable() {
117 public void run() {
118 final PsiClass[] classesToMove = new PsiClass[elements.length];
119 for (int i = 0; i < classesToMove.length; i++) {
120 classesToMove[i] = (PsiClass)elements[i];
122 setClassesToMove(classesToMove);
127 protected void performRefactoring(UsageInfo[] usages) {
128 if (!prepareWritable(usages)) return;
129 final List<PsiElement> importStatements = new ArrayList<PsiElement>();
130 if (!CodeStyleSettingsManager.getSettings(myProject).INSERT_INNER_CLASS_IMPORTS) {
131 usages = filterUsagesInImportStatements(usages, importStatements);
132 } else {
133 //rebind imports first
134 Arrays.sort(usages, new Comparator<UsageInfo>() {
135 public int compare(UsageInfo o1, UsageInfo o2) {
136 return PsiUtil.BY_POSITION.compare(o1.getElement(), o2.getElement());
140 saveNonCodeUsages(usages);
141 final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
142 try {
143 for (PsiClass classToMove : myClassesToMove) {
144 ChangeContextUtil.encodeContextInfo(classToMove, true);
145 PsiClass newClass = (PsiClass)myTargetClass.addBefore(classToMove, myTargetClass.getRBrace());
146 if (myTargetClass.isInterface()) {
147 PsiUtil.setModifierProperty(newClass, PsiModifier.PACKAGE_LOCAL, true);
149 else {
150 PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
152 newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
153 oldToNewElementsMapping.put(classToMove, newClass);
156 myNonCodeUsages = MoveClassesOrPackagesProcessor.retargetUsages(usages, oldToNewElementsMapping);
157 retargetNonCodeUsages(oldToNewElementsMapping);
159 retargetClassRefsInMoved(oldToNewElementsMapping);
161 JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)myTargetClass.getContainingFile());
162 for (PsiClass classToMove : myClassesToMove) {
163 classToMove.delete();
166 for (PsiElement element: importStatements) {
167 if (element.isValid()) {
168 element.delete();
172 catch (IncorrectOperationException e) {
173 LOG.error(e);
177 private boolean prepareWritable(final UsageInfo[] usages) {
178 Set<PsiElement> elementsToMakeWritable = new HashSet<PsiElement>();
179 Collections.addAll(elementsToMakeWritable, myClassesToMove);
180 elementsToMakeWritable.add(myTargetClass);
181 for(UsageInfo usage: usages) {
182 PsiElement element = usage.getElement();
183 if (element != null) {
184 elementsToMakeWritable.add(element);
187 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, elementsToMakeWritable.toArray(new PsiElement[elementsToMakeWritable.size()]))) {
188 return false;
190 return true;
193 private void saveNonCodeUsages(final UsageInfo[] usages) {
194 for (PsiClass classToMove : myClassesToMove) {
195 for(UsageInfo usageInfo: usages) {
196 if (usageInfo instanceof NonCodeUsageInfo) {
197 final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo;
198 PsiElement element = nonCodeUsage.getElement();
199 if (element != null && PsiTreeUtil.isAncestor(classToMove, element, false)) {
200 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
201 if (list == null) {
202 list = new ArrayList<NonCodeUsageInfo>();
203 element.putCopyableUserData(ourNonCodeUsageKey, list);
205 list.add(nonCodeUsage);
212 private void retargetNonCodeUsages(final Map<PsiElement, PsiElement> oldToNewElementMap) {
213 for (PsiElement newClass : oldToNewElementMap.values()) {
214 newClass.accept(new PsiRecursiveElementVisitor() {
215 @Override public void visitElement(final PsiElement element) {
216 super.visitElement(element);
217 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
218 if (list != null) {
219 for(NonCodeUsageInfo info: list) {
220 for(int i=0; i<myNonCodeUsages.length; i++) {
221 if (myNonCodeUsages [i] == info) {
222 myNonCodeUsages [i] = info.replaceElement(element);
223 break;
227 element.putCopyableUserData(ourNonCodeUsageKey, null);
234 protected void performPsiSpoilingRefactoring() {
235 if (myNonCodeUsages != null) {
236 RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
238 if (myMoveCallback != null) {
239 if (myMoveCallback instanceof MoveClassesOrPackagesCallback) {
240 ((MoveClassesOrPackagesCallback) myMoveCallback).classesMovedToInner(myTargetClass);
242 myMoveCallback.refactoringCompleted();
246 private static void retargetClassRefsInMoved(final Map<PsiElement, PsiElement> oldToNewElementsMapping) {
247 for (final PsiElement newClass : oldToNewElementsMapping.values()) {
248 newClass.accept(new JavaRecursiveElementVisitor() {
249 @Override public void visitReferenceElement(final PsiJavaCodeReferenceElement reference) {
250 PsiElement element = reference.resolve();
251 if (element instanceof PsiClass) {
252 for (PsiElement oldClass : oldToNewElementsMapping.keySet()) {
253 if (PsiTreeUtil.isAncestor(oldClass, element, false)) {
254 PsiClass newInnerClass = findMatchingClass((PsiClass)oldClass, (PsiClass)oldToNewElementsMapping.get(oldClass), (PsiClass)element);
255 try {
256 reference.bindToElement(newInnerClass);
257 return;
259 catch (IncorrectOperationException ex) {
260 LOG.error(ex);
265 super.visitReferenceElement(reference);
271 private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
272 if (classToMove == innerClass) {
273 return newClass;
275 PsiClass parentClass = findMatchingClass(classToMove, newClass, innerClass.getContainingClass());
276 PsiClass newInnerClass = parentClass.findInnerClassByName(innerClass.getName(), false);
277 assert newInnerClass != null;
278 return newInnerClass;
281 private static UsageInfo[] filterUsagesInImportStatements(final UsageInfo[] usages, final List<PsiElement> importStatements) {
282 List<UsageInfo> remainingUsages = new ArrayList<UsageInfo>();
283 for(UsageInfo usage: usages) {
284 PsiElement element = usage.getElement();
285 if (element == null) continue;
286 PsiImportStatement stmt = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
287 if (stmt != null) {
288 importStatements.add(stmt);
290 else {
291 remainingUsages.add(usage);
294 return remainingUsages.toArray(new UsageInfo[remainingUsages.size()]);
297 protected String getCommandName() {
298 return RefactoringBundle.message("move.class.to.inner.command.name",
299 (myClassesToMove.length > 1 ? "classes " : "class ") + StringUtil.join(myClassesToMove, new Function<PsiClass, String>() {
300 public String fun(PsiClass psiClass) {
301 return psiClass.getName();
303 }, ", "),
304 myTargetClass.getQualifiedName());
307 @NotNull
308 protected Collection<? extends PsiElement> getElementsToWrite(@NotNull final UsageViewDescriptor descriptor) {
309 List<PsiElement> result = new ArrayList<PsiElement>();
310 result.addAll(super.getElementsToWrite(descriptor));
311 result.add(myTargetClass);
312 return result;
315 public MultiMap<PsiElement, String> getConflicts(final UsageInfo[] usages) {
316 MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
318 for (PsiClass classToMove : myClassesToMove) {
319 final PsiClass innerClass = myTargetClass.findInnerClassByName(classToMove.getName(), false);
320 if (innerClass != null) {
321 conflicts.putValue(innerClass, RefactoringBundle.message("move.to.inner.duplicate.inner.class",
322 CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()),
323 CommonRefactoringUtil.htmlEmphasize(classToMove.getName())));
327 for (int i = 0; i < myClassesToMove.length; i++) {
328 PsiClass classToMove = myClassesToMove[i];
329 String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(classToMove.getModifierList());
330 String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList());
332 boolean moveToOtherPackage = !Comparing.equal(mySourcePackage[i], myTargetPackage);
333 if (moveToOtherPackage) {
334 classToMove.accept(new PackageLocalsUsageCollector(myClassesToMove, new PackageWrapper(myTargetPackage), conflicts));
337 ConflictsCollector collector = new ConflictsCollector(classToMove, conflicts);
338 if ((moveToOtherPackage &&
339 (classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) ||
340 targetClassVisibility.equals(PsiModifier.PRIVATE)) {
341 detectInaccessibleClassUsages(usages, collector, mySourceVisibility[i]);
343 if (moveToOtherPackage) {
344 detectInaccessibleMemberUsages(collector);
348 return conflicts;
351 private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector, final String visibility) {
352 for(UsageInfo usage: usages) {
353 if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) {
354 PsiElement element = usage.getElement();
355 if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue;
356 if (isInaccessibleFromTarget(element, visibility)) {
357 collector.addConflict(collector.getClassToMove(), element);
363 private boolean isInaccessibleFromTarget(final PsiElement element, final String visibility) {
364 final PsiPackage elementPackage = JavaDirectoryService.getInstance().getPackage(element.getContainingFile().getContainingDirectory());
365 return !PsiUtil.isAccessible(myTargetClass, element, null) ||
366 (!myTargetClass.isInterface() && visibility.equals(PsiModifier.PACKAGE_LOCAL) && !Comparing.equal(elementPackage, myTargetPackage));
369 private void detectInaccessibleMemberUsages(final ConflictsCollector collector) {
370 PsiElement[] members = collectPackageLocalMembers(collector.getClassToMove());
371 for(PsiElement member: members) {
372 ReferencesSearch.search(member).forEach(new Processor<PsiReference>() {
373 public boolean process(final PsiReference psiReference) {
374 PsiElement element = psiReference.getElement();
375 if (isInaccessibleFromTarget(element, PsiModifier.PACKAGE_LOCAL)) {
376 collector.addConflict(psiReference.resolve(), element);
378 return true;
384 private static PsiElement[] collectPackageLocalMembers(PsiElement classToMove) {
385 return PsiTreeUtil.collectElements(classToMove, new PsiElementFilter() {
386 public boolean isAccepted(final PsiElement element) {
387 if (element instanceof PsiMember) {
388 PsiMember member = (PsiMember) element;
389 if (VisibilityUtil.getVisibilityModifier(member.getModifierList()) == PsiModifier.PACKAGE_LOCAL) {
390 return true;
393 return false;
398 private static class ConflictsCollector {
399 private final PsiClass myClassToMove;
400 private final MultiMap<PsiElement, String> myConflicts;
401 private final Set<PsiElement> myReportedContainers = new HashSet<PsiElement>();
403 public ConflictsCollector(PsiClass classToMove, final MultiMap<PsiElement, String> conflicts) {
404 myClassToMove = classToMove;
405 myConflicts = conflicts;
408 public void addConflict(final PsiElement targetElement, final PsiElement sourceElement) {
409 PsiElement container = ConflictsUtil.getContainer(sourceElement);
410 if (!myReportedContainers.contains(container)) {
411 myReportedContainers.add(container);
412 String targetDescription = (targetElement == myClassToMove)
413 ? "Class " + CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName())
414 : StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true));
415 final String message = RefactoringBundle.message("element.will.no.longer.be.accessible",
416 targetDescription,
417 RefactoringUIUtil.getDescription(container, true));
418 myConflicts.putValue(targetElement, message);
422 public PsiElement getClassToMove() {
423 return myClassToMove;