EA-32658 - initialize field in method while introduce field
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / introduce / field / GrIntroduceFieldHandler.java
1 /*
2  * Copyright 2000-2011 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 org.jetbrains.plugins.groovy.refactoring.introduce.field;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Condition;
20 import com.intellij.psi.*;
21 import com.intellij.psi.util.PsiTreeUtil;
22 import com.intellij.refactoring.HelpID;
23 import com.intellij.refactoring.introduceField.IntroduceFieldHandler;
24 import com.intellij.util.containers.ContainerUtil;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import org.jetbrains.plugins.groovy.lang.GrReferenceAdjuster;
28 import org.jetbrains.plugins.groovy.lang.psi.GrQualifiedReference;
29 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
30 import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
31 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
32 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumTypeDefinition;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstantList;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
41 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
42 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
43 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
44 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
45 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext;
46 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceDialog;
47 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
48 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceRefactoringError;
49
50 import java.util.ArrayList;
51 import java.util.List;
52
53 import static com.intellij.util.ArrayUtil.EMPTY_STRING_ARRAY;
54 import static org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldSettings.Init.*;
55
56 /**
57  * @author Maxim.Medvedev
58  */
59 public class GrIntroduceFieldHandler extends GrIntroduceHandlerBase<GrIntroduceFieldSettings> {
60   private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldHandler");
61
62   @Override
63   protected String getRefactoringName() {
64     return IntroduceFieldHandler.REFACTORING_NAME;
65   }
66
67   @Override
68   protected String getHelpID() {
69     return HelpID.INTRODUCE_FIELD;
70   }
71
72   @NotNull
73   @Override
74   protected GrTypeDefinition findScope(GrExpression expression, GrVariable variable) {
75     PsiElement place = expression == null ? variable : expression;
76     final GrTypeDefinition scope = PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class);
77     LOG.assertTrue(scope != null);
78     return scope;
79   }
80
81   @Override
82   protected void checkExpression(GrExpression selectedExpr) {
83     checkContainingClass(selectedExpr);
84   }
85
86   private static void checkContainingClass(PsiElement place) {
87     final GrTypeDefinition containingClass = PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class);
88     if (containingClass == null) throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("cannot.introduce.field.in.script"));
89     if (containingClass.isInterface()) {
90       throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("cannot.introduce.field.in.interface"));
91     }
92     if (PsiUtil.skipParentheses(place, false) == null) {
93       throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("expression.contains.errors"));
94     }
95   }
96
97   @Override
98   protected void checkVariable(GrVariable variable) throws GrIntroduceRefactoringError {
99     checkContainingClass(variable);
100   }
101
102   @Override
103   protected void checkOccurrences(PsiElement[] occurrences) {
104     //nothing to do
105   }
106
107   @Override
108   protected GrIntroduceDialog<GrIntroduceFieldSettings> getDialog(GrIntroduceContext context) {
109     return new GrIntroduceFieldDialog(context);
110   }
111
112   @Override
113   public GrField runRefactoring(GrIntroduceContext context, GrIntroduceFieldSettings settings) {
114     final PsiClass targetClass = (PsiClass)context.scope;
115
116     if (targetClass == null) return null;
117
118     final GrVariableDeclaration declaration = createField(context, settings);
119
120     final GrVariableDeclaration added;
121     if (targetClass instanceof GrEnumTypeDefinition) {
122       final GrEnumConstantList enumConstants = ((GrEnumTypeDefinition)targetClass).getEnumConstantList();
123       added = (GrVariableDeclaration)targetClass.addAfter(declaration, enumConstants);
124     }
125     else {
126       added = ((GrVariableDeclaration)targetClass.add(declaration));
127     }
128
129     final GrField field = (GrField)added.getVariables()[0];
130     GrIntroduceFieldSettings.Init i = settings.initializeIn();
131
132     if (i == CONSTRUCTOR) {
133       initializeInConstructor(context, settings, field);
134     }
135     else if (i == CUR_METHOD) {
136       initializeInMethod(context, settings, field);
137     }
138
139     GrReferenceAdjuster.shortenReferences(added);
140     
141     //var can be invalid if it was removed while initialization
142     if (settings.removeLocalVar()) {
143       deleteLocalVar(context);
144     }
145
146     if (settings.replaceAllOccurrences()) {
147       GroovyRefactoringUtil.sortOccurrences(context.occurrences);
148       for (PsiElement occurrence : context.occurrences) {
149         replaceOccurrence(field, occurrence);
150       }
151     }
152     else {
153       if (PsiUtil.isExpressionStatement(context.expression)) {
154         context.expression.delete();
155       }
156       else {
157         replaceOccurrence(field, context.expression);
158       }
159     }
160     return field;
161   }
162
163   @Override
164   protected PsiElement[] findOccurrences(GrExpression expression, PsiElement scope) {
165     final PsiElement[] occurrences = super.findOccurrences(expression, scope);
166     GrTypeDefinition clazz = (GrTypeDefinition)scope;
167     if (shouldBeStatic(expression, clazz)) return occurrences;
168
169     List<PsiElement> filtered = new ArrayList<PsiElement>();
170     for (PsiElement occurrence : occurrences) {
171       if (!shouldBeStatic(occurrence, clazz)) {
172         filtered.add(occurrence);
173       }
174     }
175     return ContainerUtil.toArray(filtered, new PsiElement[filtered.size()]);
176   }
177
178   private static void initializeInMethod(GrIntroduceContext context, GrIntroduceFieldSettings settings, GrField field) {
179     if (context.expression == null) return;
180     final GrExpression expression = context.expression;
181     final GrTypeDefinition scope = (GrTypeDefinition)context.scope;
182     final GrMethod method = getContainingMethod(expression, scope);
183     LOG.assertTrue(method != null);
184
185     final GrStatement anchor;
186     if (settings.removeLocalVar()) {
187       GrVariable variable = resolveLocalVar(context);
188       anchor = PsiTreeUtil.getParentOfType(variable, GrStatement.class);
189     }
190     else {
191       anchor = (GrStatement)findAnchor(context, settings, context.occurrences, method.getBlock());
192     }
193
194     generateAssignment(context, settings, field, anchor, method.getBlock());
195   }
196
197   private static void initializeInConstructor(GrIntroduceContext context, GrIntroduceFieldSettings settings, GrField field) {
198     final GrTypeDefinition scope = (GrTypeDefinition)context.scope;
199     final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.project);
200
201     if (scope instanceof GrAnonymousClassDefinition) {
202       final GrClassInitializer[] initializers = scope.getInitializers();
203       final GrClassInitializer initializer;
204       if (initializers.length == 0) {
205         initializer = (GrClassInitializer)scope.add(factory.createClassInitializer());
206       }
207       else {
208         initializer = initializers[0];
209       }
210
211       final PsiElement anchor = findAnchor(context, settings, initializer.getBlock());
212       generateAssignment(context, settings, field, (GrStatement)anchor, initializer.getBlock());
213       return;
214     }
215
216     PsiMethod[] constructors = scope.getConstructors();
217     if (constructors.length == 0) {
218       final String name = scope.getName();
219       LOG.assertTrue(name != null, scope.getText());
220       final GrMethod constructor = factory.createConstructorFromText(name, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, "{}", scope);
221       final PsiElement added = scope.add(constructor);
222       constructors = new PsiMethod[]{(PsiMethod)added};
223     }
224     for (PsiMethod constructor : constructors) {
225       final GrConstructorInvocation invocation = PsiImplUtil.getChainingConstructorInvocation((GrMethod)constructor);
226       if (invocation != null && invocation.isThisCall()) continue;
227       final PsiElement anchor = findAnchor(context, settings, ((GrMethod)constructor).getBlock());
228
229       generateAssignment(context, settings, field, (GrStatement)anchor, ((GrMethod)constructor).getBlock());
230     }
231   }
232
233   private static void generateAssignment(GrIntroduceContext context,
234                                          GrIntroduceFieldSettings settings,
235                                          GrField field,
236                                          @Nullable GrStatement anchor,
237                                          GrCodeBlock defaultContainer) {
238     final GrExpression initializer;
239     if (settings.removeLocalVar()) {
240       initializer = extractVarInitializer(context);
241     }
242     else {
243       initializer = context.expression;
244     }
245     GrAssignmentExpression init = (GrAssignmentExpression)GroovyPsiElementFactory.getInstance(context.project)
246       .createExpressionFromText(settings.getName() + " = " + initializer.getText());
247
248     GrCodeBlock block;
249     if (anchor != null) {
250       anchor = GroovyRefactoringUtil.addBlockIntoParent(anchor);
251       LOG.assertTrue(anchor.getParent() instanceof GrCodeBlock);
252       block = (GrCodeBlock)anchor.getParent();
253     }
254     else {
255       block = defaultContainer;
256     }
257     init = (GrAssignmentExpression)block.addStatementBefore(init, anchor);
258     replaceOccurrence(field, init.getLValue());
259   }
260
261   private static GrExpression extractVarInitializer(GrIntroduceContext context) {
262     final PsiElement resolved = resolveLocalVar(context);
263     LOG.assertTrue(resolved instanceof GrVariable);
264     GrExpression initializer = ((GrVariable)resolved).getInitializerGroovy();
265     LOG.assertTrue(initializer != null);
266     return initializer;
267   }
268
269   @Nullable
270   private static PsiElement findAnchor(GrIntroduceContext context, GrIntroduceFieldSettings settings, final GrCodeBlock block) {
271     final List<PsiElement> elements = ContainerUtil.findAll(context.occurrences, new Condition<PsiElement>() {
272       @Override
273       public boolean value(PsiElement element) {
274         return PsiTreeUtil.isAncestor(block, element, true);
275       }
276     });
277     if (elements.size() == 0) return null;
278     return findAnchor(context, settings, ContainerUtil.toArray(elements, new PsiElement[elements.size()]), block);
279   }
280
281   private static void replaceOccurrence(GrField field, PsiElement occurrence) {
282     final GrReferenceExpression newExpr = createRefExpression(field, occurrence);
283     final PsiElement replaced;
284     if (occurrence instanceof GrExpression) {
285       replaced = ((GrExpression)occurrence).replaceWithExpression(newExpr, false);
286     } else {
287       replaced = occurrence.replace(newExpr);
288     }
289     if (replaced instanceof GrQualifiedReference) {
290       GrReferenceAdjuster.shortenReference((GrQualifiedReference)replaced);
291     }
292   }
293
294   private static GrReferenceExpression createRefExpression(GrField field, PsiElement place) {
295     final PsiClass containingClass = field.getContainingClass();
296     LOG.assertTrue(containingClass != null);
297     final String qname = containingClass.getQualifiedName();
298     final String prefix = qname != null ? qname + "." : "";
299     final String refText;
300     if (field.hasModifierProperty(PsiModifier.STATIC)) {
301       refText = prefix + field.getName();
302     }
303     else {
304       refText = prefix + "this." + field.getName();
305     }
306     return GroovyPsiElementFactory.getInstance(place.getProject()).createReferenceExpressionFromText(refText, place);
307   }
308
309   private static GrVariableDeclaration createField(GrIntroduceContext context, GrIntroduceFieldSettings settings) {
310     final String name = settings.getName();
311     final PsiType type = settings.getSelectedType();
312     final String modifier = settings.getVisibilityModifier();
313
314     final GrExpression initializer;
315     if (settings.initializeIn() == FIELD_DECLARATION) {
316       if (settings.removeLocalVar()) {
317         initializer = extractVarInitializer(context);
318       }
319       else {
320         initializer = context.expression;
321       }
322     }
323     else {
324       initializer = null;
325     }
326
327     final GrVariableDeclaration fieldDeclaration = GroovyPsiElementFactory.getInstance(context.project).createFieldDeclaration(EMPTY_STRING_ARRAY, name, initializer, type);
328
329     fieldDeclaration.getModifierList().setModifierProperty(modifier, true);
330     if (settings.isStatic()) {
331       fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.STATIC, true);
332     }
333     if (settings.declareFinal()) {
334       fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.FINAL, true);
335     }
336     return fieldDeclaration;
337   }
338
339   @Nullable
340   static GrMethod getContainingMethod(PsiElement place, GrTypeDefinition clazz) {
341     while (place != null && place != clazz) {
342       place = place.getParent();
343       if (place instanceof GrMethod) return (GrMethod)place;
344     }
345     return null;
346   }
347
348   @NotNull
349   static GrMember getContainer(PsiElement place, GrTypeDefinition clazz) {
350     while (place != null && place != clazz) {
351       place = place.getParent();
352       if (place instanceof GrMember) return (GrMember)place;
353     }
354     LOG.error("container cannot be null");
355     return null;
356   }
357
358   static boolean shouldBeStatic(PsiElement expr, GrTypeDefinition clazz) {
359     final GrMember method = getContainer(expr, clazz);
360     return method.hasModifierProperty(PsiModifier.STATIC);
361   }
362 }