Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / InitializeFinalFieldInConstructorFix.java
1 // Copyright 2000-2017 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.FileModificationService;
5 import com.intellij.codeInsight.daemon.QuickFixBundle;
6 import com.intellij.codeInsight.generation.PsiMethodMember;
7 import com.intellij.codeInsight.intention.IntentionAction;
8 import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
9 import com.intellij.codeInsight.lookup.LookupElement;
10 import com.intellij.ide.util.MemberChooser;
11 import com.intellij.openapi.application.ApplicationManager;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Document;
14 import com.intellij.openapi.editor.Editor;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.psi.*;
17 import com.intellij.psi.codeStyle.CodeStyleManager;
18 import com.intellij.psi.search.LocalSearchScope;
19 import com.intellij.psi.search.searches.ReferencesSearch;
20 import com.intellij.psi.util.PsiTreeUtil;
21 import com.intellij.psi.util.PsiUtil;
22 import com.intellij.util.IncorrectOperationException;
23 import com.intellij.util.ObjectUtils;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26
27 import java.util.*;
28
29 public class InitializeFinalFieldInConstructorFix implements IntentionAction {
30   private static final Logger LOG = Logger.getInstance(InitializeFinalFieldInConstructorFix.class);
31   private final PsiField myField;
32
33   public InitializeFinalFieldInConstructorFix(@NotNull PsiField field) {
34     myField = field;
35   }
36
37   @NotNull
38   @Override
39   public String getText() {
40     return QuickFixBundle.message("initialize.final.field.in.constructor.name");
41   }
42
43   @NotNull
44   @Override
45   public String getFamilyName() {
46     return getText();
47   }
48
49   @Override
50   public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
51     if (!myField.isValid() || myField.hasModifierProperty(PsiModifier.STATIC) || myField.hasInitializer()) {
52       return false;
53     }
54
55     final PsiClass containingClass = myField.getContainingClass();
56     if (containingClass == null || containingClass.getName() == null){
57       return false;
58     }
59
60     final PsiManager manager = myField.getManager();
61     return manager != null && BaseIntentionAction.canModify(myField);
62   }
63
64   @Override
65   public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
66     if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
67
68     final PsiClass myClass = myField.getContainingClass();
69     if (myClass == null) {
70       return;
71     }
72     if (myClass.getConstructors().length == 0) {
73       createDefaultConstructor(myClass, project, editor, file);
74     }
75
76     final List<PsiMethod> constructors = choose(filterIfFieldAlreadyAssigned(myField, myClass.getConstructors()), project);
77
78     ApplicationManager.getApplication().runWriteAction(() -> addFieldInitialization(constructors, myField, project, editor));
79   }
80
81   private static void addFieldInitialization(@NotNull List<PsiMethod> constructors,
82                                              @NotNull PsiField field,
83                                              @NotNull Project project,
84                                              @Nullable Editor editor) {
85     if (constructors.isEmpty()) return;
86
87     final LookupElement[] suggestedInitializers = AddVariableInitializerFix.suggestInitializer(field);
88
89     final List<PsiExpression> rExpressions = new ArrayList<>(constructors.size());
90     for (PsiMethod constructor : constructors) {
91       rExpressions.add(addFieldInitialization(constructor, suggestedInitializers, field, project));
92     }
93     Document doc = Objects.requireNonNull(PsiDocumentManager.getInstance(project).getDocument(field.getContainingFile()));
94     PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(doc);
95     AddVariableInitializerFix.runAssignmentTemplate(rExpressions, suggestedInitializers, editor);
96   }
97
98   @NotNull
99   private static PsiExpression addFieldInitialization(@NotNull PsiMethod constructor,
100                                                       @NotNull LookupElement[] suggestedInitializers,
101                                                       @NotNull PsiField field,
102                                                       @NotNull Project project) {
103     PsiCodeBlock methodBody = constructor.getBody();
104     if (methodBody == null) {
105       //incomplete code
106       CreateFromUsageUtils.setupMethodBody(constructor);
107       methodBody = constructor.getBody();
108       LOG.assertTrue(methodBody != null);
109     }
110
111     final String fieldName = field.getName();
112     String stmtText = fieldName + " = " + suggestedInitializers[0].getPsiElement().getText() + ";";
113     if (methodContainsParameterWithName(constructor, fieldName)) {
114       stmtText = "this." + stmtText;
115     }
116
117     final PsiManager psiManager = PsiManager.getInstance(project);
118     final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiManager.getProject());
119     final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
120
121     final PsiExpressionStatement addedStatement = (PsiExpressionStatement)methodBody.add(codeStyleManager
122       .reformat(factory.createStatementFromText(stmtText, methodBody)));
123     return ObjectUtils.notNull(((PsiAssignmentExpression)addedStatement.getExpression()).getRExpression());
124   }
125
126   private static boolean methodContainsParameterWithName(@NotNull PsiMethod constructor, @NotNull String name) {
127     for (PsiParameter parameter : constructor.getParameterList().getParameters()) {
128       if (name.equals(parameter.getName())) {
129         return true;
130       }
131     }
132     return false;
133   }
134
135   @NotNull
136   private static List<PsiMethod> choose(@NotNull PsiMethod[] ctors, @NotNull final Project project) {
137     if (ApplicationManager.getApplication().isUnitTestMode()) {
138       return Arrays.asList(ctors);
139     }
140
141     if (ctors.length == 1) {
142       return Arrays.asList(ctors[0]);
143     }
144
145     if (ctors.length > 1) {
146       final MemberChooser<PsiMethodMember> chooser = new MemberChooser<>(toPsiMethodMemberArray(ctors), false, true, project);
147       chooser.setTitle(QuickFixBundle.message("initialize.final.field.in.constructor.choose.dialog.title"));
148       chooser.show();
149
150       final List<PsiMethodMember> chosenMembers = chooser.getSelectedElements();
151       if (chosenMembers != null) {
152         return Arrays.asList(toPsiMethodArray(chosenMembers));
153       }
154     }
155
156     return Collections.emptyList();
157   }
158
159   @NotNull
160   private static PsiMethodMember[] toPsiMethodMemberArray(@NotNull PsiMethod[] methods) {
161     final PsiMethodMember[] result = new PsiMethodMember[methods.length];
162     for (int i = 0; i < methods.length; i++) {
163       result[i] = new PsiMethodMember(methods[i]);
164     }
165     return result;
166   }
167
168   @NotNull
169   private static PsiMethod[] toPsiMethodArray(@NotNull List<PsiMethodMember> methodMembers) {
170     final PsiMethod[] result = new PsiMethod[methodMembers.size()];
171     int i = 0;
172     for (PsiMethodMember methodMember : methodMembers) {
173       result[i++] = methodMember.getElement();
174     }
175     return result;
176   }
177
178   private static void createDefaultConstructor(PsiClass psiClass, @NotNull final Project project, final Editor editor, final PsiFile file) {
179     final AddDefaultConstructorFix defaultConstructorFix = new AddDefaultConstructorFix(psiClass);
180     ApplicationManager.getApplication().runWriteAction(() -> defaultConstructorFix.invoke(project, editor, file));
181   }
182
183   @NotNull
184   private static PsiMethod[] filterIfFieldAlreadyAssigned(@NotNull PsiField field, @NotNull PsiMethod[] ctors) {
185     final List<PsiMethod> result = new ArrayList<>(Arrays.asList(ctors));
186     for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(ctors))) {
187       final PsiElement element = reference.getElement();
188       if (element instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)element)) {
189         result.remove(PsiTreeUtil.getParentOfType(element, PsiMethod.class));
190       }
191     }
192     return result.toArray(PsiMethod.EMPTY_ARRAY);
193   }
194
195   @Override
196   public boolean startInWriteAction() {
197     return false;
198   }
199 }