constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / CreatePropertyFromUsageFix.java
1 // 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.
2 package com.intellij.codeInsight.daemon.impl.quickfix;
3
4 import com.intellij.codeInsight.CodeInsightUtilCore;
5 import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
6 import com.intellij.codeInsight.daemon.QuickFixBundle;
7 import com.intellij.codeInsight.generation.GenerateMembersUtil;
8 import com.intellij.codeInsight.intention.HighPriorityAction;
9 import com.intellij.codeInsight.intention.impl.TypeExpression;
10 import com.intellij.codeInsight.lookup.LookupElement;
11 import com.intellij.codeInsight.template.*;
12 import com.intellij.codeInsight.template.impl.TemplateState;
13 import com.intellij.openapi.application.ApplicationManager;
14 import com.intellij.openapi.diagnostic.Logger;
15 import com.intellij.openapi.editor.Editor;
16 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
17 import com.intellij.openapi.project.Project;
18 import com.intellij.openapi.util.TextRange;
19 import com.intellij.psi.*;
20 import com.intellij.psi.codeStyle.CodeStyleManager;
21 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
22 import com.intellij.psi.codeStyle.VariableKind;
23 import com.intellij.psi.util.PointersKt;
24 import com.intellij.psi.util.PropertyUtilBase;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.util.PsiUtil;
27 import com.intellij.refactoring.util.RefactoringUtil;
28 import com.intellij.util.IncorrectOperationException;
29 import com.intellij.util.containers.ContainerUtil;
30 import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33
34 import java.util.ArrayList;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Set;
38
39 /**
40  * @author ven
41  */
42 @Deprecated
43 @ScheduledForRemoval(inVersion = "2019.3")
44 public class CreatePropertyFromUsageFix extends CreateFromUsageBaseFix implements HighPriorityAction {
45   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreatePropertyFromUsageFix");
46   @NonNls private static final String FIELD_VARIABLE = "FIELD_NAME_VARIABLE";
47   @NonNls private static final String TYPE_VARIABLE = "FIELD_TYPE_VARIABLE";
48   @NonNls private static final String GET_PREFIX = "get";
49   @NonNls private static final String IS_PREFIX = "is";
50   @NonNls private static final String SET_PREFIX = "set";
51
52   public CreatePropertyFromUsageFix(@NotNull PsiMethodCallExpression methodCall) {
53     myMethodCall = methodCall;
54   }
55
56   protected final PsiMethodCallExpression myMethodCall;
57
58   @Override
59   @NotNull
60   public String getFamilyName() {
61     return QuickFixBundle.message("create.property.from.usage.family");
62   }
63
64   @Override
65   protected PsiElement getElement() {
66     if (!myMethodCall.isValid() || !canModify(myMethodCall)) return null;
67     return myMethodCall;
68   }
69
70   @Override
71   protected boolean isAvailableImpl(int offset) {
72     if (CreateMethodFromUsageFix.hasErrorsInArgumentList(myMethodCall)) return false;
73     String methodName = myMethodCall.getMethodExpression().getReferenceName();
74     LOG.assertTrue(methodName != null);
75     String propertyName = PropertyUtilBase.getPropertyName(methodName);
76     if (propertyName == null || propertyName.isEmpty()) return false;
77
78     String getterOrSetter = null;
79     if (methodName.startsWith(GET_PREFIX) || methodName.startsWith(IS_PREFIX)) {
80       if (!myMethodCall.getArgumentList().isEmpty()) return false;
81       getterOrSetter = QuickFixBundle.message("create.getter");
82     }
83     else if (methodName.startsWith(SET_PREFIX)) {
84       if (myMethodCall.getArgumentList().getExpressionCount() != 1) return false;
85       getterOrSetter = QuickFixBundle.message("create.setter");
86     }
87     else {
88       LOG.error("Internal error in create property intention");
89     }
90
91     List<PsiClass> classes = getTargetClasses(myMethodCall);
92     if (classes.isEmpty()) return false;
93
94     if (!checkTargetClasses(classes, methodName)) return false;
95
96     for (PsiClass aClass : classes) {
97       if (!aClass.isInterface()) {
98         setText(getterOrSetter);
99         return true;
100       }
101     }
102
103     return false;
104   }
105
106   protected boolean checkTargetClasses(List<? extends PsiClass> classes, String methodName) {
107     return true;
108   }
109
110   static class FieldExpression extends Expression {
111     private final String myDefaultFieldName;
112     private final SmartPsiElementPointer<PsiField> myField;
113     private final SmartPsiElementPointer<PsiClass> myClass;
114     private final List<SmartTypePointer> myExpectedTypes;
115
116     FieldExpression(final PsiField field, PsiClass aClass, PsiType[] expectedTypes) {
117       myField = PointersKt.createSmartPointer(field);
118       myClass = PointersKt.createSmartPointer(aClass);
119       myExpectedTypes = ContainerUtil.map(expectedTypes, type -> SmartTypePointerManager.getInstance(field.getProject()).createSmartTypePointer(type));
120       myDefaultFieldName = field.getName();
121     }
122
123     @Override
124     public Result calculateResult(ExpressionContext context) {
125       return new TextResult(myDefaultFieldName);
126     }
127
128     @Override
129     public Result calculateQuickResult(ExpressionContext context) {
130       return new TextResult(myDefaultFieldName);
131     }
132
133     @Override
134     public LookupElement[] calculateLookupItems(ExpressionContext context) {
135       PsiField field = myField.getElement();
136       PsiClass psiClass = myClass.getElement();
137       if (field == null || psiClass == null) return LookupElement.EMPTY_ARRAY;
138
139       Set<LookupElement> set = new LinkedHashSet<>();
140       set.add(JavaLookupElementBuilder.forField(field).withTypeText(field.getType().getPresentableText()));
141       for (PsiField otherField : psiClass.getFields()) {
142         if (!myDefaultFieldName.equals(otherField.getName())) {
143           PsiType otherType = otherField.getType();
144           for (SmartTypePointer pointer : myExpectedTypes) {
145             if (otherType.equals(pointer.getType())) {
146               set.add(JavaLookupElementBuilder.forField(otherField).withTypeText(otherType.getPresentableText()));
147             }
148           }
149         }
150       }
151
152       if (set.size() < 2) return null;
153       return set.toArray(LookupElement.EMPTY_ARRAY);
154     }
155   }
156
157   @Override
158   @NotNull
159   protected List<PsiClass> getTargetClasses(PsiElement element) {
160     List<PsiClass> all = super.getTargetClasses(element);
161     if (all.isEmpty()) return all;
162
163     List<PsiClass> nonInterfaces = new ArrayList<>();
164     for (PsiClass aClass : all) {
165       if (!aClass.isInterface()) nonInterfaces.add(aClass);
166     }
167     return nonInterfaces;
168   }
169
170   @Override
171   protected void invokeImpl(PsiClass targetClass) {
172     PsiManager manager = myMethodCall.getManager();
173     final Project project = manager.getProject();
174     PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject());
175
176     boolean isStatic = false;
177     PsiExpression qualifierExpression = myMethodCall.getMethodExpression().getQualifierExpression();
178     if (qualifierExpression != null) {
179       PsiReference reference = qualifierExpression.getReference();
180       if (reference != null) {
181         isStatic = reference.resolve() instanceof PsiClass;
182       }
183     }
184     else {
185       PsiMethod method = PsiTreeUtil.getParentOfType(myMethodCall, PsiMethod.class);
186       if (method != null) {
187         isStatic = method.hasModifierProperty(PsiModifier.STATIC);
188       }
189     }
190     String fieldName = getVariableName(myMethodCall, isStatic);
191     LOG.assertTrue(fieldName != null);
192     String callText = myMethodCall.getMethodExpression().getReferenceName();
193     LOG.assertTrue(callText != null, myMethodCall.getMethodExpression());
194     PsiType[] expectedTypes;
195     PsiType type;
196     PsiField field = targetClass.findFieldByName(fieldName, true);
197     if (callText.startsWith(GET_PREFIX)) {
198       expectedTypes = field != null ? new PsiType[]{field.getType()} : CreateFromUsageUtils.guessType(myMethodCall, false);
199       type = expectedTypes[0];
200     }
201     else if (callText.startsWith(IS_PREFIX)) {
202       type = PsiType.BOOLEAN;
203       expectedTypes = new PsiType[]{type};
204     }
205     else {
206       type = RefactoringUtil.getTypeByExpression(myMethodCall.getArgumentList().getExpressions()[0]);
207       if (type == null || PsiType.NULL.equals(type)) type = PsiType.getJavaLangObject(manager, myMethodCall.getResolveScope());
208       expectedTypes = new PsiType[]{type};
209     }
210
211     positionCursor(project, targetClass.getContainingFile(), targetClass);
212
213     IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
214
215     if (field == null) {
216       field = factory.createField(fieldName, type);
217       PsiUtil.setModifierProperty(field, PsiModifier.STATIC, isStatic);
218     }
219     PsiMethod accessor;
220     PsiElement fieldReference;
221     PsiElement typeReference;
222     PsiCodeBlock body;
223     if (callText.startsWith(GET_PREFIX) || callText.startsWith(IS_PREFIX)) {
224       accessor = (PsiMethod)targetClass.add(GenerateMembersUtil.generateSimpleGetterPrototype(field));
225       body = accessor.getBody();
226       LOG.assertTrue(body != null, accessor.getText());
227       fieldReference = ((PsiReturnStatement)body.getStatements()[0]).getReturnValue();
228       typeReference = accessor.getReturnTypeElement();
229     }
230     else {
231       accessor = (PsiMethod)targetClass.add(GenerateMembersUtil.generateSimpleSetterPrototype(field, targetClass));
232       body = accessor.getBody();
233       LOG.assertTrue(body != null, accessor.getText());
234       PsiAssignmentExpression expr = (PsiAssignmentExpression)((PsiExpressionStatement)body.getStatements()[0]).getExpression();
235       fieldReference = ((PsiReferenceExpression)expr.getLExpression()).getReferenceNameElement();
236       typeReference = accessor.getParameterList().getParameters()[0].getTypeElement();
237     }
238     accessor.setName(callText);
239     PsiUtil.setModifierProperty(accessor, PsiModifier.STATIC, isStatic);
240
241     TemplateBuilderImpl builder = new TemplateBuilderImpl(accessor);
242     builder.replaceElement(typeReference, TYPE_VARIABLE, new TypeExpression(project, expectedTypes), true);
243     builder.replaceElement(fieldReference, FIELD_VARIABLE, new FieldExpression(field, targetClass, expectedTypes), true);
244     builder.setEndVariableAfter(body.getLBrace());
245
246     accessor = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(accessor);
247     LOG.assertTrue(accessor != null);
248     targetClass = accessor.getContainingClass();
249     LOG.assertTrue(targetClass != null);
250     Template template = builder.buildTemplate();
251     TextRange textRange = accessor.getTextRange();
252     final PsiFile file = targetClass.getContainingFile();
253     final Editor editor = positionCursor(project, targetClass.getContainingFile(), accessor);
254     if (editor == null) return;
255     editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset());
256     editor.getCaretModel().moveToOffset(textRange.getStartOffset());
257
258     final boolean isStatic1 = isStatic;
259     startTemplate(editor, template, project, new TemplateEditingAdapter() {
260       @Override
261       public void beforeTemplateFinished(@NotNull final TemplateState state, Template template) {
262         ApplicationManager.getApplication().runWriteAction(() -> {
263           String fieldName1 = state.getVariableValue(FIELD_VARIABLE).getText();
264           if (!PsiNameHelper.getInstance(project).isIdentifier(fieldName1)) return;
265           String fieldType = state.getVariableValue(TYPE_VARIABLE).getText();
266
267           PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
268           PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
269           if (aClass == null) return;
270           PsiField field1 = aClass.findFieldByName(fieldName1, true);
271           if (field1 != null){
272             CreatePropertyFromUsageFix.this.beforeTemplateFinished(aClass, field1);
273             return;
274           }
275           PsiElementFactory factory1 = JavaPsiFacade.getElementFactory(aClass.getProject());
276           try {
277             PsiType type1 = factory1.createTypeFromText(fieldType, aClass);
278             try {
279               field1 = factory1.createField(fieldName1, type1);
280               field1 = (PsiField)aClass.add(field1);
281               PsiUtil.setModifierProperty(field1, PsiModifier.STATIC, isStatic1);
282               CreatePropertyFromUsageFix.this.beforeTemplateFinished(aClass, field1);
283             }
284             catch (IncorrectOperationException e) {
285               LOG.error(e);
286             }
287           }
288           catch (IncorrectOperationException e) {
289           }
290         });
291       }
292
293       @Override
294       public void templateFinished(@NotNull Template template, boolean brokenOff) {
295         PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
296         final int offset = editor.getCaretModel().getOffset();
297         final PsiMethod generatedMethod = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethod.class, false);
298         if (generatedMethod != null) {
299           ApplicationManager.getApplication().runWriteAction(() -> {
300             CodeStyleManager.getInstance(project).reformat(generatedMethod);
301           });
302         }
303       }
304     });
305   }
306
307   protected void beforeTemplateFinished(PsiClass aClass, PsiField field) {
308     if (myMethodCall.isValid()) {
309       positionCursor(myMethodCall.getProject(), myMethodCall.getContainingFile(), myMethodCall);
310     }
311   }
312
313   private static String getVariableName(PsiMethodCallExpression methodCall, boolean isStatic) {
314     JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(methodCall.getProject());
315     String methodName = methodCall.getMethodExpression().getReferenceName();
316     String propertyName = PropertyUtilBase.getPropertyName(methodName);
317     if (propertyName != null && !propertyName.isEmpty()) {
318       VariableKind kind = isStatic ? VariableKind.STATIC_FIELD : VariableKind.FIELD;
319       return codeStyleManager.propertyNameToVariableName(propertyName, kind);
320     }
321
322     return null;
323   }
324
325   @Override
326   protected boolean isValidElement(PsiElement element) {
327     PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element;
328     return methodCall.getMethodExpression().resolve() != null;
329   }
330 }