blob: dcae6c51d727155cb25024b84de08665166592fa [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.
*/
/**
* Created by IntelliJ IDEA.
* User: cdr
* Date: Nov 19, 2002
* Time: 12:03:39 PM
* To change this template use Options | File Templates.
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.IntentionAction;
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.JavaCodeStyleManager;
import com.intellij.psi.controlFlow.*;
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.List;
public class DeferFinalAssignmentFix implements IntentionAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.DeferFinalAssignmentFix");
private final PsiVariable variable;
private final PsiReferenceExpression expression;
public DeferFinalAssignmentFix(@NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression) {
this.variable = variable;
this.expression = expression;
}
@Override
@NotNull
public String getFamilyName() {
return QuickFixBundle.message("defer.final.assignment.with.temp.family");
}
@Override
@NotNull
public String getText() {
return QuickFixBundle.message("defer.final.assignment.with.temp.text", variable.getName());
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
if (!FileModificationService.getInstance().prepareFileForWrite(variable.getContainingFile())) return;
if (variable instanceof PsiField) {
deferField((PsiField)variable);
}
else {
deferLocalVariable((PsiLocalVariable)variable);
}
}
private void deferField(PsiField field) throws IncorrectOperationException {
PsiCodeBlock codeBlock = getEnclosingCodeBlock(field, expression);
if (codeBlock == null) return;
deferVariable(codeBlock, field, null);
}
private static PsiCodeBlock getEnclosingCodeBlock(PsiField field, PsiElement element) {
PsiClass aClass = field.getContainingClass();
if (aClass == null) return null;
PsiMethod[] constructors = aClass.getConstructors();
for (PsiMethod constructor : constructors) {
PsiCodeBlock body = constructor.getBody();
if (body == null) continue;
if (PsiTreeUtil.isAncestor(body, element, true)) return body;
}
//maybe inside class initalizer ?
PsiClassInitializer[] initializers = aClass.getInitializers();
for (PsiClassInitializer initializer : initializers) {
PsiCodeBlock body = initializer.getBody();
if (PsiTreeUtil.isAncestor(body, element, true)) return body;
}
return null;
}
private void deferLocalVariable(PsiLocalVariable variable) throws IncorrectOperationException {
PsiElement outerCodeBlock = PsiUtil.getVariableCodeBlock(variable, null);
deferVariable(outerCodeBlock, variable, variable.getParent());
}
private void deferVariable(PsiElement outerCodeBlock, PsiVariable variable, PsiElement tempDeclarationAnchor) throws IncorrectOperationException {
if (outerCodeBlock == null) return;
List<PsiReferenceExpression> outerReferences = new ArrayList<PsiReferenceExpression>();
collectReferences(outerCodeBlock, variable, outerReferences);
PsiElementFactory factory = JavaPsiFacade.getInstance(variable.getProject()).getElementFactory();
Project project = variable.getProject();
String tempName = suggestNewName(project, variable);
PsiDeclarationStatement tempVariableDeclaration = factory.createVariableDeclarationStatement(tempName, variable.getType(), null);
ControlFlow controlFlow;
try {
controlFlow = ControlFlowFactory.getInstance(project).getControlFlow(outerCodeBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false);
}
catch (AnalysisCanceledException e) {
return;
}
int minOffset = 0;
boolean writeReferenceOccurred = false;
PsiReferenceExpression writeReference = null;
for (int i = outerReferences.size()-1; i>=0; i--) {
PsiReferenceExpression reference = outerReferences.get(i);
if (!writeReferenceOccurred && !PsiUtil.isAccessedForWriting(reference)) {
// trailing read references need not be converted to temp var references
outerReferences.remove(i);
continue;
}
writeReferenceOccurred = true;
writeReference = reference;
PsiElement element = PsiUtil.getEnclosingStatement(reference);
int endOffset = element == null ? -1 : controlFlow.getEndOffset(element);
minOffset = Math.max(minOffset, endOffset);
}
LOG.assertTrue(writeReference != null);
PsiStatement finalAssignment = factory.createStatementFromText(writeReference.getText()+" = "+tempName+";", outerCodeBlock);
if (!insertToDefinitelyReachedPlace(outerCodeBlock, finalAssignment, controlFlow, minOffset, outerReferences)) return;
outerCodeBlock.addAfter(tempVariableDeclaration, tempDeclarationAnchor);
replaceReferences(outerReferences, factory.createExpressionFromText(tempName, outerCodeBlock));
}
private static boolean insertToDefinitelyReachedPlace(PsiElement codeBlock,
PsiStatement finalAssignment,
ControlFlow controlFlow,
int minOffset,
List references) throws IncorrectOperationException {
int offset = ControlFlowUtil.getMinDefinitelyReachedOffset(controlFlow, minOffset, references);
if (offset == controlFlow.getSize()) {
codeBlock.add(finalAssignment);
return true;
}
PsiElement element = null; //controlFlow.getEndOffset(codeBlock) == offset ? getEnclosingStatement(controlFlow.getElement(offset)) : null;
while (offset < controlFlow.getSize()) {
element = controlFlow.getElement(offset);
if (element != null) element = PsiUtil.getEnclosingStatement(element);
int startOffset = controlFlow.getStartOffset(element);
if (startOffset != -1 && startOffset >= minOffset && element instanceof PsiStatement) break;
offset++;
}
if (!(offset < controlFlow.getSize())) return false;
// inside loop
if (ControlFlowUtil.isInstructionReachable(controlFlow, offset, offset)) return false;
codeBlock.addBefore(finalAssignment, element);
return true;
}
private static void replaceReferences(List references, PsiElement newExpression) throws IncorrectOperationException {
for (Object reference1 : references) {
PsiElement reference = (PsiElement)reference1;
reference.replace(newExpression);
}
}
private static void collectReferences(PsiElement context, final PsiVariable variable, final List<PsiReferenceExpression> references) {
context.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
if (expression.resolve() == variable) references.add(expression);
super.visitReferenceExpression(expression);
}
});
}
private static String suggestNewName(Project project, PsiVariable variable) {
// new name should not conflict with another variable at the variable declaration level and usage level
String name = variable.getName();
// trim last digit to suggest variable names like i1,i2, i3...
if (name.length() > 1 && Character.isDigit(name.charAt(name.length()-1))) {
name = name.substring(0,name.length()-1);
}
return JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(name, variable, true);
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return
variable.isValid() &&
!(variable instanceof PsiParameter) &&
!(variable instanceof ImplicitVariable) &&
expression.isValid() &&
variable.getManager().isInProject(variable)
;
}
@Override
public boolean startInWriteAction() {
return true;
}
}