blob: 0b31eedf8be03701eff95793882293db20e7174d [file] [log] [blame]
/*
* Copyright 2000-2009 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 com.intellij.refactoring.introduceField;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil;
import com.intellij.codeInsight.navigation.NavigationUtil;
import com.intellij.ide.util.PsiClassListCellRenderer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.EnumConstantsUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.psi.util.FileTypeUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.refactoring.introduceField.BaseExpressionToFieldHandler.InitializationPlace.IN_CONSTRUCTOR;
import static com.intellij.refactoring.introduceField.BaseExpressionToFieldHandler.InitializationPlace.IN_FIELD_DECLARATION;
public abstract class LocalToFieldHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceField.LocalToFieldHandler");
private static final String REFACTORING_NAME = RefactoringBundle.message("convert.local.to.field.title");
private final Project myProject;
private final boolean myIsConstant;
public LocalToFieldHandler(Project project, boolean isConstant) {
myProject = project;
myIsConstant = isConstant;
}
protected abstract BaseExpressionToFieldHandler.Settings showRefactoringDialog(PsiClass aClass, PsiLocalVariable local, PsiExpression[] occurences, boolean isStatic);
public boolean convertLocalToField(final PsiLocalVariable local, final Editor editor) {
boolean tempIsStatic = myIsConstant;
PsiElement parent = local.getParent();
final List<PsiClass> classes = new ArrayList<PsiClass>();
while (parent != null && parent.getContainingFile() != null) {
if (parent instanceof PsiClass && !(myIsConstant && parent instanceof PsiAnonymousClass)) {
classes.add((PsiClass)parent);
}
if (parent instanceof PsiFile && FileTypeUtils.isInServerPageFile(parent)) {
String message = RefactoringBundle.message("error.not.supported.for.jsp", REFACTORING_NAME);
CommonRefactoringUtil.showErrorHint(myProject, editor, message, REFACTORING_NAME, HelpID.LOCAL_TO_FIELD);
return false;
}
if (parent instanceof PsiModifierListOwner &&((PsiModifierListOwner)parent).hasModifierProperty(PsiModifier.STATIC)) {
tempIsStatic = true;
}
parent = parent.getParent();
}
if (classes.isEmpty()) return false;
final AbstractInplaceIntroducer activeIntroducer = AbstractInplaceIntroducer.getActiveIntroducer(editor);
final boolean shouldSuggestDialog = activeIntroducer instanceof InplaceIntroduceConstantPopup &&
activeIntroducer.startsOnTheSameElement(null, local);
if (classes.size() == 1 || ApplicationManager.getApplication().isUnitTestMode() || shouldSuggestDialog) {
if (convertLocalToField(local, classes.get(getChosenClassIndex(classes)), editor, tempIsStatic)) return false;
} else {
final boolean isStatic = tempIsStatic;
final PsiClass firstClass = classes.get(0);
final PsiClass preselection = AnonymousTargetClassPreselectionUtil.getPreselection(classes, firstClass);
NavigationUtil.getPsiElementPopup(classes.toArray(new PsiClass[classes.size()]), new PsiClassListCellRenderer(), "Choose class to introduce " + (myIsConstant ? "constant" : "field"), new PsiElementProcessor<PsiClass>() {
@Override
public boolean execute(@NotNull PsiClass aClass) {
AnonymousTargetClassPreselectionUtil.rememberSelection(aClass, aClass);
convertLocalToField(local, aClass, editor, isStatic);
return false;
}
}, preselection).showInBestPositionFor(editor);
}
return true;
}
protected int getChosenClassIndex(List<PsiClass> classes) {
return classes.size() - 1;
}
private boolean convertLocalToField(PsiLocalVariable local, PsiClass aClass, Editor editor, boolean isStatic) {
final PsiExpression[] occurences = CodeInsightUtil.findReferenceExpressions(RefactoringUtil.getVariableScope(local), local);
if (editor != null) {
RefactoringUtil.highlightAllOccurrences(myProject, occurences, editor);
}
final BaseExpressionToFieldHandler.Settings settings = showRefactoringDialog(aClass, local, occurences, isStatic);
if (settings == null) return true;
//LocalToFieldDialog dialog = new LocalToFieldDialog(project, aClass, local, isStatic);
final PsiClass destinationClass = settings.getDestinationClass();
boolean rebindNeeded = false;
if (destinationClass != null) {
aClass = destinationClass;
rebindNeeded = true;
}
final PsiClass aaClass = aClass;
final boolean rebindNeeded1 = rebindNeeded;
final Runnable runnable =
new IntroduceFieldRunnable(rebindNeeded1, local, aaClass, settings, isStatic, occurences);
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(runnable);
}
}, REFACTORING_NAME, null);
return false;
}
private static PsiField createField(PsiLocalVariable local, PsiType forcedType, String fieldName, boolean includeInitializer) {
@NonNls StringBuilder pattern = new StringBuilder();
pattern.append("private int ");
pattern.append(fieldName);
if (local.getInitializer() == null) {
includeInitializer = false;
}
if (includeInitializer) {
pattern.append("=0");
}
pattern.append(";");
final Project project = local.getProject();
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
try {
PsiField field = factory.createFieldFromText(pattern.toString(), null);
field.getTypeElement().replace(factory.createTypeElement(forcedType));
if (includeInitializer) {
PsiExpression initializer =
RefactoringUtil.convertInitializerToNormalExpression(local.getInitializer(), forcedType);
field.getInitializer().replace(initializer);
}
for (PsiAnnotation annotation : local.getModifierList().getAnnotations()) {
field.getModifierList().add(annotation.copy());
}
return field;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
private static PsiExpressionStatement createAssignment(PsiLocalVariable local, String fieldname, PsiElementFactory factory) {
try {
String pattern = fieldname + "=0;";
PsiExpressionStatement statement = (PsiExpressionStatement)factory.createStatementFromText(pattern, null);
statement = (PsiExpressionStatement)CodeStyleManager.getInstance(local.getProject()).reformat(statement);
PsiAssignmentExpression expr = (PsiAssignmentExpression)statement.getExpression();
final PsiExpression initializer = RefactoringUtil.convertInitializerToNormalExpression(local.getInitializer(), local.getType());
expr.getRExpression().replace(initializer);
return statement;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
private static PsiStatement addInitializationToSetUp(final PsiLocalVariable local, final PsiField field, final PsiElementFactory factory)
throws IncorrectOperationException {
PsiMethod inClass = TestFrameworks.getInstance().findOrCreateSetUpMethod(field.getContainingClass());
assert inClass != null;
PsiStatement assignment = createAssignment(local, field.getName(), factory);
final PsiCodeBlock body = inClass.getBody();
assert body != null;
if (PsiTreeUtil.isAncestor(body, local, false)) {
assignment = (PsiStatement)body.addBefore(assignment, PsiTreeUtil.getParentOfType(local, PsiStatement.class));
} else {
assignment = (PsiStatement)body.add(assignment);
}
local.delete();
return assignment;
}
private static PsiStatement addInitializationToConstructors(PsiLocalVariable local, PsiField field, PsiMethod enclosingConstructor,
PsiElementFactory factory) throws IncorrectOperationException {
PsiClass aClass = field.getContainingClass();
PsiMethod[] constructors = aClass.getConstructors();
PsiStatement assignment = createAssignment(local, field.getName(), factory);
boolean added = false;
for (PsiMethod constructor : constructors) {
if (constructor == enclosingConstructor) continue;
PsiCodeBlock body = constructor.getBody();
if (body == null) continue;
PsiStatement[] statements = body.getStatements();
if (statements.length > 0) {
PsiStatement first = statements[0];
if (first instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)first).getExpression();
if (expression instanceof PsiMethodCallExpression) {
@NonNls String text = ((PsiMethodCallExpression)expression).getMethodExpression().getText();
if ("this".equals(text)) {
continue;
}
if ("super".equals(text) && enclosingConstructor == null && PsiTreeUtil.isAncestor(constructor, local, false)) {
local.delete();
return (PsiStatement)body.addAfter(assignment, first);
}
}
}
if (enclosingConstructor == null && PsiTreeUtil.isAncestor(constructor, local, false)) {
local.delete();
return (PsiStatement)body.addBefore(assignment, first);
}
}
assignment = (PsiStatement)body.add(assignment);
added = true;
}
if (!added && enclosingConstructor == null) {
if (aClass instanceof PsiAnonymousClass) {
final PsiClassInitializer classInitializer = (PsiClassInitializer)aClass.addAfter(factory.createClassInitializer(), field);
assignment = (PsiStatement)classInitializer.getBody().add(assignment);
} else {
PsiMethod constructor = (PsiMethod)aClass.add(factory.createConstructor());
assignment = (PsiStatement)constructor.getBody().add(assignment);
}
}
if (enclosingConstructor == null) local.delete();
return assignment;
}
static class IntroduceFieldRunnable implements Runnable {
private final String myVariableName;
private final String myFieldName;
private final boolean myRebindNeeded;
private final PsiLocalVariable myLocal;
private final Project myProject;
private final PsiClass myDestinationClass;
private final BaseExpressionToFieldHandler.Settings mySettings;
private final BaseExpressionToFieldHandler.InitializationPlace myInitializerPlace;
private final PsiExpression[] myOccurences;
private PsiField myField;
public IntroduceFieldRunnable(boolean rebindNeeded,
PsiLocalVariable local,
PsiClass aClass,
BaseExpressionToFieldHandler.Settings settings,
boolean isStatic,
PsiExpression[] occurrences) {
myVariableName = local.getName();
myFieldName = settings.getFieldName();
myRebindNeeded = rebindNeeded;
myLocal = local;
myProject = local.getProject();
myDestinationClass = aClass;
mySettings = settings;
myInitializerPlace = settings.getInitializerPlace();
myOccurences = occurrences;
}
public void run() {
try {
ChangeContextUtil.encodeContextInfo(myDestinationClass, true);
final boolean rebindNeeded2 = !myVariableName.equals(myFieldName) || myRebindNeeded;
final PsiReference[] refs;
if (rebindNeeded2) {
refs = ReferencesSearch.search(myLocal, GlobalSearchScope.projectScope(myProject), false).toArray(new PsiReference[0]);
}
else {
refs = null;
}
final PsiMethod enclosingConstructor = BaseExpressionToFieldHandler.getEnclosingConstructor(myDestinationClass, myLocal);
myField = mySettings.isIntroduceEnumConstant() ? EnumConstantsUtil.createEnumConstant(myDestinationClass, myLocal, myFieldName)
: createField(myLocal, mySettings.getForcedType(), myFieldName, myInitializerPlace == IN_FIELD_DECLARATION);
myField = (PsiField)myDestinationClass.add(myField);
BaseExpressionToFieldHandler.setModifiers(myField, mySettings);
if (!mySettings.isIntroduceEnumConstant()) {
VisibilityUtil.fixVisibility(myOccurences, myField, mySettings.getFieldVisibility());
}
myLocal.normalizeDeclaration();
PsiElement declarationStatement = myLocal.getParent();
final BaseExpressionToFieldHandler.InitializationPlace finalInitializerPlace;
if (myLocal.getInitializer() == null) {
finalInitializerPlace = IN_FIELD_DECLARATION;
}
else {
finalInitializerPlace = myInitializerPlace;
}
final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
switch (finalInitializerPlace) {
case IN_FIELD_DECLARATION:
declarationStatement.delete();
break;
case IN_CURRENT_METHOD:
PsiExpressionStatement statement = createAssignment(myLocal, myFieldName, factory);
if (declarationStatement instanceof PsiDeclarationStatement) {
declarationStatement.replace(statement);
} else {
myLocal.replace(statement.getExpression());
}
break;
case IN_CONSTRUCTOR:
addInitializationToConstructors(myLocal, myField, enclosingConstructor, factory);
break;
case IN_SETUP_METHOD:
addInitializationToSetUp(myLocal, myField, factory);
}
if (enclosingConstructor != null && myInitializerPlace == IN_CONSTRUCTOR) {
PsiStatement statement = createAssignment(myLocal, myFieldName, factory);
declarationStatement.replace(statement);
}
if (rebindNeeded2) {
for (final PsiReference reference : refs) {
if (reference != null) {
//expr = RefactoringUtil.outermostParenthesizedExpression(expr);
RefactoringUtil.replaceOccurenceWithFieldRef((PsiExpression)reference, myField, myDestinationClass);
//replaceOccurenceWithFieldRef((PsiExpression)reference, field, aaClass);
}
}
//RefactoringUtil.renameVariableReferences(local, pPrefix + fieldName, GlobalSearchScope.projectScope(myProject));
ChangeContextUtil.decodeContextInfo(myDestinationClass, myDestinationClass, null);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
public PsiField getField() {
return myField;
}
}
}