blob: 94fab9d2012f882ecd88611bb547d8bf6d5153ba [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.refactoring.introduce.field;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeStyle.GrReferenceAdjuster;
import org.jetbrains.plugins.groovy.lang.psi.GrQualifiedReference;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstantList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMembersDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
import org.jetbrains.plugins.groovy.refactoring.introduce.StringPartInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Max Medvedev
*/
public class GrIntroduceFieldProcessor {
private static final Logger LOG = Logger.getInstance(GrIntroduceFieldProcessor.class);
private final GrIntroduceContext myContext;
private final GrIntroduceFieldSettings mySettings;
@Nullable private GrExpression myInitializer;
@Nullable private GrVariable myLocalVariable;
public GrIntroduceFieldProcessor(@NotNull GrIntroduceContext context,
@NotNull GrIntroduceFieldSettings settings) {
this.myContext = context;
this.mySettings = settings;
}
@Nullable
public GrVariable run() {
PsiElement scope = myContext.getScope();
final PsiClass targetClass = scope instanceof GroovyFileBase ? ((GroovyFileBase)scope).getScriptClass() : (PsiClass)scope;
if (targetClass == null) return null;
final GrVariableDeclaration declaration = insertField(targetClass);
final GrVariable field = declaration.getVariables()[0];
if (mySettings.removeLocalVar()) {
myLocalVariable = GrIntroduceHandlerBase.resolveLocalVar(myContext);
assert myLocalVariable != null : myContext.getExpression() + ", " + myContext.getVar() + ", " + myContext.getStringPart();
}
myInitializer = (GrExpression)getInitializer().copy();
List<PsiElement> replaced = processOccurrences(targetClass, field);
switch (mySettings.initializeIn()) {
case CUR_METHOD:
initializeInMethod(field, replaced);
break;
case FIELD_DECLARATION:
field.setInitializerGroovy(myInitializer);
break;
case CONSTRUCTOR:
initializeInConstructor(field);
break;
case SETUP_METHOD:
initializeInSetup(field);
break;
}
JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration);
if (mySettings.removeLocalVar()) {
GrIntroduceHandlerBase.deleteLocalVar(myLocalVariable);
}
return field;
}
@NotNull
private List<PsiElement> processOccurrences(@NotNull PsiClass targetClass, @NotNull GrVariable field) {
if (myContext.getStringPart() != null) {
final GrExpression expr = myContext.getStringPart().replaceLiteralWithConcatenation(field.getName());
final PsiElement occurrence = replaceOccurrence(field, expr, targetClass);
updateCaretPosition(occurrence);
return Collections.singletonList(occurrence);
}
if (mySettings.replaceAllOccurrences()) {
GroovyRefactoringUtil.sortOccurrences(myContext.getOccurrences());
ArrayList<PsiElement> result = ContainerUtil.newArrayList();
for (PsiElement occurrence : myContext.getOccurrences()) {
result.add(replaceOccurrence(field, occurrence, targetClass));
}
return result;
}
GrVariable var = myContext.getVar();
if (var != null) {
GrExpression initializer = var.getInitializerGroovy();
if (initializer != null) {
return Collections.singletonList(replaceOccurrence(field, initializer, targetClass));
}
else {
return Collections.emptyList();
}
}
final GrExpression expression = myContext.getExpression();
assert expression != null;
if (PsiUtil.isExpressionStatement(expression)) {
return Collections.<PsiElement>singletonList(expression);
}
else {
return Collections.singletonList(replaceOccurrence(field, expression, targetClass));
}
}
private void updateCaretPosition(@NotNull PsiElement occurrence) {
myContext.getEditor().getCaretModel().moveToOffset(occurrence.getTextRange().getEndOffset());
myContext.getEditor().getSelectionModel().removeSelection();
}
@NotNull
protected GrVariableDeclaration insertField(@NotNull PsiClass targetClass) {
GrVariableDeclaration declaration = createField(targetClass);
if (targetClass instanceof GrEnumTypeDefinition) {
final GrEnumConstantList enumConstants = ((GrEnumTypeDefinition)targetClass).getEnumConstantList();
return (GrVariableDeclaration)targetClass.addAfter(declaration, enumConstants);
}
if (targetClass instanceof GrTypeDefinition) {
PsiElement anchor = getAnchorForDeclaration((GrTypeDefinition)targetClass);
return (GrVariableDeclaration)targetClass.addAfter(declaration, anchor);
}
else {
assert targetClass instanceof GroovyScriptClass;
final GroovyFile file = ((GroovyScriptClass)targetClass).getContainingFile();
PsiElement[] elements = file.getMethods();
if (elements.length == 0) elements = file.getStatements();
final PsiElement anchor = ArrayUtil.getFirstElement(elements);
return (GrVariableDeclaration)file.addBefore(declaration, anchor);
}
}
@Nullable
private static PsiElement getAnchorForDeclaration(@NotNull GrTypeDefinition targetClass) {
PsiElement anchor = targetClass.getBody().getLBrace();
final GrMembersDeclaration[] declarations = targetClass.getMemberDeclarations();
for (GrMembersDeclaration declaration : declarations) {
if (declaration instanceof GrVariableDeclaration) anchor = declaration;
if (!(declaration instanceof GrVariableDeclaration)) return anchor;
}
return anchor;
}
void initializeInSetup(@NotNull GrVariable field) {
final PsiMethod setUpMethod = TestFrameworks.getInstance().findOrCreateSetUpMethod(((PsiClass)myContext.getScope()));
assert setUpMethod instanceof GrMethod;
final GrOpenBlock body = ((GrMethod)setUpMethod).getBlock();
final PsiElement anchor = findAnchorForAssignment(body);
generateAssignment(field, (GrStatement)anchor, body, null);
}
void initializeInMethod(@NotNull GrVariable field, @NotNull List<PsiElement> replaced) {
final PsiElement _scope = myContext.getScope();
final PsiElement scope = _scope instanceof GroovyScriptClass ? ((GroovyScriptClass)_scope).getContainingFile() : _scope;
final PsiElement place = replaced.get(0);
final GrMember member = GrIntroduceFieldHandler.getContainer(place, scope);
GrStatementOwner container = member instanceof GrMethod ? ((GrMethod)member).getBlock() :
member instanceof GrClassInitializer ? ((GrClassInitializer)member).getBlock() :
place.getContainingFile() instanceof GroovyFile ? ((GroovyFile)place.getContainingFile()) :
null;
assert container != null;
final PsiElement anchor;
if (mySettings.removeLocalVar()) {
GrVariable variable = myLocalVariable;
anchor = PsiTreeUtil.getParentOfType(variable, GrStatement.class);
}
else {
anchor = GrIntroduceHandlerBase.findAnchor(replaced.toArray(new PsiElement[replaced.size()]), container);
GrIntroduceHandlerBase.assertStatement(anchor, myContext.getScope());
}
PsiElement occurrence = replaced.get(0);
if (!mySettings.replaceAllOccurrences() && !isRefToField(occurrence, field) && PsiUtil.isExpressionStatement(occurrence)) {
generateAssignment(field, (GrStatement)anchor, container, occurrence);
}
else {
generateAssignment(field, (GrStatement)anchor, container, null);
}
}
private static boolean isRefToField(@NotNull PsiElement occurrence, @NotNull PsiElement field) {
return occurrence instanceof GrReferenceExpression && ((GrReferenceExpression)occurrence).resolve() == field;
}
void initializeInConstructor(@NotNull GrVariable field) {
final PsiClass scope = (PsiClass)myContext.getScope();
if (scope instanceof GrAnonymousClassDefinition) {
initializeInAnonymousClassInitializer(field, (GrAnonymousClassDefinition)scope);
}
else {
initializeInConstructor(field, scope);
}
}
private void initializeInConstructor(@NotNull GrVariable field, @NotNull PsiClass scope) {
PsiMethod[] constructors = scope.getConstructors();
if (constructors.length == 0) {
constructors = new PsiMethod[]{generateConstructor(scope)};
}
for (PsiMethod constructor : constructors) {
final GrConstructorInvocation invocation = PsiImplUtil.getChainingConstructorInvocation((GrMethod)constructor);
if (invocation != null && invocation.isThisCall()) continue;
final PsiElement anchor = findAnchorForAssignment(((GrMethod)constructor).getBlock());
generateAssignment(field, (GrStatement)anchor, ((GrMethod)constructor).getBlock(), null);
}
}
@NotNull
private PsiMethod generateConstructor(@NotNull PsiClass scope) {
final String name = scope.getName();
LOG.assertTrue(name != null, scope.getText());
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myContext.getProject());
final GrMethod
constructor = factory.createConstructorFromText(name, ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_STRING_ARRAY, "{}", scope);
if (scope instanceof GroovyScriptClass) constructor.getModifierList().setModifierProperty(GrModifier.DEF, true);
return (PsiMethod)scope.add(constructor);
}
private void initializeInAnonymousClassInitializer(@NotNull GrVariable field, @NotNull GrAnonymousClassDefinition scope) {
final GrClassInitializer[] initializers = scope.getInitializers();
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myContext.getProject());
final GrClassInitializer initializer = initializers.length == 0 ? (GrClassInitializer)scope.add(factory.createClassInitializer())
: initializers[0];
final PsiElement anchor = findAnchorForAssignment(initializer.getBlock());
generateAssignment(field, (GrStatement)anchor, initializer.getBlock(), null);
}
private void generateAssignment(@NotNull GrVariable field,
@Nullable GrStatement anchor,
@NotNull GrStatementOwner defaultContainer,
@Nullable PsiElement occurrenceToDelete) {
if (myInitializer == null) return;
GrAssignmentExpression init = (GrAssignmentExpression)GroovyPsiElementFactory.getInstance(myContext.getProject())
.createExpressionFromText(mySettings.getName() + " = " + myInitializer.getText());
GrStatementOwner block;
if (anchor != null) {
anchor = GroovyRefactoringUtil.addBlockIntoParent(anchor);
LOG.assertTrue(anchor.getParent() instanceof GrStatementOwner);
block = (GrStatementOwner)anchor.getParent();
}
else {
block = defaultContainer;
}
init = (GrAssignmentExpression)block.addStatementBefore(init, anchor);
replaceOccurrence(field, init.getLValue(), (PsiClass)myContext.getScope());
if (occurrenceToDelete != null) {
occurrenceToDelete.delete();
}
}
@Nullable
private GrExpression extractVarInitializer() {
assert myLocalVariable != null;
return myLocalVariable.getInitializerGroovy();
}
@Nullable
private PsiElement findAnchorForAssignment(final GrCodeBlock block) {
final List<PsiElement> elements = ContainerUtil.findAll(myContext.getOccurrences(), new Condition<PsiElement>() {
@Override
public boolean value(PsiElement element) {
return PsiTreeUtil.isAncestor(block, element, true);
}
});
if (elements.isEmpty()) return null;
return GrIntroduceHandlerBase.findAnchor(ContainerUtil.toArray(elements, new PsiElement[elements.size()]), block);
}
@NotNull
private PsiElement replaceOccurrence(@NotNull GrVariable field, @NotNull PsiElement occurrence, @NotNull PsiClass containingClass) {
boolean isOriginal = occurrence == myContext.getExpression();
final GrReferenceExpression newExpr = createRefExpression(field, occurrence, containingClass);
final PsiElement replaced;
if (occurrence instanceof GrExpression) {
replaced = ((GrExpression)occurrence).replaceWithExpression(newExpr, false);
}
else {
replaced = occurrence.replace(newExpr);
}
if (replaced instanceof GrQualifiedReference<?>) {
GrReferenceAdjuster.shortenReference((GrQualifiedReference<?>)replaced);
}
if (isOriginal) {
updateCaretPosition(replaced);
}
return replaced;
}
@NotNull
private static GrReferenceExpression createRefExpression(@NotNull GrVariable field,
@NotNull PsiElement place,
@NotNull PsiClass containingClass) {
final String qname = containingClass.getQualifiedName();
final String prefix = qname != null ? qname + "." : "";
final String refText;
if (field.hasModifierProperty(PsiModifier.STATIC)) {
refText = prefix + field.getName();
}
else {
refText = prefix + "this." + field.getName();
}
return GroovyPsiElementFactory.getInstance(place.getProject()).createReferenceExpressionFromText(refText, place);
}
@NotNull
private GrVariableDeclaration createField(@NotNull PsiClass targetClass) {
final String name = mySettings.getName();
final PsiType type = mySettings.getSelectedType();
final String modifier = mySettings.getVisibilityModifier();
List<String> modifiers = new ArrayList<String>();
if (targetClass instanceof GroovyScriptClass) {
modifiers.add("@" + GroovyCommonClassNames.GROOVY_TRANSFORM_FIELD);
}
if (mySettings.isStatic()) modifiers.add(PsiModifier.STATIC);
if (!PsiModifier.PACKAGE_LOCAL.equals(modifier)) modifiers.add(modifier);
if (mySettings.declareFinal()) modifiers.add(PsiModifier.FINAL);
final String[] arr_modifiers = ArrayUtil.toStringArray(modifiers);
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myContext.getProject());
if (targetClass instanceof GroovyScriptClass) {
return factory.createVariableDeclaration(arr_modifiers, ((GrExpression)null), type, name);
}
else {
return factory.createFieldDeclaration(arr_modifiers, name, null, type);
}
}
@Nullable
protected GrExpression getInitializer() {
if (mySettings.removeLocalVar()) {
return extractVarInitializer();
}
GrExpression expression = myContext.getExpression();
StringPartInfo stringPart = myContext.getStringPart();
if (expression != null) {
return expression;
}
else if (stringPart != null) {
return stringPart.createLiteralFromSelected();
}
throw new IncorrectOperationException("cannot be here!");
}
}