update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineToAnonymousClassProcessor.java
blob2ecf5ea50db7faf56078f61c9db3ae4e32ce2e10
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.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;
41 import java.util.*;
43 /**
44 * @author yole
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,
61 PsiClass psiClass,
62 @Nullable final PsiCall callToInline,
63 boolean inlineThisOnly,
64 final boolean searchInComments,
65 final boolean searchInNonJavaFiles) {
66 super(project);
67 myClass = psiClass;
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);
79 @NotNull
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();
90 if (qName != null) {
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"));
118 return true;
121 return false;
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)) {
130 return true;
133 return false;
136 protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
137 UsageInfo[] usages = refUsages.get();
138 String s = getPreprocessUsagesMessage(usages);
139 if (s != null) {
140 CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("inline.to.anonymous.refactoring"), s, null, myClass.getProject());
141 return false;
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)) {
155 return;
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
161 return;
163 super.checkAddMember(member);
166 InlineMethodProcessor.addInaccessibleMemberConflicts(myClass, usages, collector, result);
167 return 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());
183 else {
184 PsiImportStatement statement = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
185 if (statement != null && !myInlineThisOnly) {
186 elementsToDelete.add(statement);
188 else {
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) {
203 try {
204 if (element.isValid()) {
205 element.delete();
208 catch (IncorrectOperationException e) {
209 LOG.error(e);
212 if (!myInlineThisOnly) {
213 try {
214 myClass.delete();
216 catch(IncorrectOperationException e) {
217 LOG.error(e);
222 private void replaceNewOrType(final PsiNewExpression psiNewExpression, final PsiClassType superType) {
223 try {
224 if (psiNewExpression.getArrayDimensions().length == 0 && psiNewExpression.getArrayInitializer() == null) {
225 new InlineToAnonymousConstructorProcessor(myClass, psiNewExpression, superType).run();
227 else {
228 PsiJavaCodeReferenceElement element =
229 JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory().createClassReferenceElement(superType.resolve());
230 psiNewExpression.getClassReference().replace(element);
233 catch (IncorrectOperationException e) {
234 LOG.error(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;
244 try {
245 typeElement.replace(factory.createTypeElement(substType));
247 catch(IncorrectOperationException e) {
248 LOG.error(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];
262 else {
263 PsiClassType[] classTypes = myClass.getExtendsListTypes();
264 if (classTypes.length > 0) {
265 superType = classTypes [0];
267 else {
268 superType = factory.createType(superClass);
271 return superType;
274 protected String getCommandName() {
275 return RefactoringBundle.message("inline.to.anonymous.command.name", myClass.getQualifiedName());
278 @Nullable
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)) {
285 hasUsages = true;
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";
311 else {
312 final JavaResolveResult resolveResult = newExpression.resolveMethodGenerics();
313 if (!resolveResult.isValidResult()) {
314 return "Class cannot be inlined because a call to its constructor is unresolved";
319 if (!hasUsages) {
320 return RefactoringBundle.message("class.is.never.used");
322 return null;