2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.daemon.impl.quickfix;
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;
49 import java.util.Arrays;
50 import java.util.HashSet;
51 import java.util.List;
57 public class DefineParamsDefaultValueAction extends PsiElementBaseIntentionAction implements Iconable, LowPriorityAction {
58 private static final Logger LOG = Logger.getInstance(DefineParamsDefaultValueAction.class);
61 public boolean startInWriteAction() {
67 public String getFamilyName() {
68 return "Generate overloaded method with default parameter values";
72 public Icon getIcon(int flags) {
73 return AllIcons.Actions.RefactoringBulb;
77 public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
78 if (!JavaLanguage.INSTANCE.equals(element.getLanguage())) {
81 final PsiElement parent = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiCodeBlock.class);
82 if (!(parent instanceof PsiMethod)) {
85 final PsiMethod method = (PsiMethod)parent;
86 final PsiParameterList parameterList = method.getParameterList();
87 if (parameterList.getParametersCount() == 0) {
90 final PsiClass containingClass = method.getContainingClass();
91 if (containingClass == null || (containingClass.isInterface() && !PsiUtil.isLanguageLevel8OrHigher(method))) {
94 setText("Generate overloaded " + (method.isConstructor() ? "constructor" : "method") + " with default parameter values");
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");
114 if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
116 Runnable runnable = () -> {
117 final PsiMethod prototype = (PsiMethod)containingClass.addBefore(methodPrototype, method);
118 RefactoringUtil.fixJavadocsForParams(prototype, new HashSet<>(Arrays.asList(prototype.getParameterList().getParameters())));
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();
127 final String methodCall;
128 if (method.getReturnType() == null) {
130 } else if (!PsiType.VOID.equals(method.getReturnType())) {
131 methodCall = "return " + method.getName();
133 methodCall = method.getName();
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();
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);
154 if (startInWriteAction()) {
157 ApplicationManager.getApplication().runWriteAction(runnable);
161 public static void startTemplate(@NotNull Project project,
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(""));
170 Template template = builder.buildTemplate();
171 editor.getCaretModel().moveToOffset(rangeMarker.getStartOffset());
173 PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
174 editor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
176 rangeMarker.dispose();
178 CreateFromUsageBaseFix.startTemplate(editor, template, project);
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) {
189 final ParameterClassMember[] members = new ParameterClassMember[parameters.length];
190 for (int i = 0; i < members.length; i++) {
191 members[i] = new ParameterClassMember(parameters[i]);
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;
198 final MemberChooser<ParameterClassMember> chooser =
199 new MemberChooser<>(members, false, true, element.getProject());
201 chooser.selectElements(new ClassMember[] {members[idx]});
204 chooser.selectElements(members);
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();
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;
227 body.replace(emptyBody);
229 prototype.getModifierList().setModifierProperty(PsiModifier.ABSTRACT, false);
230 prototype.addBefore(emptyBody, null);
233 final PsiClass aClass = method.getContainingClass();
234 if (aClass != null && aClass.isInterface() && !method.hasModifierProperty(PsiModifier.STATIC)) {
235 prototype.getModifierList().setModifierProperty(PsiModifier.DEFAULT, true);
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;
245 for (PsiParameter param : params) {
246 final int parameterIndex = parameterList.getParameterIndex(param);
247 prototype.getParameterList().getParameters()[parameterIndex].delete();