summaryrefslogtreecommitdiff
path: root/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousClassProcessor.java
blob: 25a9481259d805070299b2b9ec796edde27d560c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.refactoring.inline;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.NonCodeUsageInfoFactory;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * @author yole
 */
public class InlineToAnonymousClassProcessor extends BaseRefactoringProcessor {
  private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineToAnonymousClassProcessor");

  private PsiClass myClass;
  private final PsiCall myCallToInline;
  private final boolean myInlineThisOnly;
  private final boolean mySearchInComments;
  private final boolean mySearchInNonJavaFiles;

  protected InlineToAnonymousClassProcessor(Project project,
                                            PsiClass psiClass,
                                            @Nullable final PsiCall callToInline,
                                            boolean inlineThisOnly,
                                            final boolean searchInComments,
                                            final boolean searchInNonJavaFiles) {
    super(project);
    myClass = psiClass;
    myCallToInline = callToInline;
    myInlineThisOnly = inlineThisOnly;
    if (myInlineThisOnly) assert myCallToInline != null;
    mySearchInComments = searchInComments;
    mySearchInNonJavaFiles = searchInNonJavaFiles;
  }

  @NotNull
  protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
    return new InlineViewDescriptor(myClass);
  }

  @NotNull
  protected UsageInfo[] findUsages() {
    if (myInlineThisOnly) {
      return new UsageInfo[] { new UsageInfo(myCallToInline) };
    }
    Set<UsageInfo> usages = new HashSet<UsageInfo>();
    for (PsiReference reference : ReferencesSearch.search(myClass)) {
      usages.add(new UsageInfo(reference.getElement()));
    }

    final String qName = myClass.getQualifiedName();
    if (qName != null) {
      List<UsageInfo> nonCodeUsages = new ArrayList<UsageInfo>();
      if (mySearchInComments) {
        TextOccurrencesUtil.addUsagesInStringsAndComments(myClass, qName, nonCodeUsages,
                                                      new NonCodeUsageInfoFactory(myClass, qName));
      }

      if (mySearchInNonJavaFiles) {
        GlobalSearchScope projectScope = GlobalSearchScope.projectScope(myClass.getProject());
        TextOccurrencesUtil.addTextOccurences(myClass, qName, projectScope, nonCodeUsages,
                                          new NonCodeUsageInfoFactory(myClass, qName));
      }
      usages.addAll(nonCodeUsages);
    }

    return usages.toArray(new UsageInfo[usages.size()]);
  }

  protected void refreshElements(PsiElement[] elements) {
    assert elements.length == 1;
    myClass = (PsiClass) elements [0];
  }

  protected boolean isPreviewUsages(UsageInfo[] usages) {
    if (super.isPreviewUsages(usages)) return true;
    for(UsageInfo usage: usages) {
      if (isForcePreview(usage)) {
        WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("occurrences.found.in.comments.strings.and.non.java.files"));
        return true;
      }
    }
    return false;
  }

  private static boolean isForcePreview(final UsageInfo usage) {
    if (usage.isNonCodeUsage) return true;
    PsiElement element = usage.getElement();
    if (element != null) {
      PsiFile file = element.getContainingFile();
      if (!(file instanceof PsiJavaFile)) {
        return true;
      }
    }
    return false;
  }

  protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) {
    MultiMap<PsiElement, String> conflicts = getConflicts(refUsages.get());
    if (!conflicts.isEmpty()) {
      return showConflicts(conflicts, refUsages.get());
    }
    return super.preprocessUsages(refUsages);
  }

  public MultiMap<PsiElement, String> getConflicts(final UsageInfo[] usages) {
    final MultiMap<PsiElement, String> result = new MultiMap<PsiElement, String>();
    ReferencedElementsCollector collector = new ReferencedElementsCollector() {
      protected void checkAddMember(@NotNull final PsiMember member) {
        if (PsiTreeUtil.isAncestor(myClass, member, false)) {
          return;
        }
        final PsiModifierList modifierList = member.getModifierList();
        if (member.getContainingClass() == myClass.getSuperClass() && modifierList != null &&
            modifierList.hasModifierProperty(PsiModifier.PROTECTED)) {
          // ignore access to protected members of superclass - they'll be accessible anyway
          return;
        }
        super.checkAddMember(member);
      }
    };
    InlineMethodProcessor.addInaccessibleMemberConflicts(myClass, usages, collector, result);
    myClass.accept(new JavaRecursiveElementVisitor(){
      @Override
      public void visitParameter(PsiParameter parameter) {
        super.visitParameter(parameter);
        if (PsiUtil.resolveClassInType(parameter.getType()) != myClass) return;

        for (PsiReference psiReference : ReferencesSearch.search(parameter)) {
          final PsiElement refElement = psiReference.getElement();
          if (refElement instanceof PsiExpression) {
            final PsiReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(refElement, PsiReferenceExpression.class);
            if (referenceExpression != null && referenceExpression.getQualifierExpression() == refElement) {
              final PsiElement resolvedMember = referenceExpression.resolve();
              if (resolvedMember != null && PsiTreeUtil.isAncestor(myClass, resolvedMember, false)) {
                if (resolvedMember instanceof PsiMethod) {
                  if (myClass.findMethodsBySignature((PsiMethod)resolvedMember, true).length > 1) { //skip inherited methods
                    continue;
                  }
                }
                result.putValue(refElement, "Class cannot be inlined because a call to its member inside body");
              }
            }
          }
        }
      }

      @Override
      public void visitNewExpression(PsiNewExpression expression) {
        super.visitNewExpression(expression);
        if (PsiUtil.resolveClassInType(expression.getType()) != myClass) return;
        result.putValue(expression, "Class cannot be inlined because a call to its constructor inside body");
      }

      @Override
      public void visitMethodCallExpression(PsiMethodCallExpression expression) {
        super.visitMethodCallExpression(expression);
        final PsiReferenceExpression methodExpression = expression.getMethodExpression();
        final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
        if (qualifierExpression != null && PsiUtil.resolveClassInType(qualifierExpression.getType()) != myClass) return;
        final PsiElement resolved = methodExpression.resolve();
        if (resolved instanceof PsiMethod) {
          final PsiMethod method = (PsiMethod)resolved;
          if ("getClass".equals(method.getName()) && method.getParameterList().getParametersCount() == 0) {
            result.putValue(methodExpression, "Result of getClass() invocation would be changed");
          }
        }
      }
    });
    return result;
  }

  protected void performRefactoring(UsageInfo[] usages) {
    final PsiClassType superType = getSuperType(myClass);
    LOG.assertTrue(superType != null);
    List<PsiElement> elementsToDelete = new ArrayList<PsiElement>();
    List<PsiNewExpression> newExpressions = new ArrayList<PsiNewExpression>();
    for(UsageInfo info: usages) {
      final PsiElement element = info.getElement();
      if (element instanceof PsiNewExpression) {
        newExpressions.add((PsiNewExpression)element);
      }
      else if (element.getParent() instanceof PsiNewExpression) {
        newExpressions.add((PsiNewExpression) element.getParent());
      }
      else {
        PsiImportStatement statement = PsiTreeUtil.getParentOfType(element, PsiImportStatement.class);
        if (statement != null && !myInlineThisOnly) {
          elementsToDelete.add(statement);
        }
        else {
          PsiTypeElement typeElement = PsiTreeUtil.getParentOfType(element, PsiTypeElement.class);
          if (typeElement != null) {
            replaceWithSuperType(typeElement, superType);
          }
        }
      }
    }

    Collections.sort(newExpressions, PsiUtil.BY_POSITION);
    for(PsiNewExpression newExpression: newExpressions) {
      replaceNewOrType(newExpression, superType);
    }

    for(PsiElement element: elementsToDelete) {
      try {
        if (element.isValid()) {
          element.delete();
        }
      }
      catch (IncorrectOperationException e) {
        LOG.error(e);
      }
    }
    if (!myInlineThisOnly) {
      try {
        myClass.delete();
      }
      catch(IncorrectOperationException e) {
        LOG.error(e);
      }
    }
  }

  private void replaceNewOrType(final PsiNewExpression psiNewExpression, final PsiClassType superType) {
    try {
      if (psiNewExpression.getArrayDimensions().length == 0 && psiNewExpression.getArrayInitializer() == null) {
        new InlineToAnonymousConstructorProcessor(myClass, psiNewExpression, superType).run();
      }
      else {
        PsiJavaCodeReferenceElement element =
          JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory().createClassReferenceElement(superType.resolve());
        psiNewExpression.getClassReference().replace(element);        
      }
    }
    catch (IncorrectOperationException e) {
      LOG.error(e);
    }
  }

  private void replaceWithSuperType(final PsiTypeElement typeElement, final PsiClassType superType) {
    PsiElementFactory factory = JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory();
    PsiClassType psiType = (PsiClassType) typeElement.getType();
    PsiClassType.ClassResolveResult classResolveResult = psiType.resolveGenerics();
    PsiType substType = classResolveResult.getSubstitutor().substitute(superType);
    assert classResolveResult.getElement() == myClass;
    try {
      typeElement.replace(factory.createTypeElement(substType));
    }
    catch(IncorrectOperationException e) {
      LOG.error(e);
    }
  }

  @Nullable
  public static PsiClassType getSuperType(final PsiClass aClass) {
    PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();

    PsiClassType superType;
    PsiClass superClass = aClass.getSuperClass();
    PsiClassType[] interfaceTypes = aClass.getImplementsListTypes();
    if (interfaceTypes.length > 0 && !InlineToAnonymousClassHandler.isRedundantImplements(superClass, interfaceTypes [0])) {
      superType = interfaceTypes [0];
    }
    else {
      PsiClassType[] classTypes = aClass.getExtendsListTypes();
      if (classTypes.length > 0) {
        superType = classTypes [0];
      }
      else {
        if (superClass == null) {
          //java.lang.Object was not found
          return null;
        }
        superType = factory.createType(superClass);
      }
    }
    return superType;
  }

  protected String getCommandName() {
    return RefactoringBundle.message("inline.to.anonymous.command.name", myClass.getQualifiedName());
  }

}