462de5a8c22e7d3ef700fe6b1ebee482ef3f8e6c
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / changeSignature / DetectedJavaChangeInfo.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.refactoring.changeSignature;
17
18 import com.intellij.lang.findUsages.DescriptiveNameUtil;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.command.CommandProcessor;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Comparing;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.psi.*;
26 import com.intellij.refactoring.BaseRefactoringProcessor;
27 import com.intellij.refactoring.RefactoringBundle;
28 import com.intellij.refactoring.changeSignature.inplace.InplaceChangeSignature;
29 import com.intellij.refactoring.util.CanonicalTypes;
30 import com.intellij.usageView.UsageInfo;
31 import com.intellij.util.IncorrectOperationException;
32 import com.intellij.util.VisibilityUtil;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Map;
39
40 /**
41  * User: anna
42  * Date: 11/3/11
43  */
44 class DetectedJavaChangeInfo extends JavaChangeInfoImpl {
45   private PsiMethod mySuperMethod;
46   private final String[] myModifiers;
47
48   DetectedJavaChangeInfo(@PsiModifier.ModifierConstant String newVisibility,
49                          PsiMethod method,
50                          CanonicalTypes.Type newType,
51                          @NotNull ParameterInfoImpl[] newParms,
52                          ThrownExceptionInfo[] newExceptions,
53                          String newName, String oldName, final boolean delegate) {
54     super(newVisibility, method, newName, newType, newParms, newExceptions, delegate, new HashSet<>(), new HashSet<>(), oldName);
55     final PsiParameter[] parameters = method.getParameterList().getParameters();
56     myModifiers = new String[parameters.length];
57     for (int i = 0; i < parameters.length; i++) {
58       PsiParameter parameter = parameters[i];
59       final PsiModifierList modifierList = parameter.getModifierList();
60       if (modifierList != null) {
61         final String text = modifierList.getText();
62         myModifiers[i] = text;
63       }
64     }
65   }
66
67   @Nullable
68   static DetectedJavaChangeInfo createFromMethod(PsiMethod method, final boolean delegate) {
69     final String newVisibility = VisibilityUtil.getVisibilityModifier(method.getModifierList());
70     final PsiType returnType = method.getReturnType();
71     final CanonicalTypes.Type newReturnType = returnType != null ? CanonicalTypes.createTypeWrapper(returnType) : null;
72     final ParameterInfoImpl[] parameterInfos = ParameterInfoImpl.fromMethod(method);
73     for (ParameterInfoImpl parameterInfo : parameterInfos) {
74       if (!parameterInfo.getTypeWrapper().isValid()) {
75         return null;
76       }
77     }
78     final DetectedJavaChangeInfo fromMethod = new DetectedJavaChangeInfo(newVisibility, method, newReturnType, parameterInfos, null, method.getName(), method.getName(), delegate);
79     final PsiMethod deepestSuperMethod = method.findDeepestSuperMethod();
80     if (deepestSuperMethod != null) {
81       if (!deepestSuperMethod.getManager().isInProject(deepestSuperMethod)) return null;
82     }
83     fromMethod.setSuperMethod(deepestSuperMethod);
84     return fromMethod;
85   }
86
87   @Override
88   protected void setupPropagationEnabled(PsiParameter[] parameters, ParameterInfoImpl[] newParams) {
89     isPropagationEnabled = false;
90   }
91
92   public PsiMethod getSuperMethod() {
93     if (mySuperMethod == null) {
94       return getMethod();
95     }
96     return mySuperMethod;
97   }
98
99   public void setSuperMethod(PsiMethod superMethod) {
100     mySuperMethod = superMethod;
101   }
102
103   public String[] getModifiers() {
104     return myModifiers;
105   }
106
107   @Override
108   protected boolean checkMethodEquality() {
109     return false;
110   }
111
112   @Nullable
113   DetectedJavaChangeInfo createNextInfo(final PsiMethod method, boolean delegate) {
114     final DetectedJavaChangeInfo fromMethod = createFromMethod(method, delegate);
115     if (fromMethod == null) return null;
116     if (!this.equals(fromMethod)) {
117       if (!createParametersInfo(fromMethod.newParms)) return null;
118       if ((fromMethod.newReturnType != null && getNewReturnType() == null) ||
119           (fromMethod.newReturnType == null && getNewReturnType() != null) ||
120           (fromMethod.newReturnType != null && getNewReturnType() != null && !Comparing.strEqual(getNewReturnType().getTypeText(),
121                                                                                                  fromMethod.newReturnType.getTypeText()))) {
122         final String visibility = getNewVisibility();
123         if (Comparing.strEqual(visibility, PsiModifier.PRIVATE) &&
124             !isArrayToVarargs() &&
125             !isExceptionSetOrOrderChanged() &&
126             !isExceptionSetChanged() &&
127             !isNameChanged() &&
128             !isParameterSetOrOrderChanged() &&
129             !isParameterNamesChanged() &&
130             !isParameterTypesChanged()) {
131           return null;
132         }
133       }
134
135       try {
136         final DetectedJavaChangeInfo javaChangeInfo =
137           new DetectedJavaChangeInfo(fromMethod.getNewVisibility(), getMethod(), fromMethod.newReturnType, fromMethod.newParms, getNewExceptions(), method.getName(), getOldName(), delegate) {
138             @Override
139             protected void fillOldParams(PsiMethod method) {
140               oldParameterNames = DetectedJavaChangeInfo.this.getOldParameterNames();
141               oldParameterTypes = DetectedJavaChangeInfo.this.getOldParameterTypes();
142               if (!method.isConstructor()) {
143                 try {
144                   isReturnTypeChanged = isReturnTypeChanged ||
145                                         (DetectedJavaChangeInfo.this.getNewReturnType() != null
146                                          ? !Comparing.strEqual(DetectedJavaChangeInfo.this.getNewReturnType().getTypeText(), newReturnType.getTypeText())
147                                          : newReturnType != null);
148                 }
149                 catch (IncorrectOperationException e) {
150                   isReturnTypeChanged = true;
151                 }
152               }
153
154               for (int i = 0, length = Math.min(newParms.length, oldParameterNames.length); i < length; i++) {
155                 ParameterInfoImpl parm = newParms[i];
156                 if (parm.getName().equals(oldParameterNames[i]) && parm.getTypeText().equals(oldParameterTypes[i])) {
157                   parm.oldParameterIndex = i;
158                 }
159               }
160             }
161           };
162         javaChangeInfo.setSuperMethod(getSuperMethod());
163         return javaChangeInfo;
164       }
165       catch (IncorrectOperationException e) {
166         return null;
167       }
168     }
169     return this;
170   }
171
172   ChangeSignatureProcessor createChangeSignatureProcessor(final PsiMethod method) {
173     return new ChangeSignatureProcessor(method.getProject(), new DetectedJavaChangeInfo(getNewVisibility(), getSuperMethod(),
174                                                                                         getNewReturnType(),
175                                                                                         (ParameterInfoImpl[])getNewParameters(),
176                                                                                         getNewExceptions(), getNewName(),
177                                                                                         method.getName(), false) {
178       @Override
179       protected void fillOldParams(PsiMethod method) {
180         super.fillOldParams(method);
181         oldParameterNames = DetectedJavaChangeInfo.this.getOldParameterNames();
182         oldParameterTypes = DetectedJavaChangeInfo.this.getOldParameterTypes();
183       }
184     }) {
185       @Override
186       protected void performRefactoring(@NotNull UsageInfo[] usages) {
187         super.performRefactoring(usages);
188         final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(method.getProject());
189         final PsiParameter[] parameters = method.getParameterList().getParameters();
190         for (int i = 0; i < getModifiers().length; i++) {
191           final String modifier = getModifiers()[i];
192           final PsiModifierList modifierList = parameters[i].getModifierList();
193           if (modifierList != null && !Comparing.strEqual(modifier, modifierList.getText())) {
194             final PsiModifierList newModifierList =
195               elementFactory.createParameterFromText((modifier.isEmpty() ? "" : modifier + " ") + "type name", method).getModifierList();
196             if (newModifierList != null) {
197               modifierList.replace(newModifierList);
198             }
199           }
200         }
201       }
202     };
203   }
204
205   private boolean createParametersInfo(ParameterInfoImpl[] parameterInfos) {
206     final JavaParameterInfo[] oldParameters = getNewParameters();
207     final String[] oldParameterNames = getOldParameterNames();
208     final String[] oldParameterTypes = getOldParameterTypes();
209     final Map<JavaParameterInfo, Integer> untouchedParams = new HashMap<>();
210     for (int i = 0; i < parameterInfos.length; i++) {
211       ParameterInfoImpl parameterInfo = parameterInfos[i];
212       JavaParameterInfo oldParameter = null;
213       for (JavaParameterInfo parameter : oldParameters) {
214         if (Comparing.strEqual(parameter.getName(), parameterInfo.getName()) &&
215             Comparing.strEqual(parameter.getTypeText(), parameterInfo.getTypeText())) {
216           oldParameter = parameter;
217           break;
218         }
219       }
220
221       if (oldParameter != null) {
222         parameterInfos[i] = new ParameterInfoImpl(oldParameter.getOldIndex(),
223                                                   oldParameter.getName(),
224                                                   oldParameter.getTypeWrapper(),
225                                                   null);
226         untouchedParams.put(parameterInfos[i], oldParameter.getOldIndex());
227       }
228     }
229
230     for (int i = 0; i < parameterInfos.length; i++) {
231       ParameterInfoImpl parameterInfo = parameterInfos[i];
232       if (!untouchedParams.containsKey(parameterInfo)) {
233         JavaParameterInfo oldParameter = null;
234         if (oldParameters.length > i && oldParameterNames.length > i) {
235           if (Comparing.strEqual(oldParameterNames[i], parameterInfo.getName()) ||
236               Comparing.strEqual(oldParameterTypes[i], parameterInfo.getTypeText())) {
237             if (!untouchedParams.containsValue(oldParameters[i].getOldIndex())) {
238               oldParameter = oldParameters[i];
239             }
240           }
241         }
242         final CanonicalTypes.Type typeWrapper = parameterInfo.getTypeWrapper();
243         if (!typeWrapper.isValid()) return false;
244         parameterInfos[i] = new ParameterInfoImpl(oldParameter != null ? oldParameter.getOldIndex() : -1,
245                                                   parameterInfo.getName(),
246                                                   typeWrapper,
247                                                   null);
248       }
249     }
250     return true;
251   }
252
253   boolean perform(ChangeInfo initialChangeInfo, final String oldText, boolean silently) {
254     final PsiMethod method = getSuperMethod();
255
256     Project project = initialChangeInfo.getMethod().getProject();
257     final PsiMethod currentMethod = (PsiMethod)initialChangeInfo.getMethod();
258     final TextRange signatureRange = JavaChangeSignatureDetector.getSignatureRange(currentMethod);
259     final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
260     final Document document = documentManager.getDocument(currentMethod.getContainingFile());
261     if (silently || ApplicationManager.getApplication().isUnitTestMode()) {
262       final String currentSignature = currentMethod.getContainingFile().getText().substring(signatureRange.getStartOffset(),
263                                                                                             signatureRange.getEndOffset());
264       InplaceChangeSignature.temporallyRevertChanges(JavaChangeSignatureDetector.getSignatureRange(currentMethod), document, oldText, project);
265       createChangeSignatureProcessor(method).run();
266       InplaceChangeSignature
267         .temporallyRevertChanges(JavaChangeSignatureDetector.getSignatureRange(currentMethod), document, currentSignature, project);
268       return true;
269     }
270     final JavaMethodDescriptor descriptor = new JavaMethodDescriptor(currentMethod) {
271       @Override
272       public String getReturnTypeText() {
273         return getNewReturnType().getTypeText();
274       }
275     };
276     final JavaChangeSignatureDialog dialog =
277       new JavaChangeSignatureDialog(method.getProject(), descriptor, true, method) {
278         protected BaseRefactoringProcessor createRefactoringProcessor() {
279           return createChangeSignatureProcessor(method);
280         }
281
282         @Override
283         protected void invokeRefactoring(final BaseRefactoringProcessor processor) {
284           CommandProcessor.getInstance().executeCommand(myProject, () -> {
285             InplaceChangeSignature.temporallyRevertChanges(JavaChangeSignatureDetector.getSignatureRange(currentMethod), document, oldText, project);
286             doRefactor(processor);
287           }, RefactoringBundle.message("changing.signature.of.0", DescriptiveNameUtil.getDescriptiveName(currentMethod)), null);
288         }
289
290         private void doRefactor(BaseRefactoringProcessor processor) {
291           super.invokeRefactoring(processor);
292         }
293       };
294     return dialog.showAndGet();
295   }
296 }