blob: 5b43bd9d35a65f61c8f92b23704f55e08c748d4b [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.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.usageView.BaseUsageViewDescriptor;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrClassInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
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.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.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrVariableDeclarationOwner;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ControlFlowBuilder;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
/**
* @author Max Medvedev
*/
public class GroovyInlineLocalProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance(GroovyInlineLocalProcessor.class);
private final InlineLocalVarSettings mySettings;
private final GrVariable myLocal;
public GroovyInlineLocalProcessor(Project project, InlineLocalVarSettings settings, GrVariable local) {
super(project);
this.mySettings = settings;
this.myLocal = local;
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new BaseUsageViewDescriptor(myLocal);
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final UsageInfo[] usages = refUsages.get();
for (UsageInfo usage : usages) {
collectConflicts(usage.getReference(), conflicts);
}
return showConflicts(conflicts, usages);
}
@Override
protected boolean isPreviewUsages(UsageInfo[] usages) {
for (UsageInfo usage : usages) {
if (usage instanceof ClosureUsage) return true;
}
return false;
}
private void collectConflicts(final PsiReference reference, final MultiMap<PsiElement, String> conflicts) {
GrExpression expr = (GrExpression)reference.getElement();
if (PsiUtil.isAccessedForWriting(expr)) {
conflicts.putValue(expr, GroovyRefactoringBundle.message("variable.is.accessed.for.writing", myLocal.getName()));
}
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
final Instruction[] controlFlow = mySettings.getFlow();
final ArrayList<BitSet> writes = ControlFlowUtils.inferWriteAccessMap(controlFlow, myLocal);
ArrayList<UsageInfo> toInline = new ArrayList<UsageInfo>();
collectRefs(myLocal, controlFlow, writes, mySettings.getWriteInstructionNumber(), toInline);
return toInline.toArray(new UsageInfo[toInline.size()]);
}
/**
* ClosureUsage represents usage of local var inside closure
*/
private static class ClosureUsage extends UsageInfo {
private ClosureUsage(@NotNull PsiReference reference) {
super(reference);
}
}
private static void collectRefs(final GrVariable variable,
Instruction[] flow,
final ArrayList<BitSet> writes,
final int writeInstructionNumber,
final ArrayList<UsageInfo> toInline) {
for (Instruction instruction : flow) {
final PsiElement element = instruction.getElement();
if (instruction instanceof ReadWriteVariableInstruction) {
if (((ReadWriteVariableInstruction)instruction).isWrite()) continue;
if (element instanceof GrVariable && element != variable) continue;
if (!(element instanceof GrReferenceExpression)) continue;
final GrReferenceExpression ref = (GrReferenceExpression)element;
if (ref.isQualified() || ref.resolve() != variable) continue;
final BitSet prev = writes.get(instruction.num());
if (writeInstructionNumber >= 0 && prev.cardinality() == 1 && prev.get(writeInstructionNumber)) {
toInline.add(new UsageInfo(ref));
}
else if (writeInstructionNumber == -1 && prev.cardinality() == 0) {
toInline.add(new ClosureUsage(ref));
}
}
else if (element instanceof GrClosableBlock) {
final BitSet prev = writes.get(instruction.num());
if (writeInstructionNumber >= 0 && prev.cardinality() == 1 && prev.get(writeInstructionNumber) ||
writeInstructionNumber == -1 && prev.cardinality() == 0) {
final Instruction[] closureFlow = ((GrClosableBlock)element).getControlFlow();
collectRefs(variable, closureFlow, ControlFlowUtils.inferWriteAccessMap(closureFlow, variable), -1, toInline);
}
}
else if (element instanceof GrAnonymousClassDefinition) {
final BitSet prev = writes.get(instruction.num());
if (writeInstructionNumber >= 0 && prev.cardinality() == 1 && prev.get(writeInstructionNumber) ||
writeInstructionNumber == -1 && prev.cardinality() == 0) {
((GrAnonymousClassDefinition)element).acceptChildren(new GroovyRecursiveElementVisitor() {
@Override
public void visitField(GrField field) {
GrExpression initializer = field.getInitializerGroovy();
if (initializer != null) {
Instruction[] flow = new ControlFlowBuilder(field.getProject()).buildControlFlow(initializer);
collectRefs(variable, flow, ControlFlowUtils.inferWriteAccessMap(flow, variable), -1, toInline);
}
}
@Override
public void visitMethod(GrMethod method) {
GrOpenBlock block = method.getBlock();
if (block != null) {
Instruction[] flow = block.getControlFlow();
collectRefs(variable, flow, ControlFlowUtils.inferWriteAccessMap(flow, variable), -1, toInline);
}
}
@Override
public void visitClassInitializer(GrClassInitializer initializer) {
GrOpenBlock block = initializer.getBlock();
Instruction[] flow = block.getControlFlow();
collectRefs(variable, flow, ControlFlowUtils.inferWriteAccessMap(flow, variable), -1, toInline);
}
});
}
}
}
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
CommonRefactoringUtil.sortDepthFirstRightLeftOrder(usages);
final GrExpression initializer = mySettings.getInitializer();
GrExpression initializerToUse = GrIntroduceHandlerBase.insertExplicitCastIfNeeded(myLocal, mySettings.getInitializer());
for (UsageInfo usage : usages) {
GrVariableInliner.inlineReference(usage, myLocal, initializerToUse);
}
final PsiElement initializerParent = initializer.getParent();
if (initializerParent instanceof GrAssignmentExpression) {
initializerParent.delete();
return;
}
if (initializerParent instanceof GrVariable) {
final Collection<PsiReference> all = ReferencesSearch.search(myLocal).findAll();
if (!all.isEmpty()) {
initializer.delete();
return;
}
}
final PsiElement owner = myLocal.getParent().getParent();
if (owner instanceof GrVariableDeclarationOwner) {
((GrVariableDeclarationOwner)owner).removeVariable(myLocal);
}
else {
myLocal.delete();
}
}
@Override
protected String getCommandName() {
return RefactoringBundle.message("inline.command", myLocal.getName());
}
}