blob: c5bd61eeb6a53bbdcb80bd8a0dd7738d7276d58a [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.inline;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.lang.Language;
import com.intellij.lang.refactoring.InlineActionHandler;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.RefactoringMessageDialog;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
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.members.GrMember;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
import java.util.ArrayList;
import java.util.BitSet;
/**
* @author Max Medvedev
*/
public class GroovyInlineLocalHandler extends InlineActionHandler {
private static final Logger LOG = Logger.getInstance(GroovyInlineLocalHandler.class);
public static final String INLINE_VARIABLE = RefactoringBundle.message("inline.variable.title");
@Override
public boolean isEnabledForLanguage(Language l) {
return GroovyLanguage.INSTANCE == l;
}
@Override
public boolean canInlineElement(PsiElement element) {
return PsiUtil.isLocalVariable(element);
}
@Override
public void inlineElement(final Project project, Editor editor, final PsiElement element) {
invoke(project, editor, (GrVariable)element);
}
public static void invoke(final Project project, Editor editor, final GrVariable local) {
final PsiReference invocationReference = editor != null ? TargetElementUtilBase.findReference(editor) : null;
final InlineLocalVarSettings localVarSettings = createSettings(local, editor, invocationReference != null);
if (localVarSettings == null) return;
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, local)) return;
final GroovyInlineLocalProcessor processor = new GroovyInlineLocalProcessor(project, localVarSettings, local);
processor.setPrepareSuccessfulSwingThreadCallback(new Runnable() {
@Override
public void run() {
//do nothing
}
});
processor.run();
}
/**
* Returns Settings object for referenced definition in case of local variable
*/
@Nullable
private static InlineLocalVarSettings createSettings(final GrVariable variable, Editor editor, boolean invokedOnReference) {
final String localName = variable.getName();
final Project project = variable.getProject();
GrExpression initializer = null;
Instruction writeInstr = null;
Instruction[] flow = null;
//search for initializer to inline
if (invokedOnReference) {
LOG.assertTrue(editor != null, "null editor but invokedOnReference==true");
final PsiReference ref = TargetElementUtilBase.findReference(editor);
LOG.assertTrue(ref != null);
PsiElement cur = ref.getElement();
if (cur instanceof GrReferenceExpression) {
GrControlFlowOwner controlFlowOwner;
do {
controlFlowOwner = ControlFlowUtils.findControlFlowOwner(cur);
if (controlFlowOwner == null) break;
flow = controlFlowOwner.getControlFlow();
final ArrayList<BitSet> writes = ControlFlowUtils.inferWriteAccessMap(flow, variable);
final PsiElement finalCur = cur;
Instruction instruction = ControlFlowUtils.findInstruction(finalCur, flow);
LOG.assertTrue(instruction != null);
final BitSet prev = writes.get(instruction.num());
if (prev.cardinality() == 1) {
writeInstr = flow[prev.nextSetBit(0)];
final PsiElement element = writeInstr.getElement();
if (element instanceof GrVariable) {
initializer = ((GrVariable)element).getInitializerGroovy();
}
else if (element instanceof GrReferenceExpression) {
initializer = TypeInferenceHelper.getInitializerFor((GrReferenceExpression)element);
}
}
PsiElement old_cur = cur;
if (controlFlowOwner instanceof GrClosableBlock) {
cur = controlFlowOwner;
}
else {
PsiElement parent = controlFlowOwner.getParent();
if (parent instanceof GrMember) cur = ((GrMember)parent).getContainingClass();
}
if (cur == old_cur) break;
}
while (initializer == null);
}
}
else {
flow = ControlFlowUtils.findControlFlowOwner(variable).getControlFlow();
initializer = variable.getInitializerGroovy();
writeInstr = ContainerUtil.find(flow, new Condition<Instruction>() {
@Override
public boolean value(Instruction instruction) {
return instruction.getElement() == variable;
}
});
}
if (initializer == null || writeInstr == null) {
String message = GroovyRefactoringBundle.message("cannot.find.a.single.definition.to.inline.local.var");
CommonRefactoringUtil.showErrorHint(variable.getProject(), editor, message, INLINE_VARIABLE, HelpID.INLINE_VARIABLE);
return null;
}
int writeInstructionNumber = writeInstr.num();
if (ApplicationManager.getApplication().isUnitTestMode()) {
return new InlineLocalVarSettings(initializer, writeInstructionNumber, flow);
}
final String question = GroovyRefactoringBundle.message("inline.local.variable.prompt.0.1", localName);
RefactoringMessageDialog dialog = new RefactoringMessageDialog(INLINE_VARIABLE, question, HelpID.INLINE_VARIABLE, "OptionPane.questionIcon", true, project);
dialog.show();
if (dialog.isOK()) {
return new InlineLocalVarSettings(initializer, writeInstructionNumber, flow);
}
return null;
}
}