[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / AddMethodQualifierFix.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.codeInsight.daemon.impl.quickfix;
17
18 import com.intellij.codeInsight.daemon.QuickFixBundle;
19 import com.intellij.codeInsight.intention.IntentionAction;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.command.WriteCommandAction;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.ui.popup.PopupStep;
25 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
26 import com.intellij.psi.*;
27 import com.intellij.ui.popup.list.ListPopupImpl;
28 import com.intellij.util.IncorrectOperationException;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.jetbrains.annotations.TestOnly;
32
33 import javax.swing.*;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37
38 /**
39  * @author Dmitry Batkovich
40  */
41 public class AddMethodQualifierFix implements IntentionAction {
42   private static final boolean UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
43   private enum SearchMode { MAX_2_CANDIDATES, FULL_SEARCH }
44
45   private final SmartPsiElementPointer<PsiMethodCallExpression> myMethodCall;
46   private List<PsiVariable> myCandidates;
47
48   public AddMethodQualifierFix(final PsiMethodCallExpression methodCallExpression) {
49     myMethodCall = SmartPointerManager.getInstance(methodCallExpression.getProject()).createSmartPsiElementPointer(methodCallExpression);
50   }
51
52   @NotNull
53   @Override
54   public String getText() {
55     if (myCandidates == null || myCandidates.isEmpty()) {
56       if(ApplicationManager.getApplication().isUnitTestMode()) {
57         return "";
58       }
59       throw new IllegalStateException();
60     }
61     if (myCandidates.size() == 1) {
62       return QuickFixBundle.message("add.method.qualifier.fix.text", myCandidates.get(0).getName());
63     } else {
64       return getFamilyName();
65     }
66   }
67
68   @NotNull
69   @Override
70   public String getFamilyName() {
71     return QuickFixBundle.message("add.method.qualifier.fix.family");
72   }
73
74   @Override
75   public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) {
76     final PsiMethodCallExpression element = myMethodCall.getElement();
77     if (element == null || !element.isValid() || element.getMethodExpression().getQualifierExpression() != null) {
78       return false;
79     }
80     if (myCandidates == null) {
81       myCandidates = findCandidates(SearchMode.MAX_2_CANDIDATES);
82     }
83     return !myCandidates.isEmpty();
84   }
85
86   private List<PsiVariable> findCandidates(@NotNull SearchMode mode) {
87     List<PsiVariable> candidates = new ArrayList<>();
88     final PsiMethodCallExpression methodCallElement = myMethodCall.getElement();
89     final String methodName = methodCallElement.getMethodExpression().getReferenceName();
90     if (methodName == null) {
91       return Collections.emptyList();
92     }
93
94     for (final PsiVariable var : CreateFromUsageUtils.guessMatchingVariables(methodCallElement)) {
95       if (var.getName() == null) {
96         continue;
97       }
98       final PsiType type = var.getType();
99       if (!(type instanceof PsiClassType)) {
100         continue;
101       }
102       final PsiClass resolvedClass = ((PsiClassType)type).resolve();
103       if (resolvedClass == null) {
104         continue;
105       }
106       if (resolvedClass.findMethodsByName(methodName, true).length > 0) {
107         candidates.add(var);
108         if (mode == SearchMode.MAX_2_CANDIDATES && candidates.size() >= 2) {
109           return candidates;
110         }
111       }
112     }
113     return candidates;
114   }
115
116   @TestOnly
117   public List<PsiVariable> getCandidates() {
118     return findCandidates(SearchMode.FULL_SEARCH);
119   }
120
121   @Nullable
122   @Override
123   public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
124     return myMethodCall.getElement();
125   }
126
127   @Override
128   public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
129     List<PsiVariable> candidates = findCandidates(SearchMode.FULL_SEARCH);
130     if (candidates.size() == 1 || UNIT_TEST_MODE) {
131       qualify(candidates.get(0), editor);
132     }
133     else {
134       chooseAndQualify(project, editor, candidates);
135     }
136   }
137
138   @Override
139   public boolean startInWriteAction() {
140     return false;
141   }
142
143   private void chooseAndQualify(Project project, Editor editor, List<PsiVariable> candidates) {
144     final BaseListPopupStep<PsiVariable> step =
145       new BaseListPopupStep<PsiVariable>(QuickFixBundle.message("add.qualifier"), candidates) {
146         @Override
147         public PopupStep onChosen(final PsiVariable selectedValue, final boolean finalChoice) {
148           if (selectedValue != null && finalChoice) {
149             qualify(selectedValue, editor);
150           }
151           return FINAL_CHOICE;
152         }
153
154         @NotNull
155         @Override
156         public String getTextFor(final PsiVariable value) {
157           return value.getName();
158         }
159
160         @Override
161         public Icon getIconFor(final PsiVariable aValue) {
162           return aValue.getIcon(0);
163         }
164       };
165
166     ListPopupImpl popup = new ListPopupImpl(project, step);
167     popup.showInBestPositionFor(editor);
168   }
169
170   private void qualify(final PsiVariable qualifier, final Editor editor) {
171     WriteCommandAction.runWriteCommandAction(qualifier.getProject(), () -> {
172       final String qualifierPresentableText = qualifier.getName();
173       final PsiMethodCallExpression oldExpression = myMethodCall.getElement();
174       final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(qualifier.getProject());
175       final PsiExpression expression = elementFactory
176         .createExpressionFromText(qualifierPresentableText + "." + oldExpression.getMethodExpression().getReferenceName() + "()", null);
177       final PsiElement replacedExpression = oldExpression.replace(expression);
178       editor.getCaretModel().moveToOffset(replacedExpression.getTextOffset() + replacedExpression.getTextLength());
179     });
180   }
181 }