EditorConfig documentation test
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / changeSignature / ChangeSignatureProcessor.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.refactoring.changeSignature;
3
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.project.Project;
7 import com.intellij.openapi.ui.Messages;
8 import com.intellij.openapi.util.Ref;
9 import com.intellij.psi.*;
10 import com.intellij.psi.codeStyle.CodeStyleManager;
11 import com.intellij.psi.util.*;
12 import com.intellij.refactoring.RefactoringBundle;
13 import com.intellij.refactoring.rename.RenameUtil;
14 import com.intellij.refactoring.ui.ConflictsDialog;
15 import com.intellij.refactoring.util.CanonicalTypes;
16 import com.intellij.usageView.UsageInfo;
17 import com.intellij.usageView.UsageViewDescriptor;
18 import com.intellij.util.IncorrectOperationException;
19 import com.intellij.util.VisibilityUtil;
20 import com.intellij.util.containers.MultiMap;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23
24 import java.util.*;
25
26 import static com.intellij.util.ObjectUtils.assertNotNull;
27
28 /**
29  * @author Jeka
30  */
31 public class ChangeSignatureProcessor extends ChangeSignatureProcessorBase {
32   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeSignature.ChangeSignatureProcessor");
33
34   public ChangeSignatureProcessor(Project project,
35                                   PsiMethod method,
36                                   final boolean generateDelegate,
37                                   @Nullable // null means unchanged
38                                   @PsiModifier.ModifierConstant String newVisibility,
39                                   String newName,
40                                   PsiType newType,
41                                   @NotNull ParameterInfoImpl[] parameterInfo) {
42     this(project, method, generateDelegate, newVisibility, newName,
43          newType != null ? CanonicalTypes.createTypeWrapper(newType) : null,
44          parameterInfo, null, null, null);
45   }
46
47   public ChangeSignatureProcessor(Project project,
48                                   PsiMethod method,
49                                   final boolean generateDelegate,
50                                   @Nullable // null means unchanged
51                                   @PsiModifier.ModifierConstant String newVisibility,
52                                   String newName,
53                                   PsiType newType,
54                                   ParameterInfoImpl[] parameterInfo,
55                                   ThrownExceptionInfo[] exceptionInfos) {
56     this(project, method, generateDelegate, newVisibility, newName,
57          newType != null ? CanonicalTypes.createTypeWrapper(newType) : null,
58          parameterInfo, exceptionInfos, null, null);
59   }
60
61   public ChangeSignatureProcessor(Project project,
62                                   PsiMethod method,
63                                   boolean generateDelegate,
64                                   @Nullable // null means unchanged
65                                   @PsiModifier.ModifierConstant String newVisibility,
66                                   String newName,
67                                   CanonicalTypes.Type newType,
68                                   @NotNull ParameterInfoImpl[] parameterInfo,
69                                   ThrownExceptionInfo[] thrownExceptions,
70                                   Set<PsiMethod> propagateParametersMethods,
71                                   Set<PsiMethod> propagateExceptionsMethods) {
72     this(project, generateChangeInfo(method, generateDelegate, newVisibility, newName, newType, parameterInfo, thrownExceptions,
73                                      propagateParametersMethods, propagateExceptionsMethods));
74   }
75
76   public ChangeSignatureProcessor(Project project, final JavaChangeInfo changeInfo) {
77     super(project, changeInfo);
78     LOG.assertTrue(myChangeInfo.getMethod().isValid());
79   }
80
81   private static JavaChangeInfo generateChangeInfo(PsiMethod method,
82                                                    boolean generateDelegate,
83                                                    @Nullable // null means unchanged
84                                                    @PsiModifier.ModifierConstant String newVisibility,
85                                                    String newName,
86                                                    CanonicalTypes.Type newType,
87                                                    @NotNull ParameterInfoImpl[] parameterInfo,
88                                                    ThrownExceptionInfo[] thrownExceptions,
89                                                    Set<PsiMethod> propagateParametersMethods,
90                                                    Set<PsiMethod> propagateExceptionsMethods) {
91     LOG.assertTrue(method.isValid());
92
93     if (propagateParametersMethods == null) {
94       propagateParametersMethods = new HashSet<>();
95     }
96
97     if (propagateExceptionsMethods == null) {
98       propagateExceptionsMethods = new HashSet<>();
99     }
100
101     if (newVisibility == null) {
102       newVisibility = VisibilityUtil.getVisibilityModifier(method.getModifierList());
103     }
104
105     final JavaChangeInfoImpl javaChangeInfo =
106       new JavaChangeInfoImpl(newVisibility, method, newName, newType, parameterInfo, thrownExceptions, generateDelegate,
107                              propagateParametersMethods, propagateExceptionsMethods);
108     javaChangeInfo.setCheckUnusedParameter();
109     return javaChangeInfo;
110   }
111
112   @Override
113   @NotNull
114   protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
115     return new ChangeSignatureViewDescriptor(getChangeInfo().getMethod());
116   }
117
118   @Override
119   public JavaChangeInfoImpl getChangeInfo() {
120     return (JavaChangeInfoImpl)super.getChangeInfo();
121   }
122
123   @Override
124   protected void refreshElements(@NotNull PsiElement[] elements) {
125     boolean condition = elements.length == 1 && elements[0] instanceof PsiMethod;
126     LOG.assertTrue(condition);
127     getChangeInfo().updateMethod((PsiMethod) elements[0]);
128   }
129
130   @Override
131   protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
132     for (ChangeSignatureUsageProcessor processor : ChangeSignatureUsageProcessor.EP_NAME.getExtensions()) {
133       if (!processor.setupDefaultValues(myChangeInfo, refUsages, myProject)) return false;
134     }
135     MultiMap<PsiElement, String> conflictDescriptions = new MultiMap<>();
136     collectConflictsFromExtensions(refUsages, conflictDescriptions, myChangeInfo);
137
138     final UsageInfo[] usagesIn = refUsages.get();
139     RenameUtil.addConflictDescriptions(usagesIn, conflictDescriptions);
140     Set<UsageInfo> usagesSet = new HashSet<>(Arrays.asList(usagesIn));
141     RenameUtil.removeConflictUsages(usagesSet);
142     if (!conflictDescriptions.isEmpty()) {
143       if (ApplicationManager.getApplication().isUnitTestMode()) {
144         if (ConflictsInTestsException.isTestIgnore()) return true;
145         throw new ConflictsInTestsException(conflictDescriptions.values());
146       }
147       if (myPrepareSuccessfulSwingThreadCallback != null) {
148         ConflictsDialog dialog = prepareConflictsDialog(conflictDescriptions, usagesIn);
149         if (!dialog.showAndGet()) {
150           if (dialog.isShowConflicts()) prepareSuccessful();
151           return false;
152         }
153       }
154     }
155
156     if (myChangeInfo.isReturnTypeChanged()) {
157       askToRemoveCovariantOverriders(usagesSet);
158     }
159
160     refUsages.set(usagesSet.toArray(UsageInfo.EMPTY_ARRAY));
161     prepareSuccessful();
162     return true;
163   }
164
165   private void askToRemoveCovariantOverriders(Set<? extends UsageInfo> usages) {
166     if (PsiUtil.isLanguageLevel5OrHigher(myChangeInfo.getMethod())) {
167       List<UsageInfo> covariantOverriderInfos = new ArrayList<>();
168       for (UsageInfo usageInfo : usages) {
169         if (usageInfo instanceof OverriderUsageInfo) {
170           final OverriderUsageInfo info = (OverriderUsageInfo)usageInfo;
171           PsiMethod overrider = assertNotNull(info.getOverridingMethod());
172           PsiMethod baseMethod = info.getBaseMethod();
173           PsiSubstitutor substitutor = calculateSubstitutor(overrider, baseMethod);
174           PsiType type;
175           try {
176             type = substitutor.substitute(getChangeInfo().newReturnType.getType(myChangeInfo.getMethod(), myManager));
177           }
178           catch (IncorrectOperationException e) {
179             LOG.error(e);
180             return;
181           }
182           final PsiType overriderType = overrider.getReturnType();
183           if (overriderType != null && !type.equals(overriderType) && type.isAssignableFrom(overriderType)) {
184             covariantOverriderInfos.add(usageInfo);
185           }
186         }
187       }
188
189       // to be able to do filtering
190       preprocessCovariantOverriders(covariantOverriderInfos);
191
192       if (!covariantOverriderInfos.isEmpty()) {
193         if (ApplicationManager.getApplication().isUnitTestMode() || !isProcessCovariantOverriders()) {
194           for (UsageInfo usageInfo : covariantOverriderInfos) {
195             usages.remove(usageInfo);
196           }
197         }
198       }
199     }
200   }
201
202   protected void preprocessCovariantOverriders(final List<UsageInfo> covariantOverriderInfos) {
203   }
204
205   protected boolean isProcessCovariantOverriders() {
206     String message = RefactoringBundle.message("do.you.want.to.process.overriding.methods.with.covariant.return.type");
207     return Messages.showYesNoDialog(myProject, message, ChangeSignatureHandler.REFACTORING_NAME, Messages.getQuestionIcon()) == Messages.YES;
208   }
209
210   public static void makeEmptyBody(final PsiElementFactory factory, final PsiMethod delegate) throws IncorrectOperationException {
211     PsiCodeBlock body = delegate.getBody();
212     if (body != null) {
213       body.replace(factory.createCodeBlock());
214     }
215     else {
216       delegate.add(factory.createCodeBlock());
217     }
218     PsiUtil.setModifierProperty(delegate, PsiModifier.ABSTRACT, false);
219   }
220
221   @Nullable
222   public static PsiCallExpression addDelegatingCallTemplate(PsiMethod delegate, String newName) throws IncorrectOperationException {
223     Project project = delegate.getProject();
224     PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
225     PsiCodeBlock body = delegate.getBody();
226     assert body != null;
227     final PsiCallExpression callExpression;
228     if (delegate.isConstructor()) {
229       PsiElement callStatement = factory.createStatementFromText("this();", null);
230       callStatement = CodeStyleManager.getInstance(project).reformat(callStatement);
231       callStatement = body.add(callStatement);
232       callExpression = (PsiCallExpression)((PsiExpressionStatement) callStatement).getExpression();
233     }
234     else {
235       if (PsiType.VOID.equals(delegate.getReturnType())) {
236         PsiElement callStatement = factory.createStatementFromText(newName + "();", null);
237         callStatement = CodeStyleManager.getInstance(project).reformat(callStatement);
238         callStatement = body.add(callStatement);
239         callExpression = (PsiCallExpression)((PsiExpressionStatement) callStatement).getExpression();
240       }
241       else {
242         PsiElement callStatement = factory.createStatementFromText("return " + newName + "();", null);
243         callStatement = CodeStyleManager.getInstance(project).reformat(callStatement);
244         callStatement = body.add(callStatement);
245         callExpression = (PsiCallExpression)((PsiReturnStatement) callStatement).getReturnValue();
246       }
247     }
248     return callExpression;
249   }
250
251   @NotNull
252   static PsiSubstitutor calculateSubstitutor(@NotNull PsiMethod derivedMethod, @NotNull PsiMethod baseMethod) {
253     PsiSubstitutor substitutor;
254     if (derivedMethod.getManager().areElementsEquivalent(derivedMethod, baseMethod)) {
255       substitutor = PsiSubstitutor.EMPTY;
256     }
257     else {
258       PsiClass baseClass = baseMethod.getContainingClass();
259       PsiClass derivedClass = derivedMethod.getContainingClass();
260       if (baseClass != null && InheritanceUtil.isInheritorOrSelf(derivedClass, baseClass, true)) {
261         PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, derivedClass, PsiSubstitutor.EMPTY);
262         MethodSignature superMethodSignature = baseMethod.getSignature(superClassSubstitutor);
263         MethodSignature methodSignature = derivedMethod.getSignature(PsiSubstitutor.EMPTY);
264         PsiSubstitutor superMethodSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superMethodSignature);
265         substitutor = superMethodSubstitutor != null ? superMethodSubstitutor : superClassSubstitutor;
266       }
267       else {
268         substitutor = PsiSubstitutor.EMPTY;
269       }
270     }
271     return substitutor;
272   }
273 }