constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineMethodHandler.java
1
2 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
3 package com.intellij.refactoring.inline;
4
5 import com.intellij.codeInsight.TargetElementUtil;
6 import com.intellij.lang.java.JavaLanguage;
7 import com.intellij.openapi.editor.Editor;
8 import com.intellij.openapi.project.Project;
9 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
10 import com.intellij.openapi.vfs.VirtualFile;
11 import com.intellij.psi.*;
12 import com.intellij.psi.util.PsiTreeUtil;
13 import com.intellij.refactoring.HelpID;
14 import com.intellij.refactoring.RefactoringBundle;
15 import com.intellij.refactoring.util.CommonRefactoringUtil;
16 import com.intellij.refactoring.util.InlineUtil;
17 import com.intellij.refactoring.util.RefactoringUtil;
18 import org.jetbrains.annotations.Nullable;
19
20 import java.util.Collections;
21 import java.util.function.Supplier;
22
23 public class InlineMethodHandler extends JavaInlineActionHandler {
24   private static final String REFACTORING_NAME = RefactoringBundle.message("inline.method.title");
25
26   private InlineMethodHandler() {
27   }
28
29   @Override
30   public boolean canInlineElement(PsiElement element) {
31     return element instanceof PsiMethod && element.getNavigationElement() instanceof PsiMethod && element.getLanguage() == JavaLanguage.INSTANCE;
32   }
33
34   @Override
35   public void inlineElement(final Project project, Editor editor, PsiElement element) {
36     performInline(project, editor, (PsiMethod)element.getNavigationElement(), false);
37   }
38
39   /**
40    * Try to inline method, displaying UI or error message if necessary
41    * @param project project where method is declared
42    * @param editor active editor where cursor might point to the call site 
43    * @param method method to be inlined
44    * @param allowInlineThisOnly if true, only call-site at cursor will be suggested 
45    *                            (in this case caller must check that cursor points to the valid reference)
46    */
47   public static void performInline(Project project, Editor editor, PsiMethod method, boolean allowInlineThisOnly) {
48     PsiReference reference = editor != null ? TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset()) : null;
49
50     PsiCodeBlock methodBody = method.getBody();
51     Supplier<PsiCodeBlock> specialization = InlineMethodSpecialization.forReference(reference);
52     if (specialization != null) {
53       allowInlineThisOnly = true;
54       methodBody = specialization.get();
55     }
56
57     if (methodBody == null){
58       String message;
59       if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
60         message = RefactoringBundle.message("refactoring.cannot.be.applied.to.abstract.methods", REFACTORING_NAME);
61       }
62       else if (method.hasModifierProperty(PsiModifier.NATIVE)) {
63         message = RefactoringBundle.message("refactoring.cannot.be.applied.to.native.methods", REFACTORING_NAME);
64       }
65       else {
66         message = RefactoringBundle.message("refactoring.cannot.be.applied.no.sources.attached", REFACTORING_NAME);
67       }
68       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
69       return;
70     }
71
72     if (reference != null) {
73       final PsiElement refElement = reference.getElement();
74       if (!isJavaLanguage(refElement.getLanguage())) {
75         String message = RefactoringBundle
76           .message("refactoring.is.not.supported.for.language", "Inline of Java method", refElement.getLanguage().getDisplayName());
77         CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
78         return;
79       }
80     }
81
82     if (reference == null && checkRecursive(method)) {
83       String message = RefactoringBundle.message("refactoring.is.not.supported.for.recursive.methods", REFACTORING_NAME);
84       CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
85       return;
86     }
87
88     if (reference != null) {
89       final String errorMessage = InlineMethodProcessor.checkUnableToInsertCodeBlock(methodBody, reference.getElement());
90       if (errorMessage != null) {
91         CommonRefactoringUtil.showErrorHint(project, editor, errorMessage, REFACTORING_NAME, HelpID.INLINE_METHOD);
92         return;
93       }
94     }
95
96     if (method.isConstructor()) {
97       if (method.isVarArgs()) {
98         String message = RefactoringBundle.message("refactoring.cannot.be.applied.to.vararg.constructors", REFACTORING_NAME);
99         CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_CONSTRUCTOR);
100         return;
101       }
102       final boolean chainingConstructor = InlineUtil.isChainingConstructor(method);
103       if (!chainingConstructor) {
104         if (!isThisReference(reference)) {
105           String message = RefactoringBundle.message("refactoring.cannot.be.applied.to.inline.non.chaining.constructors", REFACTORING_NAME);
106           CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_CONSTRUCTOR);
107           return;
108         }
109         allowInlineThisOnly = true;
110       }
111       if (reference != null) {
112         final PsiElement refElement = reference.getElement();
113         PsiCall constructorCall = refElement instanceof PsiJavaCodeReferenceElement ? RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)refElement) : null;
114         if (constructorCall == null || !method.equals(constructorCall.resolveMethod())) reference = null;
115       }
116     }
117     else {
118       if (reference != null && !method.getManager().areElementsEquivalent(method, reference.resolve())) {
119         reference = null;
120       }
121     }
122
123     if (reference != null && PsiTreeUtil.getParentOfType(reference.getElement(), PsiImportStaticStatement.class) != null) {
124       reference = null;
125     }
126
127     final boolean invokedOnReference = reference != null;
128     if (!invokedOnReference) {
129       final VirtualFile vFile = method.getContainingFile().getVirtualFile();
130       ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(Collections.singletonList(vFile));
131     }
132
133     PsiJavaCodeReferenceElement refElement = null;
134     if (reference != null) {
135       final PsiElement referenceElement = reference.getElement();
136       if (referenceElement instanceof PsiJavaCodeReferenceElement) {
137         refElement = (PsiJavaCodeReferenceElement)referenceElement;
138       }
139     }
140     InlineMethodDialog dialog = new InlineMethodDialog(project, method, refElement, editor, allowInlineThisOnly);
141     dialog.show();
142   }
143
144   public static boolean checkRecursive(PsiMethod method) {
145     return checkCalls(method.getBody(), method);
146   }
147
148   private static boolean checkCalls(PsiElement scope, PsiMethod method) {
149     if (scope instanceof PsiMethodCallExpression){
150       PsiMethod refMethod = (PsiMethod)((PsiMethodCallExpression)scope).getMethodExpression().resolve();
151       if (method.equals(refMethod)) return true;
152     }
153
154     if (scope instanceof PsiMethodReferenceExpression) {
155       if (method.equals(((PsiMethodReferenceExpression)scope).resolve())) return true;
156     }
157
158     for(PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()){
159       if (checkCalls(child, method)) return true;
160     }
161
162     return false;
163   }
164
165   public static boolean isThisReference(PsiReference reference) {
166     if (reference != null) {
167       final PsiElement referenceElement = reference.getElement();
168       if (referenceElement instanceof PsiJavaCodeReferenceElement &&
169           referenceElement.getParent() instanceof PsiMethodCallExpression &&
170           "this".equals(((PsiJavaCodeReferenceElement)referenceElement).getReferenceName())) {
171         return true;
172       }
173     }
174     return false;
175   }
176
177   @Nullable
178   @Override
179   public String getActionName(PsiElement element) {
180     return REFACTORING_NAME + "...";
181   }
182 }