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