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
[] 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
) {
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());
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() {
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
);
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
>();
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()) {
166 catch (IncorrectOperationException 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()]))) {
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
);
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
);
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
);
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
);
250 reference
.bindToElement(newInnerClass
);
253 catch (IncorrectOperationException ex
) {
259 super.visitReferenceElement(reference
);
265 private static PsiClass
findMatchingClass(final PsiClass classToMove
, final PsiClass newClass
, final PsiClass innerClass
) {
266 if (classToMove
== innerClass
) {
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);
282 importStatements
.add(stmt
);
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());
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
);
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
);
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
);
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
) {
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",
407 RefactoringUIUtil
.getDescription(container
, true));
408 myConflicts
.putValue(targetElement
, message
);
412 public PsiElement
getClassToMove() {
413 return myClassToMove
;