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
;
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
) {
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());
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() {
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
);
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
>();
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);
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()) {
172 catch (IncorrectOperationException 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()]))) {
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
);
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
);
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
);
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
);
256 reference
.bindToElement(newInnerClass
);
259 catch (IncorrectOperationException ex
) {
265 super.visitReferenceElement(reference
);
271 private static PsiClass
findMatchingClass(final PsiClass classToMove
, final PsiClass newClass
, final PsiClass innerClass
) {
272 if (classToMove
== innerClass
) {
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);
288 importStatements
.add(stmt
);
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();
304 myTargetClass
.getQualifiedName());
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
);
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
);
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
);
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
) {
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",
417 RefactoringUIUtil
.getDescription(container
, true));
418 myConflicts
.putValue(targetElement
, message
);
422 public PsiElement
getClassToMove() {
423 return myClassToMove
;