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
.inline
;
18 import com
.intellij
.openapi
.diagnostic
.Logger
;
19 import com
.intellij
.openapi
.project
.Project
;
20 import com
.intellij
.openapi
.util
.Ref
;
21 import com
.intellij
.openapi
.wm
.WindowManager
;
22 import com
.intellij
.patterns
.ElementPattern
;
23 import com
.intellij
.patterns
.PlatformPatterns
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.psi
.search
.GlobalSearchScope
;
26 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
27 import com
.intellij
.psi
.util
.PsiTreeUtil
;
28 import com
.intellij
.psi
.util
.PsiUtil
;
29 import com
.intellij
.refactoring
.BaseRefactoringProcessor
;
30 import com
.intellij
.refactoring
.RefactoringBundle
;
31 import com
.intellij
.refactoring
.rename
.NonCodeUsageInfoFactory
;
32 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
33 import com
.intellij
.refactoring
.util
.TextOccurrencesUtil
;
34 import com
.intellij
.usageView
.UsageInfo
;
35 import com
.intellij
.usageView
.UsageViewDescriptor
;
36 import com
.intellij
.util
.IncorrectOperationException
;
37 import com
.intellij
.util
.containers
.MultiMap
;
38 import org
.jetbrains
.annotations
.NotNull
;
39 import org
.jetbrains
.annotations
.Nullable
;
46 public class InlineToAnonymousClassProcessor
extends BaseRefactoringProcessor
{
47 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.inline.InlineToAnonymousClassProcessor");
49 private PsiClass myClass
;
50 private final PsiCall myCallToInline
;
51 private final boolean myInlineThisOnly
;
52 private final boolean mySearchInComments
;
53 private final boolean mySearchInNonJavaFiles
;
55 private final ElementPattern ourCatchClausePattern
= PlatformPatterns
.psiElement(PsiTypeElement
.class).withParent(PlatformPatterns
.psiElement(PsiParameter
.class).withParent(
56 PlatformPatterns
.psiElement(PsiCatchSection
.class)));
57 private final ElementPattern ourThrowsClausePattern
= PlatformPatterns
.psiElement().withParent(PlatformPatterns
.psiElement(PsiReferenceList
.class).withFirstChild(
58 PlatformPatterns
.psiElement().withText(PsiKeyword
.THROWS
)));
60 protected InlineToAnonymousClassProcessor(Project project
,
62 @Nullable final PsiCall callToInline
,
63 boolean inlineThisOnly
,
64 final boolean searchInComments
,
65 final boolean searchInNonJavaFiles
) {
68 myCallToInline
= callToInline
;
69 myInlineThisOnly
= inlineThisOnly
;
70 if (myInlineThisOnly
) assert myCallToInline
!= null;
71 mySearchInComments
= searchInComments
;
72 mySearchInNonJavaFiles
= searchInNonJavaFiles
;
75 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
) {
76 return new InlineViewDescriptor(myClass
);
80 protected UsageInfo
[] findUsages() {
81 if (myInlineThisOnly
) {
82 return new UsageInfo
[] { new UsageInfo(myCallToInline
) };
84 Set
<UsageInfo
> usages
= new HashSet
<UsageInfo
>();
85 for (PsiReference reference
: ReferencesSearch
.search(myClass
)) {
86 usages
.add(new UsageInfo(reference
.getElement()));
89 final String qName
= myClass
.getQualifiedName();
91 List
<UsageInfo
> nonCodeUsages
= new ArrayList
<UsageInfo
>();
92 if (mySearchInComments
) {
93 TextOccurrencesUtil
.addUsagesInStringsAndComments(myClass
, qName
, nonCodeUsages
,
94 new NonCodeUsageInfoFactory(myClass
, qName
));
97 if (mySearchInNonJavaFiles
) {
98 GlobalSearchScope projectScope
= GlobalSearchScope
.projectScope(myClass
.getProject());
99 TextOccurrencesUtil
.addTextOccurences(myClass
, qName
, projectScope
, nonCodeUsages
,
100 new NonCodeUsageInfoFactory(myClass
, qName
));
102 usages
.addAll(nonCodeUsages
);
105 return usages
.toArray(new UsageInfo
[usages
.size()]);
108 protected void refreshElements(PsiElement
[] elements
) {
109 assert elements
.length
== 1;
110 myClass
= (PsiClass
) elements
[0];
113 protected boolean isPreviewUsages(UsageInfo
[] usages
) {
114 if (super.isPreviewUsages(usages
)) return true;
115 for(UsageInfo usage
: usages
) {
116 if (isForcePreview(usage
)) {
117 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("occurrences.found.in.comments.strings.and.non.java.files"));
124 private static boolean isForcePreview(final UsageInfo usage
) {
125 if (usage
.isNonCodeUsage
) return true;
126 PsiElement element
= usage
.getElement();
127 if (element
!= null) {
128 PsiFile file
= element
.getContainingFile();
129 if (!(file
instanceof PsiJavaFile
)) {
136 protected boolean preprocessUsages(final Ref
<UsageInfo
[]> refUsages
) {
137 UsageInfo
[] usages
= refUsages
.get();
138 String s
= getPreprocessUsagesMessage(usages
);
140 CommonRefactoringUtil
.showErrorMessage(RefactoringBundle
.message("inline.to.anonymous.refactoring"), s
, null, myClass
.getProject());
143 MultiMap
<PsiElement
, String
> conflicts
= getConflicts(usages
);
144 if (!conflicts
.isEmpty()) {
145 return showConflicts(conflicts
);
147 return super.preprocessUsages(refUsages
);
150 public MultiMap
<PsiElement
, String
> getConflicts(final UsageInfo
[] usages
) {
151 MultiMap
<PsiElement
, String
> result
= new MultiMap
<PsiElement
, String
>();
152 ReferencedElementsCollector collector
= new ReferencedElementsCollector() {
153 protected void checkAddMember(@NotNull final PsiMember member
) {
154 if (PsiTreeUtil
.isAncestor(myClass
, member
, false)) {
157 final PsiModifierList modifierList
= member
.getModifierList();
158 if (member
.getContainingClass() == myClass
.getSuperClass() && modifierList
!= null &&
159 modifierList
.hasModifierProperty(PsiModifier
.PROTECTED
)) {
160 // ignore access to protected members of superclass - they'll be accessible anyway
163 super.checkAddMember(member
);
166 InlineMethodProcessor
.addInaccessibleMemberConflicts(myClass
, usages
, collector
, result
);
170 protected void performRefactoring(UsageInfo
[] usages
) {
171 PsiClassType superType
= getSuperType();
173 List
<PsiElement
> elementsToDelete
= new ArrayList
<PsiElement
>();
174 List
<PsiNewExpression
> newExpressions
= new ArrayList
<PsiNewExpression
>();
175 for(UsageInfo info
: usages
) {
176 final PsiElement element
= info
.getElement();
177 if (element
instanceof PsiNewExpression
) {
178 newExpressions
.add((PsiNewExpression
)element
);
180 else if (element
.getParent() instanceof PsiNewExpression
) {
181 newExpressions
.add((PsiNewExpression
) element
.getParent());
184 PsiImportStatement statement
= PsiTreeUtil
.getParentOfType(element
, PsiImportStatement
.class);
185 if (statement
!= null && !myInlineThisOnly
) {
186 elementsToDelete
.add(statement
);
189 PsiTypeElement typeElement
= PsiTreeUtil
.getParentOfType(element
, PsiTypeElement
.class);
190 if (typeElement
!= null) {
191 replaceWithSuperType(typeElement
, superType
);
197 Collections
.sort(newExpressions
, PsiUtil
.BY_POSITION
);
198 for(PsiNewExpression newExpression
: newExpressions
) {
199 replaceNewOrType(newExpression
, superType
);
202 for(PsiElement element
: elementsToDelete
) {
204 if (element
.isValid()) {
208 catch (IncorrectOperationException e
) {
212 if (!myInlineThisOnly
) {
216 catch(IncorrectOperationException e
) {
222 private void replaceNewOrType(final PsiNewExpression psiNewExpression
, final PsiClassType superType
) {
224 if (psiNewExpression
.getArrayDimensions().length
== 0 && psiNewExpression
.getArrayInitializer() == null) {
225 new InlineToAnonymousConstructorProcessor(myClass
, psiNewExpression
, superType
).run();
228 PsiJavaCodeReferenceElement element
=
229 JavaPsiFacade
.getInstance(myClass
.getProject()).getElementFactory().createClassReferenceElement(superType
.resolve());
230 psiNewExpression
.getClassReference().replace(element
);
233 catch (IncorrectOperationException e
) {
238 private void replaceWithSuperType(final PsiTypeElement typeElement
, final PsiClassType superType
) {
239 PsiElementFactory factory
= JavaPsiFacade
.getInstance(myClass
.getProject()).getElementFactory();
240 PsiClassType psiType
= (PsiClassType
) typeElement
.getType();
241 PsiClassType
.ClassResolveResult classResolveResult
= psiType
.resolveGenerics();
242 PsiType substType
= classResolveResult
.getSubstitutor().substitute(superType
);
243 assert classResolveResult
.getElement() == myClass
;
245 typeElement
.replace(factory
.createTypeElement(substType
));
247 catch(IncorrectOperationException e
) {
252 private PsiClassType
getSuperType() {
253 PsiElementFactory factory
= JavaPsiFacade
.getInstance(myClass
.getProject()).getElementFactory();
255 PsiClassType superType
;
256 PsiClass superClass
= myClass
.getSuperClass();
257 PsiClassType
[] interfaceTypes
= myClass
.getImplementsListTypes();
258 if (interfaceTypes
.length
> 0 && !InlineToAnonymousClassHandler
.isRedundantImplements(superClass
, interfaceTypes
[0])) {
259 assert interfaceTypes
.length
== 1;
260 superType
= interfaceTypes
[0];
263 PsiClassType
[] classTypes
= myClass
.getExtendsListTypes();
264 if (classTypes
.length
> 0) {
265 superType
= classTypes
[0];
268 superType
= factory
.createType(superClass
);
274 protected String
getCommandName() {
275 return RefactoringBundle
.message("inline.to.anonymous.command.name", myClass
.getQualifiedName());
279 public String
getPreprocessUsagesMessage(final UsageInfo
[] usages
) {
280 boolean hasUsages
= false;
281 for(UsageInfo usage
: usages
) {
282 final PsiElement element
= usage
.getElement();
283 if (element
== null) continue;
284 if (!PsiTreeUtil
.isAncestor(myClass
, element
, false)) {
287 final PsiElement parentElement
= element
.getParent();
288 if (parentElement
!= null) {
289 if (parentElement
.getParent() instanceof PsiClassObjectAccessExpression
) {
290 return "Class cannot be inlined because it has usages of its class literal";
292 if (ourCatchClausePattern
.accepts(parentElement
)) {
293 return "Class cannot be inlined because it is used in a 'catch' clause";
296 if (ourThrowsClausePattern
.accepts(element
)) {
297 return "Class cannot be inlined because it is used in a 'throws' clause";
299 if (parentElement
instanceof PsiThisExpression
) {
300 return "Class cannot be inlined because it is used as a 'this' qualifier";
302 if (parentElement
instanceof PsiNewExpression
) {
303 final PsiNewExpression newExpression
= (PsiNewExpression
)parentElement
;
304 final PsiMethod
[] constructors
= myClass
.getConstructors();
305 if (constructors
.length
== 0) {
306 PsiExpressionList newArgumentList
= newExpression
.getArgumentList();
307 if (newArgumentList
!= null && newArgumentList
.getExpressions().length
> 0) {
308 return "Class cannot be inlined because a call to its constructor is unresolved";
312 final JavaResolveResult resolveResult
= newExpression
.resolveMethodGenerics();
313 if (!resolveResult
.isValidResult()) {
314 return "Class cannot be inlined because a call to its constructor is unresolved";
320 return RefactoringBundle
.message("class.is.never.used");