52fbd2d2e2db371ebbd5fbcf0d72117dc341b1ca
[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.blocks.GrOpenBlock;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumTypeDefinition;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstantList;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
42 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
43 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
44 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
45 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
46 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext;
47 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceDialog;
48 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
49 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceRefactoringError;
50
51 import java.util.ArrayList;
52 import java.util.List;
53
54 import static com.intellij.util.ArrayUtil.EMPTY_STRING_ARRAY;
55 import static org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldSettings.Init.*;
56
57 /**
58  * @author Maxim.Medvedev
59  */
60 public class GrIntroduceFieldHandler extends GrIntroduceHandlerBase<GrIntroduceFieldSettings> {
61   private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldHandler");
62
63   @Override
64   protected String getRefactoringName() {
65     return IntroduceFieldHandler.REFACTORING_NAME;
66   }
67
68   @Override
69   protected String getHelpID() {
70     return HelpID.INTRODUCE_FIELD;
71   }
72
73   @NotNull
74   @Override
75   protected GrTypeDefinition findScope(GrExpression expression, GrVariable variable) {
76     PsiElement place = expression == null ? variable : expression;
77     final GrTypeDefinition scope = PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class);
78     LOG.assertTrue(scope != null);
79     return scope;
80   }
81
82   @Override
83   protected void checkExpression(GrExpression selectedExpr) {
84     checkContainingClass(selectedExpr);
85   }
86
87   private static void checkContainingClass(PsiElement place) {
88     final GrTypeDefinition containingClass = PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class);
89     if (containingClass == null) throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("cannot.introduce.field.in.script"));
90     if (containingClass.isInterface()) {
91       throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("cannot.introduce.field.in.interface"));
92     }
93     if (PsiUtil.skipParentheses(place, false) == null) {
94       throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("expression.contains.errors"));
95     }
96   }
97
98   @Override
99   protected void checkVariable(GrVariable variable) throws GrIntroduceRefactoringError {
100     checkContainingClass(variable);
101   }
102
103   @Override
104   protected void checkOccurrences(PsiElement[] occurrences) {
105     //notning to do
106   }
107
108   @Override
109   protected GrIntroduceDialog<GrIntroduceFieldSettings> getDialog(GrIntroduceContext context) {
110     return new GrIntroduceFieldDialog(context);
111   }
112
113   @Override
114   public GrField runRefactoring(GrIntroduceContext context, GrIntroduceFieldSettings settings) {
115     final PsiClass targetClass = (PsiClass)context.scope;
116
117     if (targetClass == null) return null;
118
119     final GrVariableDeclaration declaration = createField(context, settings);
120
121     final GrVariableDeclaration added;
122     if (targetClass instanceof GrEnumTypeDefinition) {
123       final GrEnumConstantList enumConstants = ((GrEnumTypeDefinition)targetClass).getEnumConstantList();
124       added = (GrVariableDeclaration)targetClass.addAfter(declaration, enumConstants);
125     }
126     else {
127       added = ((GrVariableDeclaration)targetClass.add(declaration));
128     }
129
130     final GrField field = (GrField)added.getVariables()[0];
131     GrIntroduceFieldSettings.Init i = settings.initializeIn();
132
133     if (i == CONSTRUCTOR) {
134       initializeInConstructor(context, settings, field);
135     }
136     else if (i == CUR_METHOD) {
137       initializeInMethod(context, settings, field);
138     }
139
140     GrReferenceAdjuster.shortenReferences(added);
141     if (settings.removeLocalVar()) {
142       deleteLocalVar(context);
143     }
144
145     if (settings.replaceAllOccurrences()) {
146       GroovyRefactoringUtil.sortOccurrences(context.occurrences);
147       for (PsiElement occurrence : context.occurrences) {
148         replaceOccurence(field, occurrence);
149       }
150     }
151     else {
152       if (PsiUtil.isExpressionStatement(context.expression)) {
153         context.expression.delete();
154       }
155       else {
156         replaceOccurence(field, context.expression);
157       }
158     }
159     return field;
160   }
161
162   @Override
163   protected PsiElement[] findOccurences(GrExpression expression, PsiElement scope) {
164     final PsiElement[] occurences = super.findOccurences(expression, scope);
165     GrTypeDefinition clazz = (GrTypeDefinition)scope;
166     if (shouldBeStatic(expression, clazz)) return occurences;
167
168     List<PsiElement> filtered = new ArrayList<PsiElement>();
169     for (PsiElement occurence : occurences) {
170       if (!shouldBeStatic(occurence, clazz)) {
171         filtered.add(occurence);
172       }
173     }
174     return ContainerUtil.toArray(filtered, new PsiElement[filtered.size()]);
175   }
176
177   private static void initializeInMethod(GrIntroduceContext context, GrIntroduceFieldSettings settings, GrField field) {
178     if (context.expression == null) return;
179     final GrExpression expression = context.expression;
180     final GrTypeDefinition scope = (GrTypeDefinition)context.scope;
181     final GrMethod method = getContainingMethod(expression, scope);
182     LOG.assertTrue(method != null);
183     final GrOpenBlock block = method.getBlock();
184     LOG.assertTrue(block != null);
185     final GrStatement anchor;
186     if (settings.removeLocalVar()) {
187       final GrVariable variable = resolveLocalVar(context);
188       anchor = PsiTreeUtil.getParentOfType(variable, GrStatement.class);
189     }
190     else {
191       anchor = (GrStatement)findAnchor(context, settings, context.occurrences, block);
192     }
193
194     generateAssignment(context, settings, field, anchor, block);
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                                          GrStatement anchor,
237                                          final GrOpenBlock block) {
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     init = (GrAssignmentExpression)block.addStatementBefore(init, anchor);
248     replaceOccurence(field, init.getLValue());
249   }
250
251   private static GrExpression extractVarInitializer(GrIntroduceContext context) {
252     final PsiElement resolved = resolveLocalVar(context);
253     LOG.assertTrue(resolved instanceof GrVariable);
254     GrExpression initializer = ((GrVariable)resolved).getInitializerGroovy();
255     LOG.assertTrue(initializer != null);
256     return initializer;
257   }
258
259   @Nullable
260   private static PsiElement findAnchor(GrIntroduceContext context, GrIntroduceFieldSettings settings, final GrCodeBlock block) {
261     final List<PsiElement> elements = ContainerUtil.findAll(context.occurrences, new Condition<PsiElement>() {
262       @Override
263       public boolean value(PsiElement element) {
264         return PsiTreeUtil.isAncestor(block, element, true);
265       }
266     });
267     if (elements.size() == 0) return null;
268     return findAnchor(context, settings, ContainerUtil.toArray(elements, new PsiElement[elements.size()]), block);
269   }
270
271   private static void replaceOccurence(GrField field, PsiElement occurence) {
272     final GrReferenceExpression newExpr = createRefExpression(field, occurence);
273     final PsiElement replaced;
274     if (occurence instanceof GrExpression) {
275       replaced = ((GrExpression)occurence).replaceWithExpression(newExpr, false);
276     } else {
277       replaced = occurence.replace(newExpr);
278     }
279     if (replaced instanceof GrQualifiedReference) {
280       GrReferenceAdjuster.shortenReference((GrQualifiedReference)replaced);
281     }
282   }
283
284   private static GrReferenceExpression createRefExpression(GrField field, PsiElement place) {
285     final PsiClass containingClass = field.getContainingClass();
286     LOG.assertTrue(containingClass != null);
287     final String qname = containingClass.getQualifiedName();
288     final String prefix = qname != null ? qname + "." : "";
289     final String refText;
290     if (field.hasModifierProperty(PsiModifier.STATIC)) {
291       refText = prefix + field.getName();
292     }
293     else {
294       refText = prefix + "this." + field.getName();
295     }
296     return GroovyPsiElementFactory.getInstance(place.getProject()).createReferenceExpressionFromText(refText, place);
297   }
298
299   private static GrVariableDeclaration createField(GrIntroduceContext context, GrIntroduceFieldSettings settings) {
300     final String name = settings.getName();
301     final PsiType type = settings.getSelectedType();
302     final String modifier = settings.getVisibilityModifier();
303
304     final GrExpression initializer;
305     if (settings.initializeIn() == FIELD_DECLARATION) {
306       if (settings.removeLocalVar()) {
307         initializer = extractVarInitializer(context);
308       }
309       else {
310         initializer = context.expression;
311       }
312     }
313     else {
314       initializer = null;
315     }
316
317     final GrVariableDeclaration fieldDeclaration = GroovyPsiElementFactory.getInstance(context.project).createFieldDeclaration(EMPTY_STRING_ARRAY, name, initializer, type);
318
319     fieldDeclaration.getModifierList().setModifierProperty(modifier, true);
320     if (settings.isStatic()) {
321       fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.STATIC, true);
322     }
323     if (settings.declareFinal()) {
324       fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.FINAL, true);
325     }
326     return fieldDeclaration;
327   }
328
329   @Nullable
330   static GrMethod getContainingMethod(PsiElement place, GrTypeDefinition clazz) {
331     while (place != null && place != clazz) {
332       place = place.getParent();
333       if (place instanceof GrMethod) return (GrMethod)place;
334     }
335     return null;
336   }
337
338   @NotNull
339   static GrMember getContainer(PsiElement place, GrTypeDefinition clazz) {
340     while (place != null && place != clazz) {
341       place = place.getParent();
342       if (place instanceof GrMember) return (GrMember)place;
343     }
344     LOG.error("container cannot be null");
345     return null;
346   }
347
348   static boolean shouldBeStatic(PsiElement expr, GrTypeDefinition clazz) {
349     final GrMember method = getContainer(expr, clazz);
350     return method.hasModifierProperty(PsiModifier.STATIC);
351   }
352 }