| /* |
| * 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()); |
| } |
| } |