blob: 0132b1651107de2bc49b145a3e0c6894fcb9fd69 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author cdr
*/
public class MoveFieldAssignmentToInitializerAction extends BaseIntentionAction {
@Override
@NotNull
public String getFamilyName() {
return getText();
}
@Override
@NotNull
public String getText() {
return CodeInsightBundle.message("intention.move.field.assignment.to.declaration");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
PsiAssignmentExpression assignment = getAssignmentUnderCaret(editor, file);
if (assignment == null) return false;
PsiElement parent = assignment.getParent();
if (!(parent instanceof PsiExpressionStatement)) return false;
PsiField field = getAssignedField(assignment);
if (field == null || field.hasInitializer()) return false;
PsiClass psiClass = field.getContainingClass();
if (psiClass == null || psiClass.isInterface()) return false;
if (psiClass.getContainingFile() != file) return false;
PsiModifierListOwner ctrOrInitializer = enclosingMethodOrClassInitializer(assignment, field);
if (ctrOrInitializer == null) return false;
if (ctrOrInitializer.hasModifierProperty(PsiModifier.STATIC) != field.hasModifierProperty(PsiModifier.STATIC)) return false;
if (!isValidAsFieldInitializer(assignment.getRExpression(), ctrOrInitializer)) return false;
if (!isInitializedWithSameExpression(field, assignment, new ArrayList<PsiAssignmentExpression>())) return false;
return true;
}
private static boolean isValidAsFieldInitializer(final PsiExpression initializer, final PsiModifierListOwner ctrOrInitializer) {
if (initializer == null) return false;
final Ref<Boolean> result = new Ref<Boolean>(Boolean.TRUE);
initializer.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
PsiElement resolved = expression.resolve();
if (resolved == null) return;
if (PsiTreeUtil.isAncestor(ctrOrInitializer, resolved, false) && !PsiTreeUtil.isAncestor(initializer, resolved, false)) {
// resolved somewhere inside constructor but outside initializer
result.set(Boolean.FALSE);
}
}
});
return result.get().booleanValue();
}
private static PsiModifierListOwner enclosingMethodOrClassInitializer(final PsiAssignmentExpression assignment, final PsiField field) {
PsiElement parentOwner = assignment;
while (true) {
parentOwner = PsiTreeUtil.getParentOfType(parentOwner, PsiModifierListOwner.class, true, PsiMember.class);
if (parentOwner == null) return null;
PsiElement parent = parentOwner.getParent();
if (parent == field.getContainingClass()) return (PsiModifierListOwner)parentOwner;
}
}
private static boolean isInitializedWithSameExpression(final PsiField field,
final PsiAssignmentExpression assignment,
final Collection<PsiAssignmentExpression> initializingAssignments) {
final PsiExpression expression = assignment.getRExpression();
if (expression == null) return false;
PsiClass containingClass = field.getContainingClass();
if (containingClass == null) return false;
final Ref<Boolean> result = new Ref<Boolean>(Boolean.TRUE);
final List<PsiAssignmentExpression> totalUsages = new ArrayList<PsiAssignmentExpression>();
containingClass.accept(new JavaRecursiveElementVisitor() {
private PsiCodeBlock currentInitializingBlock; //ctr or class initializer
@Override
public void visitCodeBlock(PsiCodeBlock block) {
PsiElement parent = block.getParent();
if (parent instanceof PsiClassInitializer || parent instanceof PsiMethod && ((PsiMethod)parent).isConstructor()) {
currentInitializingBlock = block;
super.visitCodeBlock(block);
currentInitializingBlock = null;
}
else {
super.visitCodeBlock(block);
}
}
@Override
public void visitReferenceExpression(PsiReferenceExpression reference) {
if (!result.get().booleanValue()) return;
super.visitReferenceExpression(reference);
if (!PsiUtil.isOnAssignmentLeftHand(reference)) return;
PsiElement resolved = reference.resolve();
if (resolved != field) return;
PsiAssignmentExpression assignmentExpression = PsiTreeUtil.getParentOfType(reference, PsiAssignmentExpression.class);
PsiExpression rValue = assignmentExpression.getRExpression();
if (currentInitializingBlock != null) {
// ignore usages other than initializing
if (rValue == null || !PsiEquivalenceUtil.areElementsEquivalent(rValue, expression)) {
result.set(Boolean.FALSE);
}
initializingAssignments.add(assignmentExpression);
}
totalUsages.add(assignment);
}
});
// the only assignment is OK
if (totalUsages.size() == 1 && initializingAssignments.isEmpty()) {
initializingAssignments.addAll(totalUsages);
return true;
}
return result.get().booleanValue();
}
private static PsiField getAssignedField(final PsiAssignmentExpression assignment) {
PsiExpression lExpression = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression());
if (!(lExpression instanceof PsiReferenceExpression)) return null;
PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
if (!(resolved instanceof PsiField)) return null;
return (PsiField)resolved;
}
private static PsiAssignmentExpression getAssignmentUnderCaret(final Editor editor, final PsiFile file) {
int offset = editor.getCaretModel().getOffset();
PsiElement element = file.findElementAt(offset);
if (element == null || element instanceof PsiCompiledElement) return null;
return PsiTreeUtil.getParentOfType(element, PsiAssignmentExpression.class, false, PsiMember.class);
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
PsiAssignmentExpression assignment = getAssignmentUnderCaret(editor, file);
if (assignment == null) return;
PsiField field = getAssignedField(assignment);
if (field == null) return;
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
List<PsiAssignmentExpression> assignments = new ArrayList<PsiAssignmentExpression>();
if (!isInitializedWithSameExpression(field, assignment, assignments)) return;
PsiExpression initializer = assignment.getRExpression();
field.setInitializer(initializer);
for (PsiAssignmentExpression assignmentExpression : assignments) {
PsiElement statement = assignmentExpression.getParent();
PsiElement parent = statement.getParent();
if (parent instanceof PsiIfStatement ||
parent instanceof PsiWhileStatement ||
parent instanceof PsiForStatement ||
parent instanceof PsiForeachStatement) {
PsiStatement emptyStatement =
JavaPsiFacade.getInstance(file.getProject()).getElementFactory().createStatementFromText(";", statement);
statement.replace(emptyStatement);
}
else {
statement.delete();
}
}
TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
HighlightManager.getInstance(project).addOccurrenceHighlights(editor, new PsiElement[]{field.getInitializer()}, attributes, false, null);
}
}