inplace change signature: delegate in template
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / DefineParamsDefaultValueAction.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.FileModificationService;
19 import com.intellij.codeInsight.generation.ClassMember;
20 import com.intellij.codeInsight.hint.HintManager;
21 import com.intellij.codeInsight.intention.LowPriorityAction;
22 import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
23 import com.intellij.codeInsight.intention.impl.ParameterClassMember;
24 import com.intellij.codeInsight.template.Template;
25 import com.intellij.codeInsight.template.TemplateBuilderImpl;
26 import com.intellij.codeInsight.template.impl.TextExpression;
27 import com.intellij.icons.AllIcons;
28 import com.intellij.ide.util.MemberChooser;
29 import com.intellij.lang.java.JavaLanguage;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.editor.RangeMarker;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.Iconable;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.psi.*;
38 import com.intellij.psi.codeStyle.CodeStyleManager;
39 import com.intellij.psi.util.PsiTreeUtil;
40 import com.intellij.psi.util.PsiUtil;
41 import com.intellij.refactoring.util.RefactoringUtil;
42 import com.intellij.util.ArrayUtil;
43 import com.intellij.util.IncorrectOperationException;
44 import com.intellij.util.containers.ContainerUtil;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import javax.swing.*;
49 import java.util.Arrays;
50 import java.util.HashSet;
51 import java.util.List;
52
53 /**
54  * User: anna
55  * Date: 8/2/12
56  */
57 public class DefineParamsDefaultValueAction extends PsiElementBaseIntentionAction implements Iconable, LowPriorityAction {
58   private static final Logger LOG = Logger.getInstance(DefineParamsDefaultValueAction.class);
59
60   @Override
61   public boolean startInWriteAction() {
62     return false;
63   }
64
65   @NotNull
66   @Override
67   public String getFamilyName() {
68     return "Generate overloaded method with default parameter values";
69   }
70
71   @Override
72   public Icon getIcon(int flags) {
73     return AllIcons.Actions.RefactoringBulb;
74   }
75
76   @Override
77   public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
78     if (!JavaLanguage.INSTANCE.equals(element.getLanguage())) {
79       return false;
80     }
81     final PsiElement parent = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiCodeBlock.class);
82     if (!(parent instanceof PsiMethod)) {
83       return false;
84     }
85     final PsiMethod method = (PsiMethod)parent;
86     final PsiParameterList parameterList = method.getParameterList();
87     if (parameterList.getParametersCount() == 0) {
88       return false;
89     }
90     final PsiClass containingClass = method.getContainingClass();
91     if (containingClass == null || (containingClass.isInterface() && !PsiUtil.isLanguageLevel8OrHigher(method))) {
92       return false;
93     }
94     setText("Generate overloaded " + (method.isConstructor() ? "constructor" : "method") + " with default parameter values");
95     return true;
96   }
97
98   @Override
99   public void invoke(@NotNull final Project project, final Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
100     final PsiParameter[] parameters = getParams(element);
101     if (parameters == null || parameters.length == 0) return;
102     final PsiMethod method = (PsiMethod)parameters[0].getDeclarationScope();
103     final PsiMethod methodPrototype = generateMethodPrototype(method, parameters);
104     final PsiClass containingClass = method.getContainingClass();
105     if (containingClass == null) return;
106     final PsiMethod existingMethod = containingClass.findMethodBySignature(methodPrototype, false);
107     if (existingMethod != null) {
108       editor.getCaretModel().moveToOffset(existingMethod.getTextOffset());
109       HintManager.getInstance().showErrorHint(editor, (existingMethod.isConstructor() ? "Constructor" : "Method") +
110                                                       " with the chosen signature already exists");
111       return;
112     }
113
114     if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
115
116     Runnable runnable = () -> {
117       final PsiMethod prototype = (PsiMethod)containingClass.addBefore(methodPrototype, method);
118       RefactoringUtil.fixJavadocsForParams(prototype, new HashSet<>(Arrays.asList(prototype.getParameterList().getParameters())));
119
120
121       PsiCodeBlock body = prototype.getBody();
122       final String callArgs =
123         "(" + StringUtil.join(method.getParameterList().getParameters(), psiParameter -> {
124           if (ArrayUtil.find(parameters, psiParameter) > -1) return "IntelliJIDEARulezzz";
125           return psiParameter.getName();
126         }, ",") + ");";
127       final String methodCall;
128       if (method.getReturnType() == null) {
129         methodCall = "this";
130       } else if (!PsiType.VOID.equals(method.getReturnType())) {
131         methodCall = "return " + method.getName();
132       } else {
133         methodCall = method.getName();
134       }
135       LOG.assertTrue(body != null);
136       body.add(JavaPsiFacade.getElementFactory(project).createStatementFromText(methodCall + callArgs, method));
137       body = (PsiCodeBlock)CodeStyleManager.getInstance(project).reformat(body);
138       final PsiStatement stmt = body.getStatements()[0];
139       final PsiExpression expr;
140       if (stmt instanceof PsiReturnStatement) {
141         expr = ((PsiReturnStatement)stmt).getReturnValue();
142       } else if (stmt instanceof PsiExpressionStatement) {
143         expr = ((PsiExpressionStatement)stmt).getExpression();
144       }
145       else {
146         expr = null;
147       }
148       if (expr instanceof PsiMethodCallExpression) {
149         PsiExpression[] args = ((PsiMethodCallExpression)expr).getArgumentList().getExpressions();
150         PsiExpression[] toDefaults = ContainerUtil.map2Array(parameters, PsiExpression.class, (parameter -> args[method.getParameterList().getParameterIndex(parameter)]));
151         startTemplate(project, editor, toDefaults, prototype);
152       }
153     };
154     if (startInWriteAction()) {
155       runnable.run();
156     } else {
157       ApplicationManager.getApplication().runWriteAction(runnable);
158     }
159   }
160
161   public static void startTemplate(@NotNull Project project,
162                                    Editor editor,
163                                    PsiExpression[] argsToBeDelegated,
164                                    PsiMethod delegateMethod) {
165     TemplateBuilderImpl builder = new TemplateBuilderImpl(delegateMethod);
166     RangeMarker rangeMarker = editor.getDocument().createRangeMarker(delegateMethod.getTextRange());
167     for (final PsiExpression exprToBeDefault  : argsToBeDelegated) {
168       builder.replaceElement(exprToBeDefault, new TextExpression(""));
169     }
170     Template template = builder.buildTemplate();
171     editor.getCaretModel().moveToOffset(rangeMarker.getStartOffset());
172
173     PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
174     editor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
175
176     rangeMarker.dispose();
177
178     CreateFromUsageBaseFix.startTemplate(editor, template, project);
179   }
180
181   @Nullable
182   protected PsiParameter[] getParams(PsiElement element) {
183     final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
184     assert method != null;
185     final PsiParameter[] parameters = method.getParameterList().getParameters();
186     if (parameters.length == 1) {
187       return parameters;
188     }
189     final ParameterClassMember[] members = new ParameterClassMember[parameters.length];
190     for (int i = 0; i < members.length; i++) {
191       members[i] = new ParameterClassMember(parameters[i]);
192     }
193     final PsiParameter selectedParam = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
194     final int idx = selectedParam != null ? ArrayUtil.find(parameters, selectedParam) : -1;
195     if (ApplicationManager.getApplication().isUnitTestMode()) {
196       return idx >= 0 ? new PsiParameter[] {selectedParam} : null;
197     }
198     final MemberChooser<ParameterClassMember> chooser =
199       new MemberChooser<>(members, false, true, element.getProject());
200     if (idx >= 0) {
201       chooser.selectElements(new ClassMember[] {members[idx]});
202     }
203     else {
204       chooser.selectElements(members);
205     }
206     chooser.setTitle("Choose Default Value Parameters");
207     chooser.setCopyJavadocVisible(false);
208     if (chooser.showAndGet()) {
209       final List<ParameterClassMember> elements = chooser.getSelectedElements();
210       if (elements != null) {
211         PsiParameter[] params = new PsiParameter[elements.size()];
212         for (int i = 0; i < params.length; i++) {
213           params[i] = elements.get(i).getParameter();
214         }
215         return params;
216       }
217     }
218     return null;
219   }
220
221   private static PsiMethod generateMethodPrototype(PsiMethod method, PsiParameter... params) {
222     final PsiMethod prototype = (PsiMethod)method.copy();
223     final PsiCodeBlock body = prototype.getBody();
224     final PsiCodeBlock emptyBody = JavaPsiFacade.getElementFactory(method.getProject()).createMethodFromText("void foo(){}", prototype).getBody();
225     assert emptyBody != null;
226     if (body != null) {
227       body.replace(emptyBody);
228     } else {
229       prototype.getModifierList().setModifierProperty(PsiModifier.ABSTRACT, false);
230       prototype.addBefore(emptyBody, null);
231     }
232
233     final PsiClass aClass = method.getContainingClass();
234     if (aClass != null && aClass.isInterface() && !method.hasModifierProperty(PsiModifier.STATIC)) {
235       prototype.getModifierList().setModifierProperty(PsiModifier.DEFAULT, true);
236     }
237
238     final PsiParameterList parameterList = method.getParameterList();
239     Arrays.sort(params, (p1, p2) -> {
240       final int parameterIndex1 = parameterList.getParameterIndex(p1);
241       final int parameterIndex2 = parameterList.getParameterIndex(p2);
242       return parameterIndex1 > parameterIndex2 ? -1 : 1;
243     });
244
245     for (PsiParameter param : params) {
246       final int parameterIndex = parameterList.getParameterIndex(param);
247       prototype.getParameterList().getParameters()[parameterIndex].delete();
248     }
249     return prototype;
250   }
251 }