2 * Copyright 2000-2011 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.plugins.groovy.refactoring.introduce.field;
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;
50 import java.util.ArrayList;
51 import java.util.List;
53 import static com.intellij.util.ArrayUtil.EMPTY_STRING_ARRAY;
54 import static org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldSettings.Init.*;
57 * @author Maxim.Medvedev
59 public class GrIntroduceFieldHandler extends GrIntroduceHandlerBase<GrIntroduceFieldSettings> {
60 private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldHandler");
63 protected String getRefactoringName() {
64 return IntroduceFieldHandler.REFACTORING_NAME;
68 protected String getHelpID() {
69 return HelpID.INTRODUCE_FIELD;
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);
82 protected void checkExpression(GrExpression selectedExpr) {
83 checkContainingClass(selectedExpr);
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"));
92 if (PsiUtil.skipParentheses(place, false) == null) {
93 throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("expression.contains.errors"));
98 protected void checkVariable(GrVariable variable) throws GrIntroduceRefactoringError {
99 checkContainingClass(variable);
103 protected void checkOccurrences(PsiElement[] occurrences) {
108 protected GrIntroduceDialog<GrIntroduceFieldSettings> getDialog(GrIntroduceContext context) {
109 return new GrIntroduceFieldDialog(context);
113 public GrField runRefactoring(GrIntroduceContext context, GrIntroduceFieldSettings settings) {
114 final PsiClass targetClass = (PsiClass)context.scope;
116 if (targetClass == null) return null;
118 final GrVariableDeclaration declaration = createField(context, settings);
120 final GrVariableDeclaration added;
121 if (targetClass instanceof GrEnumTypeDefinition) {
122 final GrEnumConstantList enumConstants = ((GrEnumTypeDefinition)targetClass).getEnumConstantList();
123 added = (GrVariableDeclaration)targetClass.addAfter(declaration, enumConstants);
126 added = ((GrVariableDeclaration)targetClass.add(declaration));
129 final GrField field = (GrField)added.getVariables()[0];
130 GrIntroduceFieldSettings.Init i = settings.initializeIn();
132 if (i == CONSTRUCTOR) {
133 initializeInConstructor(context, settings, field);
135 else if (i == CUR_METHOD) {
136 initializeInMethod(context, settings, field);
139 GrReferenceAdjuster.shortenReferences(added);
141 //var can be invalid if it was removed while initialization
142 if (settings.removeLocalVar()) {
143 deleteLocalVar(context);
146 if (settings.replaceAllOccurrences()) {
147 GroovyRefactoringUtil.sortOccurrences(context.occurrences);
148 for (PsiElement occurrence : context.occurrences) {
149 replaceOccurrence(field, occurrence);
153 if (PsiUtil.isExpressionStatement(context.expression)) {
154 context.expression.delete();
157 replaceOccurrence(field, context.expression);
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;
169 List<PsiElement> filtered = new ArrayList<PsiElement>();
170 for (PsiElement occurrence : occurrences) {
171 if (!shouldBeStatic(occurrence, clazz)) {
172 filtered.add(occurrence);
175 return ContainerUtil.toArray(filtered, new PsiElement[filtered.size()]);
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);
185 final GrStatement anchor;
186 if (settings.removeLocalVar()) {
187 GrVariable variable = resolveLocalVar(context);
188 anchor = PsiTreeUtil.getParentOfType(variable, GrStatement.class);
191 anchor = (GrStatement)findAnchor(context, settings, context.occurrences, method.getBlock());
194 generateAssignment(context, settings, field, anchor, method.getBlock());
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);
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());
208 initializer = initializers[0];
211 final PsiElement anchor = findAnchor(context, settings, initializer.getBlock());
212 generateAssignment(context, settings, field, (GrStatement)anchor, initializer.getBlock());
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};
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());
229 generateAssignment(context, settings, field, (GrStatement)anchor, ((GrMethod)constructor).getBlock());
233 private static void generateAssignment(GrIntroduceContext context,
234 GrIntroduceFieldSettings settings,
236 @Nullable GrStatement anchor,
237 GrCodeBlock defaultContainer) {
238 final GrExpression initializer;
239 if (settings.removeLocalVar()) {
240 initializer = extractVarInitializer(context);
243 initializer = context.expression;
245 GrAssignmentExpression init = (GrAssignmentExpression)GroovyPsiElementFactory.getInstance(context.project)
246 .createExpressionFromText(settings.getName() + " = " + initializer.getText());
249 if (anchor != null) {
250 anchor = GroovyRefactoringUtil.addBlockIntoParent(anchor);
251 LOG.assertTrue(anchor.getParent() instanceof GrCodeBlock);
252 block = (GrCodeBlock)anchor.getParent();
255 block = defaultContainer;
257 init = (GrAssignmentExpression)block.addStatementBefore(init, anchor);
258 replaceOccurrence(field, init.getLValue());
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);
270 private static PsiElement findAnchor(GrIntroduceContext context, GrIntroduceFieldSettings settings, final GrCodeBlock block) {
271 final List<PsiElement> elements = ContainerUtil.findAll(context.occurrences, new Condition<PsiElement>() {
273 public boolean value(PsiElement element) {
274 return PsiTreeUtil.isAncestor(block, element, true);
277 if (elements.size() == 0) return null;
278 return findAnchor(context, settings, ContainerUtil.toArray(elements, new PsiElement[elements.size()]), block);
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);
287 replaced = occurrence.replace(newExpr);
289 if (replaced instanceof GrQualifiedReference) {
290 GrReferenceAdjuster.shortenReference((GrQualifiedReference)replaced);
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();
304 refText = prefix + "this." + field.getName();
306 return GroovyPsiElementFactory.getInstance(place.getProject()).createReferenceExpressionFromText(refText, place);
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();
314 final GrExpression initializer;
315 if (settings.initializeIn() == FIELD_DECLARATION) {
316 if (settings.removeLocalVar()) {
317 initializer = extractVarInitializer(context);
320 initializer = context.expression;
327 final GrVariableDeclaration fieldDeclaration = GroovyPsiElementFactory.getInstance(context.project).createFieldDeclaration(EMPTY_STRING_ARRAY, name, initializer, type);
329 fieldDeclaration.getModifierList().setModifierProperty(modifier, true);
330 if (settings.isStatic()) {
331 fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.STATIC, true);
333 if (settings.declareFinal()) {
334 fieldDeclaration.getModifierList().setModifierProperty(PsiModifier.FINAL, true);
336 return fieldDeclaration;
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;
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;
354 LOG.error("container cannot be null");
358 static boolean shouldBeStatic(PsiElement expr, GrTypeDefinition clazz) {
359 final GrMember method = getContainer(expr, clazz);
360 return method.hasModifierProperty(PsiModifier.STATIC);