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