update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / move / moveClassesOrPackages / MoveClassToInnerProcessor.java
blob2ed7bb68b76d1b97a06143c238162699c3738ef4
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 myClassToMove;
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 classToMove,
69 @NotNull final PsiClass targetClass,
70 boolean searchInComments,
71 boolean searchInNonJavaFiles,
72 MoveCallback moveCallback) {
73 super(project);
74 setClassToMove(classToMove);
75 myTargetClass = targetClass;
76 mySearchInComments = searchInComments;
77 mySearchInNonJavaFiles = searchInNonJavaFiles;
78 myMoveCallback = moveCallback;
79 myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory());
82 private void setClassToMove(final PsiClass classToMove) {
83 myClassToMove = classToMove;
84 mySourceVisibility = VisibilityUtil.getVisibilityModifier(myClassToMove.getModifierList());
85 mySourcePackage = JavaDirectoryService.getInstance().getPackage(myClassToMove.getContainingFile().getContainingDirectory());
88 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
89 return new MoveClassesOrPackagesViewDescriptor(new PsiElement[] { myClassToMove },
90 mySearchInComments, mySearchInNonJavaFiles,
91 myTargetClass.getQualifiedName());
94 @NotNull
95 public UsageInfo[] findUsages() {
96 List<UsageInfo> usages = new ArrayList<UsageInfo>();
97 String newName = myTargetClass.getQualifiedName() + "." + myClassToMove.getName();
98 Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(myClassToMove, mySearchInComments,
99 mySearchInNonJavaFiles, newName));
100 for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext();) {
101 UsageInfo usageInfo = iterator.next();
102 if (!(usageInfo instanceof NonCodeUsageInfo) && PsiTreeUtil.isAncestor(myClassToMove, usageInfo.getElement(), false)) {
103 iterator.remove();
106 return usages.toArray(new UsageInfo[usages.size()]);
109 protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
110 return showConflicts(getConflicts(refUsages.get()));
113 protected void refreshElements(final PsiElement[] elements) {
114 assert elements.length == 1;
115 ApplicationManager.getApplication().runReadAction(new Runnable() {
116 public void run() {
117 setClassToMove((PsiClass)elements[0]);
122 protected void performRefactoring(UsageInfo[] usages) {
123 if (!prepareWritable(usages)) return;
125 try {
126 saveNonCodeUsages(usages);
127 ChangeContextUtil.encodeContextInfo(myClassToMove, true);
128 PsiClass newClass = (PsiClass)myTargetClass.addBefore(myClassToMove, myTargetClass.getRBrace());
129 PsiUtil.setModifierProperty(newClass, PsiModifier.STATIC, true);
130 newClass = (PsiClass)ChangeContextUtil.decodeContextInfo(newClass, null, null);
132 retargetClassRefs(myClassToMove, newClass);
134 final List<PsiElement> importStatements = new ArrayList<PsiElement>();
135 if (!CodeStyleSettingsManager.getSettings(myProject).INSERT_INNER_CLASS_IMPORTS) {
136 usages = filterUsagesInImportStatements(usages, importStatements);
139 Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
140 oldToNewElementsMapping.put(myClassToMove, newClass);
141 myNonCodeUsages = MoveClassesOrPackagesProcessor.retargetUsages(usages, oldToNewElementsMapping);
142 retargetNonCodeUsages(newClass);
144 JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile)newClass.getContainingFile());
146 myClassToMove.delete();
147 for(PsiElement element: importStatements) {
148 if (element.isValid()) {
149 element.delete();
153 catch (IncorrectOperationException e) {
154 LOG.error(e);
158 private boolean prepareWritable(final UsageInfo[] usages) {
159 Set<PsiElement> elementsToMakeWritable = new HashSet<PsiElement>();
160 elementsToMakeWritable.add(myClassToMove);
161 elementsToMakeWritable.add(myTargetClass);
162 for(UsageInfo usage: usages) {
163 PsiElement element = usage.getElement();
164 if (element != null) {
165 elementsToMakeWritable.add(element);
168 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, elementsToMakeWritable.toArray(new PsiElement[elementsToMakeWritable.size()]))) {
169 return false;
171 return true;
174 private void saveNonCodeUsages(final UsageInfo[] usages) {
175 for(UsageInfo usageInfo: usages) {
176 if (usageInfo instanceof NonCodeUsageInfo) {
177 final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo;
178 PsiElement element = nonCodeUsage.getElement();
179 if (element != null && PsiTreeUtil.isAncestor(myClassToMove, element, false)) {
180 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
181 if (list == null) {
182 list = new ArrayList<NonCodeUsageInfo>();
183 element.putCopyableUserData(ourNonCodeUsageKey, list);
185 list.add(nonCodeUsage);
191 private void retargetNonCodeUsages(final PsiClass newClass) {
192 newClass.accept(new PsiRecursiveElementVisitor() {
193 @Override public void visitElement(final PsiElement element) {
194 super.visitElement(element);
195 List<NonCodeUsageInfo> list = element.getCopyableUserData(ourNonCodeUsageKey);
196 if (list != null) {
197 for(NonCodeUsageInfo info: list) {
198 for(int i=0; i<myNonCodeUsages.length; i++) {
199 if (myNonCodeUsages [i] == info) {
200 myNonCodeUsages [i] = info.replaceElement(element);
201 break;
205 element.putCopyableUserData(ourNonCodeUsageKey, null);
211 protected void performPsiSpoilingRefactoring() {
212 if (myNonCodeUsages != null) {
213 RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
215 if (myMoveCallback != null) {
216 if (myMoveCallback instanceof MoveClassesOrPackagesCallback) {
217 ((MoveClassesOrPackagesCallback) myMoveCallback).classesMovedToInner(myTargetClass);
219 myMoveCallback.refactoringCompleted();
223 private static void retargetClassRefs(final PsiClass classToMove, final PsiClass newClass) {
224 newClass.accept(new JavaRecursiveElementVisitor() {
225 @Override public void visitReferenceElement(final PsiJavaCodeReferenceElement reference) {
226 PsiElement element = reference.resolve();
227 if (element instanceof PsiClass && PsiTreeUtil.isAncestor(classToMove, element, false)) {
228 PsiClass newInnerClass = findMatchingClass(classToMove, newClass, (PsiClass) element);
229 try {
230 reference.bindToElement(newInnerClass);
232 catch(IncorrectOperationException ex) {
233 LOG.error(ex);
236 else {
237 super.visitReferenceElement(reference);
243 private static PsiClass findMatchingClass(final PsiClass classToMove, final PsiClass newClass, final PsiClass innerClass) {
244 if (classToMove == innerClass) {
245 return newClass;
247 PsiClass parentClass = findMatchingClass(classToMove, newClass, innerClass.getContainingClass());
248 PsiClass newInnerClass = parentClass.findInnerClassByName(innerClass.getName(), false);
249 assert newInnerClass != null;
250 return newInnerClass;
253 private static UsageInfo[] filterUsagesInImportStatements(final UsageInfo[] usages, final List<PsiElement> importStatements) {
254 List<UsageInfo> remainingUsages = new ArrayList<UsageInfo>();
255 for(UsageInfo usage: usages) {
256 PsiElement element = usage.getElement();
257 if (element == null) continue;
258 PsiImportStatement stmt = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
259 if (stmt != null) {
260 importStatements.add(stmt);
262 else {
263 remainingUsages.add(usage);
266 return remainingUsages.toArray(new UsageInfo[remainingUsages.size()]);
269 protected String getCommandName() {
270 return RefactoringBundle.message("move.class.to.inner.command.name",
271 myClassToMove.getQualifiedName(),
272 myTargetClass.getQualifiedName());
275 @NotNull
276 protected Collection<? extends PsiElement> getElementsToWrite(@NotNull final UsageViewDescriptor descriptor) {
277 List<PsiElement> result = new ArrayList<PsiElement>();
278 result.addAll(super.getElementsToWrite(descriptor));
279 result.add(myTargetClass);
280 return result;
283 public MultiMap<PsiElement, String> getConflicts(final UsageInfo[] usages) {
284 MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
286 final PsiClass innerClass = myTargetClass.findInnerClassByName(myClassToMove.getName(), false);
287 if (innerClass != null) {
288 conflicts.putValue(innerClass, RefactoringBundle.message("move.to.inner.duplicate.inner.class",
289 CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()),
290 CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName())));
293 String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(myClassToMove.getModifierList());
294 String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList());
296 boolean moveToOtherPackage = !Comparing.equal(mySourcePackage, myTargetPackage);
297 if (moveToOtherPackage) {
298 PsiElement[] elementsToMove = new PsiElement[] { myClassToMove };
299 myClassToMove.accept(new PackageLocalsUsageCollector(elementsToMove, new PackageWrapper(myTargetPackage), conflicts));
302 ConflictsCollector collector = new ConflictsCollector(conflicts);
303 if ((moveToOtherPackage &&
304 (classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) ||
305 targetClassVisibility.equals(PsiModifier.PRIVATE)) {
306 detectInaccessibleClassUsages(usages, collector);
308 if (moveToOtherPackage) {
309 detectInaccessibleMemberUsages(collector);
312 return conflicts;
315 private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector) {
316 for(UsageInfo usage: usages) {
317 if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) {
318 PsiElement element = usage.getElement();
319 if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue;
320 if (isInaccessibleFromTarget(element, mySourceVisibility)) {
321 collector.addConflict(myClassToMove, element);
327 private boolean isInaccessibleFromTarget(final PsiElement element, final String visibility) {
328 final PsiPackage elementPackage = JavaDirectoryService.getInstance().getPackage(element.getContainingFile().getContainingDirectory());
329 return !PsiUtil.isAccessible(myTargetClass, element, null) ||
330 (visibility.equals(PsiModifier.PACKAGE_LOCAL) && !Comparing.equal(elementPackage, myTargetPackage));
333 private void detectInaccessibleMemberUsages(final ConflictsCollector collector) {
334 PsiElement[] members = collectPackageLocalMembers();
335 for(PsiElement member: members) {
336 ReferencesSearch.search(member).forEach(new Processor<PsiReference>() {
337 public boolean process(final PsiReference psiReference) {
338 PsiElement element = psiReference.getElement();
339 if (isInaccessibleFromTarget(element, PsiModifier.PACKAGE_LOCAL)) {
340 collector.addConflict(psiReference.resolve(), element);
342 return true;
348 private PsiElement[] collectPackageLocalMembers() {
349 return PsiTreeUtil.collectElements(myClassToMove, new PsiElementFilter() {
350 public boolean isAccepted(final PsiElement element) {
351 if (element instanceof PsiMember) {
352 PsiMember member = (PsiMember) element;
353 if (VisibilityUtil.getVisibilityModifier(member.getModifierList()) == PsiModifier.PACKAGE_LOCAL) {
354 return true;
357 return false;
362 private class ConflictsCollector {
363 private final MultiMap<PsiElement, String> myConflicts;
364 private final Set<PsiElement> myReportedContainers = new HashSet<PsiElement>();
366 public ConflictsCollector(final MultiMap<PsiElement, String> conflicts) {
367 myConflicts = conflicts;
370 public void addConflict(final PsiElement targetElement, final PsiElement sourceElement) {
371 PsiElement container = ConflictsUtil.getContainer(sourceElement);
372 if (container == null) return;
373 if (!myReportedContainers.contains(container)) {
374 myReportedContainers.add(container);
375 String targetDescription = (targetElement == myClassToMove)
376 ? "Class " + CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName())
377 : StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true));
378 final String message = RefactoringBundle.message("element.will.no.longer.be.accessible",
379 targetDescription,
380 RefactoringUIUtil.getDescription(container, true));
381 myConflicts.putValue(targetElement, message);