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
;
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
) {
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());
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)) {
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() {
117 setClassToMove((PsiClass
)elements
[0]);
122 protected void performRefactoring(UsageInfo
[] usages
) {
123 if (!prepareWritable(usages
)) return;
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()) {
153 catch (IncorrectOperationException 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()]))) {
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
);
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
);
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
);
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
);
230 reference
.bindToElement(newInnerClass
);
232 catch(IncorrectOperationException ex
) {
237 super.visitReferenceElement(reference
);
243 private static PsiClass
findMatchingClass(final PsiClass classToMove
, final PsiClass newClass
, final PsiClass innerClass
) {
244 if (classToMove
== innerClass
) {
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);
260 importStatements
.add(stmt
);
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());
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
);
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
);
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
);
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
) {
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",
380 RefactoringUIUtil
.getDescription(container
, true));
381 myConflicts
.putValue(targetElement
, message
);