Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / ide / util / SuperMethodWarningUtil.java
1 /*
2  * Copyright 2000-2015 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.ide.util;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.codeInspection.InspectionsBundle;
20 import com.intellij.lang.findUsages.DescriptiveNameUtil;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ReadAction;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.progress.ProcessCanceledException;
25 import com.intellij.openapi.progress.ProgressManager;
26 import com.intellij.openapi.roots.ProjectRootManager;
27 import com.intellij.openapi.ui.DialogWrapper;
28 import com.intellij.openapi.ui.Messages;
29 import com.intellij.openapi.ui.popup.JBPopupFactory;
30 import com.intellij.openapi.util.Key;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.PsiClass;
33 import com.intellij.psi.PsiElement;
34 import com.intellij.psi.PsiMethod;
35 import com.intellij.psi.PsiModifier;
36 import com.intellij.psi.impl.FindSuperElementsHelper;
37 import com.intellij.psi.presentation.java.SymbolPresentationUtil;
38 import com.intellij.psi.search.PsiElementProcessor;
39 import com.intellij.psi.search.searches.DeepestSuperMethodsSearch;
40 import com.intellij.psi.util.PsiUtilCore;
41 import com.intellij.util.ArrayUtil;
42 import com.intellij.util.containers.ContainerUtil;
43 import org.jetbrains.annotations.NotNull;
44
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.HashSet;
48 import java.util.Set;
49
50 public class SuperMethodWarningUtil {
51   public static Key<PsiMethod[]> SIBLINGS = Key.create("MULTIPLE_INHERITANCE");
52   private SuperMethodWarningUtil() {}
53
54   @NotNull
55   public static PsiMethod[] checkSuperMethods(@NotNull PsiMethod method, @NotNull String actionString) {
56     ApplicationManager.getApplication().assertIsDispatchThread();
57     return checkSuperMethods(method, actionString, Collections.emptyList());
58   }
59
60   @NotNull
61   public static PsiMethod[] getTargetMethodCandidates(@NotNull PsiMethod method, @NotNull Collection<PsiElement> ignore) {
62     PsiClass aClass = method.getContainingClass();
63     if (aClass == null) return new PsiMethod[]{method};
64
65     final Collection<PsiMethod> superMethods = getSuperMethods(method, aClass, ignore);
66     if (superMethods.isEmpty()) return new PsiMethod[]{method};
67     return superMethods.toArray(PsiMethod.EMPTY_ARRAY);
68   }
69   
70   @NotNull
71   public static PsiMethod[] checkSuperMethods(@NotNull PsiMethod method, @NotNull String actionString, @NotNull Collection<PsiElement> ignore) {
72     ApplicationManager.getApplication().assertIsDispatchThread();
73     PsiMethod[] methodTargetCandidates = getTargetMethodCandidates(method, ignore);
74     if (methodTargetCandidates.length == 1 && methodTargetCandidates[0] == method) return methodTargetCandidates;
75
76     Set<String> superClasses = new HashSet<>();
77     boolean superAbstract = false;
78     boolean parentInterface = false;
79     for (final PsiMethod superMethod : methodTargetCandidates) {
80       final PsiClass containingClass = superMethod.getContainingClass();
81       superClasses.add(containingClass.getQualifiedName());
82       final boolean isInterface = containingClass.isInterface();
83       superAbstract |= isInterface || superMethod.hasModifierProperty(PsiModifier.ABSTRACT);
84       parentInterface |= isInterface;
85     }
86
87     SuperMethodWarningDialog dialog =
88         new SuperMethodWarningDialog(method.getProject(), DescriptiveNameUtil.getDescriptiveName(method), actionString, superAbstract,
89                                      parentInterface, method.getContainingClass().isInterface(), ArrayUtil.toStringArray(superClasses));
90     dialog.show();
91
92     if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
93       return methodTargetCandidates;
94     }
95     if (dialog.getExitCode() == SuperMethodWarningDialog.NO_EXIT_CODE) {
96       return new PsiMethod[]{method};
97     }
98
99     return PsiMethod.EMPTY_ARRAY;
100   }
101
102   @NotNull
103   static Collection<PsiMethod> getSuperMethods(@NotNull PsiMethod method, PsiClass aClass, @NotNull Collection<PsiElement> ignore) {
104     ApplicationManager.getApplication().assertIsDispatchThread();
105     assert !ApplicationManager.getApplication().isWriteAccessAllowed();
106     final Collection<PsiMethod> superMethods = DeepestSuperMethodsSearch.search(method).findAll();
107     superMethods.removeAll(ignore);
108
109     if (superMethods.isEmpty()) {
110       VirtualFile virtualFile = PsiUtilCore.getVirtualFile(aClass);
111       if (virtualFile != null && ProjectRootManager.getInstance(aClass.getProject()).getFileIndex().isInSourceContent(virtualFile)) {
112         PsiMethod[] siblingSuperMethod = new PsiMethod[1];
113         if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(()->{
114           siblingSuperMethod[0] = ReadAction.compute(()->FindSuperElementsHelper.getSiblingInheritedViaSubClass(method));
115         }, "Searching for sub-classes", true, aClass.getProject())) {
116           throw new ProcessCanceledException();
117         }
118         if (siblingSuperMethod[0] != null) {
119           superMethods.add(siblingSuperMethod[0]);
120         }
121       }
122     }
123     return superMethods;
124   }
125
126
127   public static PsiMethod checkSuperMethod(@NotNull PsiMethod method, @NotNull String actionString) {
128     ApplicationManager.getApplication().assertIsDispatchThread();
129     PsiClass aClass = method.getContainingClass();
130     if (aClass == null) return method;
131
132     PsiMethod superMethod = method.findDeepestSuperMethod();
133     if (superMethod == null) return method;
134
135     if (ApplicationManager.getApplication().isUnitTestMode()) return superMethod;
136
137     PsiClass containingClass = superMethod.getContainingClass();
138
139     SuperMethodWarningDialog dialog =
140         new SuperMethodWarningDialog(
141             method.getProject(),
142             DescriptiveNameUtil.getDescriptiveName(method), actionString, containingClass.isInterface() || superMethod.hasModifierProperty(PsiModifier.ABSTRACT),
143             containingClass.isInterface(), aClass.isInterface(), containingClass.getQualifiedName()
144         );
145     dialog.show();
146
147     if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) return superMethod;
148     if (dialog.getExitCode() == SuperMethodWarningDialog.NO_EXIT_CODE) return method;
149
150     return null;
151   }
152
153   public static void checkSuperMethod(@NotNull PsiMethod method,
154                                       @NotNull String actionString,
155                                       @NotNull final PsiElementProcessor<? super PsiMethod> processor,
156                                       @NotNull Editor editor) {
157     ApplicationManager.getApplication().assertIsDispatchThread();
158     PsiClass aClass = method.getContainingClass();
159     if (aClass == null) {
160       processor.execute(method);
161       return;
162     }
163
164     PsiMethod[] superMethods = method.findDeepestSuperMethods();
165     if (superMethods.length == 0) {
166       processor.execute(method);
167       return;
168     }
169
170     final PsiClass containingClass = superMethods[0].getContainingClass();
171     if (containingClass == null) {
172       processor.execute(method);
173       return;
174     }
175
176     if (ApplicationManager.getApplication().isUnitTestMode()) {
177       processor.execute(superMethods[0]);
178       return;
179     }
180
181     final PsiMethod[] methods = {superMethods[0], method};
182     final String renameBase = actionString + " base method" + (superMethods.length > 1 ? "s" : "");
183     final String renameCurrent = actionString + " only current method";
184     String title = method.getName() +
185                    (superMethods.length > 1 ? " has super methods"
186                                             : (containingClass.isInterface() && !aClass.isInterface() ? " implements"
187                                                                                                       : " overrides") +
188                                               " method of " + SymbolPresentationUtil.getSymbolPresentableText(containingClass));
189     JBPopupFactory.getInstance().createPopupChooserBuilder(ContainerUtil.newArrayList(renameBase, renameCurrent))
190       .setTitle(title)
191       .setMovable(false)
192       .setResizable(false)
193       .setRequestFocus(true)
194       .setItemChosenCallback((value) -> {
195         if (value.equals(renameBase)) {
196           try {
197             methods[0].putUserData(SIBLINGS, superMethods);
198             processor.execute(methods[0]);
199           }
200           finally {
201             methods[0].putUserData(SIBLINGS, null);
202           }
203         }
204         else {
205           processor.execute(methods[1]);
206         }
207       })
208       .createPopup().showInBestPositionFor(editor);
209   }
210
211   @Messages.YesNoCancelResult
212   public static int askWhetherShouldAnnotateBaseMethod(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
213     ApplicationManager.getApplication().assertIsDispatchThread();
214     String implement = !method.hasModifierProperty(PsiModifier.ABSTRACT) && superMethod.hasModifierProperty(PsiModifier.ABSTRACT)
215                   ? InspectionsBundle.message("inspection.annotate.quickfix.implements")
216                   : InspectionsBundle.message("inspection.annotate.quickfix.overrides");
217     String message = InspectionsBundle.message("inspection.annotate.quickfix.overridden.method.messages",
218                                                DescriptiveNameUtil.getDescriptiveName(method), implement,
219                                                DescriptiveNameUtil.getDescriptiveName(superMethod));
220     String title = InspectionsBundle.message("inspection.annotate.quickfix.overridden.method.warning");
221     return Messages.showYesNoCancelDialog(method.getProject(), message, title, "Annotate", "Don't Annotate", CommonBundle.getCancelButtonText(), Messages.getQuestionIcon());
222   }
223 }