From 975174dd9d8346edb517f6aeae0690de540de46c Mon Sep 17 00:00:00 2001 From: Maxim Medvedev Date: Wed, 6 Jul 2011 15:35:38 +0400 Subject: [PATCH] IDEA-42116 inline local based on control flow improvemnt --- .../ReferencesToInlineSearcher.java | 2 +- .../inline/GenericInlineHandler.java | 8 +- .../utils/ControlFlowUtils.java | 114 ++++++++- .../impl/statements/GrVariableBaseImpl.java | 9 + .../plugins/groovy/lang/psi/util/PsiUtil.java | 21 ++ .../GroovyRefactoringBundle.properties | 3 +- .../refactoring/inline/GrInlineFieldUtil.java | 83 +++++++ .../refactoring/inline/GrVariableInliner.java | 13 +- .../inline/GroovyInlineHandler.java | 45 ++-- .../inline/GroovyInlineVariableUtil.java | 227 +++++++++++++----- .../inline/InlineLocalVarSettings.java | 57 +++++ .../inline/InlineVariableTest.java | 5 + .../refactoring/inlineLocal/partial1.test | 21 ++ .../refactoring/inlineLocal/partial2.test | 9 + .../refactoring/inlineLocal/partial3.test | 5 + .../refactoring/inlineLocal/partial4.test | 28 +++ 16 files changed, 560 insertions(+), 90 deletions(-) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrInlineFieldUtil.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/InlineLocalVarSettings.java create mode 100644 plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial1.test create mode 100644 plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial2.test create mode 100644 plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial3.test create mode 100644 plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial4.test diff --git a/platform/lang-api/src/com/intellij/lang/refactoring/ReferencesToInlineSearcher.java b/platform/lang-api/src/com/intellij/lang/refactoring/ReferencesToInlineSearcher.java index 7cacd11bd1cc..84e84313ac89 100644 --- a/platform/lang-api/src/com/intellij/lang/refactoring/ReferencesToInlineSearcher.java +++ b/platform/lang-api/src/com/intellij/lang/refactoring/ReferencesToInlineSearcher.java @@ -24,5 +24,5 @@ import java.util.Collection; * @author Max Medvedev */ public interface ReferencesToInlineSearcher { - Collection findReferences(PsiElement element); + Collection findReferences(PsiElement element, InlineHandler.Settings settings); } diff --git a/platform/lang-impl/src/com/intellij/refactoring/inline/GenericInlineHandler.java b/platform/lang-impl/src/com/intellij/refactoring/inline/GenericInlineHandler.java index fbef2ea18526..4ec3e43decc7 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/inline/GenericInlineHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/inline/GenericInlineHandler.java @@ -57,18 +57,18 @@ public class GenericInlineHandler { return settings != null; } - final Collection allReferences; + final Collection allReferences; if (settings.isOnlyOneReferenceToInline()) { allReferences = Collections.singleton(invocationReference); } else { - final Ref> usagesRef = new Ref>(); + final Ref> usagesRef = new Ref>(); ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { @Override public void run() { final ReferencesToInlineSearcher searcher = ReferencesToInlineSearchers.getSearcher(element.getLanguage()); if (searcher != null) { - usagesRef.set(searcher.findReferences(element)); + usagesRef.set(searcher.findReferences(element, settings)); } else { usagesRef.set(ReferencesSearch.search(element).findAll()); @@ -185,7 +185,7 @@ public class GenericInlineHandler { } //order of usages across different files is irrelevant - public static PsiReference[] sortDepthFirstRightLeftOrder(final Collection allReferences) { + public static PsiReference[] sortDepthFirstRightLeftOrder(final Collection allReferences) { final PsiReference[] usages = allReferences.toArray(new PsiReference[allReferences.size()]); Arrays.sort(usages, new Comparator() { public int compare(final PsiReference usage1, final PsiReference usage2) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/utils/ControlFlowUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/utils/ControlFlowUtils.java index 7e5bda7cd443..345f343355b5 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/utils/ControlFlowUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/utils/ControlFlowUtils.java @@ -15,9 +15,13 @@ */ package org.jetbrains.plugins.groovy.codeInspection.utils; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; @@ -41,21 +45,26 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrRefere import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; 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.controlFlow.impl.IfEndInstruction; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.MaybeReturnInstruction; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAEngine; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DfaInstance; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.Semilattice; +import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; @SuppressWarnings({"OverlyComplexClass"}) public class ControlFlowUtils { + private static final Logger LOG = Logger.getInstance(ControlFlowUtils.class); private ControlFlowUtils() { super(); } + public static boolean statementMayCompleteNormally( @Nullable GrStatement statement) { if (statement == null) { @@ -616,4 +625,103 @@ public class ControlFlowUtils { } return null; } + + /** + * searches for next or previous write access to local variable + * @param local variable to analyze + * @param place place to start searching + * @param ahead if true search for next write. if false searches for previous write + * @return all write instructions leading to (or preceding) the place + */ + public static ReadWriteVariableInstruction[] findWriteAccess(GrVariable local, final PsiElement place, boolean ahead) { + LOG.assertTrue(GroovyRefactoringUtil.isLocalVariable(local), local.getClass()); + + final GrControlFlowOwner owner = findControlFlowOwner(local); + LOG.assertTrue(owner != null); + + final Instruction cur = findInstruction(place, owner.getControlFlow()); + + if (cur == null) throw new IllegalArgumentException("place is not in the flow"); + + final ArrayList result = new ArrayList(); + final HashSet visited = new HashSet(); + visited.add(cur); + writeAccess(cur, local.getName(), visited, result, ahead); + return result.toArray(new ReadWriteVariableInstruction[result.size()]); + } + + @Nullable + private static Instruction findInstruction(final PsiElement place, Instruction[] controlFlow) { + return ContainerUtil.find(controlFlow, new Condition() { + @Override + public boolean value(Instruction instruction) { + return instruction.getElement() == place; + } + }); + } + + private static void writeAccess(Instruction cur, String name, Set visited, Collection result, boolean ahead) { + final Iterable toIterate = ahead ? cur.allSucc() : cur.allPred(); + for (Instruction i : toIterate) { + if (visited.contains(i)) continue; + visited.add(i); + if (i instanceof ReadWriteVariableInstruction && ((ReadWriteVariableInstruction)i).isWrite() && name.equals(((ReadWriteVariableInstruction)i).getVariableName())) { + result.add((ReadWriteVariableInstruction)i); + } + else { + writeAccess(i, name, visited, result, ahead); + } + } + } + + public static ArrayList inferWriteAccessMap(final Instruction[] flow, final GrVariable var) { + + final Semilattice sem = new Semilattice() { + @Override + public BitSet join(ArrayList ins) { + BitSet result = new BitSet(flow.length); + for (BitSet set : ins) { + result.or(set); + } + return result; + } + + @Override + public boolean eq(BitSet e1, BitSet e2) { + return e1.equals(e2); + } + }; + + DfaInstance dfa = new DfaInstance() { + @Override + public void fun(BitSet bitSet, Instruction instruction) { + if (!(instruction instanceof ReadWriteVariableInstruction)) return; + if (!((ReadWriteVariableInstruction)instruction).isWrite()) return; + + final PsiElement element = instruction.getElement(); + if (element instanceof GrVariable && element != var) return; + if (element instanceof GrReferenceExpression) { + final GrReferenceExpression ref = (GrReferenceExpression)element; + if (ref.isQualified() || ref.resolve() != var) return; + } + + bitSet.clear(); + bitSet.set(instruction.num()); + } + + @NotNull + @Override + public BitSet initial() { + return new BitSet(flow.length); + } + + @Override + public boolean isForward() { + return true; + } + }; + + return new DFAEngine(flow, dfa, sem).performDFA(); + } + } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrVariableBaseImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrVariableBaseImpl.java index 6e5bb9140756..547062029f76 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrVariableBaseImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrVariableBaseImpl.java @@ -312,4 +312,13 @@ public abstract class GrVariableBaseImpl extends GrStubEl } return null; } + + @Override + public void deleteChildInternal(@NotNull ASTNode child) { + final PsiElement psi = child.getPsi(); + if (psi == getInitializerGroovy()) { + deleteChildInternal(findChildByType(GroovyTokenTypes.mASSIGN).getNode()); + } + super.deleteChildInternal(child); + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/PsiUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/PsiUtil.java index c5866c323340..b76238b72cb2 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/PsiUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/PsiUtil.java @@ -1192,4 +1192,25 @@ public class PsiUtil { return !ref.isQualified() && name.equals(ref.getName()); } + + @Nullable + public static GrExpression getInitializerFor(GrReferenceExpression lValue) { + if (!isLValue(lValue)) throw new IllegalArgumentException("arg is not lValue"); + + final PsiElement parent = lValue.getParent(); + if (parent instanceof GrAssignmentExpression) return ((GrAssignmentExpression)parent).getRValue(); + if (parent instanceof GrTupleExpression) { + final int i = ((GrTupleExpression)parent).indexOf(lValue); + final PsiElement pparent = parent.getParent(); + LOG.assertTrue(pparent instanceof GrAssignmentExpression); + + final GrExpression rValue = ((GrAssignmentExpression)pparent).getRValue(); + if (rValue instanceof GrListOrMap && !((GrListOrMap)rValue).isMap()) { + final GrExpression[] initializers = ((GrListOrMap)rValue).getInitializers(); + if (initializers.length < i) return initializers[i]; + } + } + + return null; + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GroovyRefactoringBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GroovyRefactoringBundle.properties index 72861ed561e3..d7b37db44660 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GroovyRefactoringBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GroovyRefactoringBundle.properties @@ -43,7 +43,7 @@ cannot.find.a.single.definition.to.inline.local.var=Cannot find a single definit cannot.find.a.single.definition.to.inline.field=No initializer present for the field inline.local.command=Inline Variable local.varaible.is.lvalue=Variable to be inlined has occurrence as lvalue in assignment -inline.local.variable.prompt=Inline local variable ''{0}''? +inline.local.variable.prompt.0.1=Inline local variable ''{0}'' ({1,choice,1#1 occurrence|2#{1, number} occurrences})? inline.field.prompt=Inline field ''{0}''? ######################################################################################################################## @@ -162,3 +162,4 @@ files.to.be.converted=Files to be converted converting.files.to.java=Converting files to Java rename.groovy.property=Rename &Groovy property reference.to.accessor.0.is.used=Reference to accessor {0} cannot be inlined. +variable.is.accessed.for.writing=Variable ''{0}'' is accessed for writing diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrInlineFieldUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrInlineFieldUtil.java new file mode 100644 index 000000000000..f2d37e6276f7 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrInlineFieldUtil.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2011 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.lang.refactoring.InlineHandler; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiModifier; +import com.intellij.refactoring.HelpID; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; +import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle; + +/** + * @author Max Medvedev + */ +public class GrInlineFieldUtil { + + public static final String INLINE_FIELD = RefactoringBundle.message("inline.field.title"); + + private GrInlineFieldUtil() { + } + + @Nullable + static InlineHandler.Settings inlineFieldSettings(final GrField field, Editor editor, boolean invokedOnReference) { + final Project project = field.getProject(); + + if (!field.hasModifierProperty(PsiModifier.FINAL)) { + String message = RefactoringBundle.message("0.refactoring.is.supported.only.for.final.fields", INLINE_FIELD); + CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_FIELD, HelpID.INLINE_FIELD); + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + + if (field.getInitializerGroovy() == null) { + String message = GroovyRefactoringBundle.message("cannot.find.a.single.definition.to.inline.field"); + CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_FIELD, HelpID.INLINE_FIELD); + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + + return inlineFieldDialogResult(project, field, invokedOnReference); + } + + private static InlineHandler.Settings inlineFieldDialogResult(Project project, GrField field, final boolean invokedOnReference) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return new InlineHandler.Settings() { + @Override + public boolean isOnlyOneReferenceToInline() { + return invokedOnReference; + } + }; + } + + final InlineGroovyFieldDialog dialog = new InlineGroovyFieldDialog(project, field, invokedOnReference); + dialog.show(); + if (dialog.isOK()) { + return new InlineHandler.Settings() { + @Override + public boolean isOnlyOneReferenceToInline() { + return dialog.isInlineThisOnly(); + } + }; + } + else { + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrVariableInliner.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrVariableInliner.java index 40842fbf168e..8fc9e8d6413e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrVariableInliner.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GrVariableInliner.java @@ -50,12 +50,17 @@ public class GrVariableInliner implements InlineHandler.Inliner { private Project myProject; private final GrExpression myTempExpr; - public GrVariableInliner(GrVariable variable) { + public GrVariableInliner(GrVariable variable, InlineHandler.Settings settings) { myProject = variable.getProject(); - GrExpression initializer = variable.getInitializerGroovy(); - LOG.assertTrue(initializer != null); - + GrExpression initializer; + if (settings instanceof InlineLocalVarSettings) { + initializer = ((InlineLocalVarSettings)settings).getInitializer(); + } + else { + initializer = variable.getInitializerGroovy(); + LOG.assertTrue(initializer != null); + } myTempExpr = (GrExpression)skipParentheses(initializer, false); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineHandler.java index 8c45b5b06519..51453817feee 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineHandler.java @@ -49,14 +49,14 @@ public class GroovyInlineHandler implements InlineHandler, ReferencesToInlineSea public Settings prepareInlineElement(final PsiElement element, Editor editor, boolean invokedOnReference) { if (element instanceof GrVariable) { if (GroovyRefactoringUtil.isLocalVariable((GrVariable)element)) { - return GroovyInlineVariableUtil.inlineLocalVariableSettings((GrVariable)element, editor); + return GroovyInlineVariableUtil.inlineLocalVariableSettings((GrVariable)element, editor, invokedOnReference); } else if (element instanceof GrField) { - return GroovyInlineVariableUtil.inlineFieldSettings((GrField)element, editor, invokedOnReference); + return GrInlineFieldUtil.inlineFieldSettings((GrField)element, editor, invokedOnReference); } } else if (element instanceof GrAccessorMethod) { - return GroovyInlineVariableUtil.inlineFieldSettings(((GrAccessorMethod)element).getProperty(), editor, invokedOnReference); + return GrInlineFieldUtil.inlineFieldSettings(((GrAccessorMethod)element).getProperty(), editor, invokedOnReference); } else if (element instanceof GrMethod) { return GroovyInlineMethodUtil.inlineMethodSettings((GrMethod)element, editor, invokedOnReference); @@ -82,6 +82,11 @@ public class GroovyInlineHandler implements InlineHandler, ReferencesToInlineSea if (element instanceof GrAccessorMethod) { element = ((GrAccessorMethod)element).getProperty(); } + + if (element instanceof GrVariable && GroovyRefactoringUtil.isLocalVariable((GrVariable)element)) { + GroovyInlineVariableUtil.removeDefinition(element, settings); + return; + } final PsiElement owner = element.getParent().getParent(); if (element instanceof GrVariable && owner instanceof GrVariableDeclarationOwner) { ((GrVariableDeclarationOwner)owner).removeVariable(((GrVariable)element)); @@ -94,10 +99,10 @@ public class GroovyInlineHandler implements InlineHandler, ReferencesToInlineSea @Nullable public Inliner createInliner(PsiElement element, Settings settings) { if (element instanceof GrVariable) { - return GroovyInlineVariableUtil.createInlinerForVariable(((GrVariable)element)); + return GroovyInlineVariableUtil.createInlinerForVariable((GrVariable)element, settings); } if (element instanceof GrAccessorMethod) { - return GroovyInlineVariableUtil.createInlinerForVariable(((GrAccessorMethod)element).getProperty()); + return GroovyInlineVariableUtil.createInlinerForVariable(((GrAccessorMethod)element).getProperty(), settings); } if (element instanceof GrMethod) { return new GroovyMethodInliner((GrMethod)element); @@ -106,23 +111,31 @@ public class GroovyInlineHandler implements InlineHandler, ReferencesToInlineSea } @Override - public Collection findReferences(PsiElement element) { - if (element instanceof GrAccessorMethod) element = ((GrAccessorMethod)element).getProperty(); - - if (!(element instanceof GrField)) { - return ReferencesSearch.search(element).findAll(); + public Collection findReferences(PsiElement element, Settings settings) { + if (element instanceof GrVariable && GroovyRefactoringUtil.isLocalVariable((GrVariable)element)) { + if (settings instanceof InlineLocalVarSettings) { + return ((InlineLocalVarSettings)settings).getRefs(); + } } - LOG.assertTrue(((GrField)element).getSetter() == null); + else if (element instanceof GrAccessorMethod || element instanceof GrField) { + if (element instanceof GrAccessorMethod) { + element = ((GrAccessorMethod)element).getProperty(); + } - Collection result = new ArrayList(); - result.addAll(ReferencesSearch.search(element).findAll()); + LOG.assertTrue(((GrField)element).getSetter() == null); - for (GrAccessorMethod getter : ((GrField)element).getGetters()) { - result.addAll(MethodReferencesSearch.search(getter).findAll()); + Collection result = new ArrayList(); + result.addAll(ReferencesSearch.search(element).findAll()); + + for (GrAccessorMethod getter : ((GrField)element).getGetters()) { + result.addAll(MethodReferencesSearch.search(getter).findAll()); + } + + return result; } - return result; + return ReferencesSearch.search(element).findAll(); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineVariableUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineVariableUtil.java index 9823aebe2691..eee34f59e333 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineVariableUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/GroovyInlineVariableUtil.java @@ -16,64 +16,218 @@ package org.jetbrains.plugins.groovy.refactoring.inline; +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.lang.refactoring.InlineHandler; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; 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.RangeHighlighter; +import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.wm.WindowManager; -import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiUtilBase; 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.lang.psi.api.statements.GrField; +import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; +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.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.util.PsiUtil; import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle; +import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; /** * @author ilyas */ public class GroovyInlineVariableUtil { + private static final Logger LOG = Logger.getInstance(GroovyInlineVariableUtil.class); public static final String INLINE_VARIABLE = RefactoringBundle.message("inline.variable.title"); - public static final String INLINE_FIELD = RefactoringBundle.message("inline.field.title"); private GroovyInlineVariableUtil() { } + public static void removeDefinition(PsiElement element, InlineHandler.Settings settings) { + LOG.assertTrue(element instanceof GrVariable && GroovyRefactoringUtil.isLocalVariable((GrVariable)element), element.getClass()); + + if (settings instanceof InlineLocalVarSettings) { + final GrExpression initializer = ((InlineLocalVarSettings)settings).getInitializer(); + final PsiElement parent = initializer.getParent(); + + if (parent instanceof GrAssignmentExpression) { + parent.delete(); + return; + } + else if (parent instanceof GrVariable) { + if (!((InlineLocalVarSettings)settings).isRemoveDeclaration()) { + initializer.delete(); + return; + } + } + } + + final PsiElement owner = element.getParent().getParent(); + if (owner instanceof GrVariableDeclarationOwner) { + ((GrVariableDeclarationOwner)owner).removeVariable((GrVariable)element); + } + else { + element.delete(); + } + } /** - * Creates new inliner for local variable occurences + * Creates new inliner for local variable occurrences */ - static InlineHandler.Inliner createInlinerForVariable(final GrVariable variable) { - return new GrVariableInliner(variable); + static InlineHandler.Inliner createInlinerForVariable(final GrVariable variable, InlineHandler.Settings settings) { + return new GrVariableInliner(variable, settings); } /** * Returns Settings object for referenced definition in case of local variable */ @Nullable - static InlineHandler.Settings inlineLocalVariableSettings(final GrVariable variable, Editor editor) { + static InlineHandler.Settings inlineLocalVariableSettings(final GrVariable variable, @Nullable Editor editor, boolean invokedOnReference) { final String localName = variable.getName(); final Project project = variable.getProject(); - if (variable.getInitializerGroovy() == null) { + + + final Collection refs = ReferencesSearch.search(variable).findAll(); + + GrExpression initializer = null; + + Instruction writeInstr = null; + + final Instruction[] flow = ControlFlowUtils.findControlFlowOwner(variable).getControlFlow(); + final ArrayList writes = ControlFlowUtils.inferWriteAccessMap(flow, variable); + + GrReferenceExpression refExpr = null; + if (invokedOnReference) { + LOG.assertTrue(editor != null, "null editor but invokedOnReference==true"); + final PsiReference ref = TargetElementUtilBase.findReference(editor); + LOG.assertTrue(ref != null); + final PsiElement cur = ref.getElement(); + if (cur instanceof GrReferenceExpression) { + refExpr = (GrReferenceExpression)cur; + final Instruction instruction = ContainerUtil.find(flow, new Condition() { + @Override + public boolean value(Instruction instruction) { + return instruction.getElement() == cur; + } + }); + + 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 = PsiUtil.getInitializerFor((GrReferenceExpression)element); + } + } + } + } + else { + initializer = variable.getInitializerGroovy(); + writeInstr = ContainerUtil.find(flow, new Condition() { + @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 InlineHandler.Settings.CANNOT_INLINE_SETTINGS; } - return inlineLocalVarDialogResult(localName, project); + ArrayList toInline = new ArrayList(); + for (Instruction instruction : flow) { + if (!(instruction instanceof ReadWriteVariableInstruction)) continue; + if (((ReadWriteVariableInstruction)instruction).isWrite()) continue; + + final PsiElement element = instruction.getElement(); + 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 (prev.cardinality() == 1 && prev.get(writeInstr.num())) { + toInline.add(ref); + } + } + + if (toInline.size()==0) { + CommonRefactoringUtil.showErrorHint(project, editor, GroovyRefactoringBundle.message("variable.is.never.used.0", localName), INLINE_VARIABLE, HelpID.INLINE_VARIABLE); + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + + ArrayList highlighters = new ArrayList(); + HighlightManager highlightManager = HighlightManager.getInstance(project); + EditorColorsManager colorsManager = EditorColorsManager.getInstance(); + TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); + final TextAttributes writeAttributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); + + if (refExpr != null && PsiUtil.isAccessedForReading(refExpr) && !toInline.contains(refExpr)) { + highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{refExpr}, attributes, true, null); + String message = RefactoringBundle.getCannotRefactorMessage(GroovyRefactoringBundle.message("variable.is.accessed.for.writing", localName)); + CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_VARIABLE, HelpID.INLINE_VARIABLE); + WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + + for (GrReferenceExpression ref : toInline) { + if (PsiUtil.isAccessedForWriting(ref)) { + final String message = GroovyRefactoringBundle.message("variable.is.accessed.for.writing", localName); + HighlightManager.getInstance(project).addOccurrenceHighlights(editor, new PsiElement[]{ref}, writeAttributes, true, null); + CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_VARIABLE, HelpID.INLINE_VARIABLE); + WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); + + return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; + } + } + + highlightManager.addOccurrenceHighlights(editor, PsiUtilBase.toPsiElementArray(toInline), attributes, false, highlighters); + return inlineLocalVarDialogResult(localName, project, toInline, initializer, toInline.size() == refs.size()); } /** * Shows dialog with question to inline */ @Nullable - private static InlineHandler.Settings inlineLocalVarDialogResult(String localName, Project project) { + private static InlineHandler.Settings inlineLocalVarDialogResult(String localName, + Project project, + Collection refs, + GrExpression initializer, + boolean removeDeclaration) { if (!ApplicationManager.getApplication().isUnitTestMode()) { - final String question = GroovyRefactoringBundle.message("inline.local.variable.prompt", localName); + final String question = GroovyRefactoringBundle.message("inline.local.variable.prompt.0.1", localName, refs.size()); RefactoringMessageDialog dialog = new RefactoringMessageDialog(INLINE_VARIABLE, question, HelpID.INLINE_VARIABLE, "OptionPane.questionIcon", true, project); dialog.show(); if (!dialog.isOK()) { @@ -82,55 +236,6 @@ public class GroovyInlineVariableUtil { } } - return new InlineHandler.Settings() { - public boolean isOnlyOneReferenceToInline() { - return false; - } - }; - } - - @Nullable - static InlineHandler.Settings inlineFieldSettings(final GrField field, Editor editor, boolean invokedOnReference) { - final Project project = field.getProject(); - - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - String message = RefactoringBundle.message("0.refactoring.is.supported.only.for.final.fields", INLINE_FIELD); - CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_FIELD, HelpID.INLINE_FIELD); - return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; - } - - if (field.getInitializerGroovy() == null) { - String message = GroovyRefactoringBundle.message("cannot.find.a.single.definition.to.inline.field"); - CommonRefactoringUtil.showErrorHint(project, editor, message, INLINE_FIELD, HelpID.INLINE_FIELD); - return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; - } - - return inlineFieldDialogResult(project, field, invokedOnReference); - } - - private static InlineHandler.Settings inlineFieldDialogResult(Project project, GrField field, final boolean invokedOnReference) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - return new InlineHandler.Settings() { - @Override - public boolean isOnlyOneReferenceToInline() { - return invokedOnReference; - } - }; - } - - final InlineGroovyFieldDialog dialog = new InlineGroovyFieldDialog(project, field, invokedOnReference); - dialog.show(); - if (dialog.isOK()) { - return new InlineHandler.Settings() { - @Override - public boolean isOnlyOneReferenceToInline() { - return dialog.isInlineThisOnly(); - } - }; - } - else { - WindowManager.getInstance().getStatusBar(project).setInfo(GroovyRefactoringBundle.message("press.escape.to.remove.the.highlighting")); - return InlineHandler.Settings.CANNOT_INLINE_SETTINGS; - } + return new InlineLocalVarSettings(initializer, refs, removeDeclaration); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/InlineLocalVarSettings.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/InlineLocalVarSettings.java new file mode 100644 index 000000000000..c878ef975982 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/inline/InlineLocalVarSettings.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2011 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.lang.refactoring.InlineHandler; +import com.intellij.psi.PsiReference; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; + +import java.util.Collection; + +/** + * @author Max Medvedev + */ +public class InlineLocalVarSettings implements InlineHandler.Settings { + private GrExpression myInitializer; + private Collection myRefs; + private boolean myRemoveDeclaration; + + public InlineLocalVarSettings(GrExpression initializer, Collection refs, boolean removeDeclaration) { + myInitializer = initializer; + + myRefs = refs; + myRemoveDeclaration = removeDeclaration; + } + + @Override + public boolean isOnlyOneReferenceToInline() { + return false; + } + + public GrExpression getInitializer() { + return myInitializer; + } + + + public Collection getRefs() { + return myRefs; + } + + public boolean isRemoveDeclaration() { + return myRemoveDeclaration; + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/inline/InlineVariableTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/inline/InlineVariableTest.java index 3b50c53571a9..566f3dee8e1e 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/inline/InlineVariableTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/refactoring/inline/InlineVariableTest.java @@ -40,6 +40,11 @@ public class InlineVariableTest extends LightCodeInsightFixtureTestCase { public void testField() {doTest();} + public void testPartial1() {doTest();} + public void testPartial2() {doTest();} + public void testPartial3() {doTest();} + public void testPartial4() {doTest();} + protected void doTest() { InlineMethodTest.doInlineTest(myFixture, getTestDataPath() + getTestName(true) + ".test", new GroovyInlineHandler()); } diff --git a/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial1.test b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial1.test new file mode 100644 index 000000000000..fe90eeccb7ce --- /dev/null +++ b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial1.test @@ -0,0 +1,21 @@ +def a = 2 + +if (b) { + print a +} +else { + a = foo(a) +} + +print a +----- +def a + +if (b) { + print 2 +} +else { + a = foo(2) +} + +print a \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial2.test b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial2.test new file mode 100644 index 000000000000..274830690694 --- /dev/null +++ b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial2.test @@ -0,0 +1,9 @@ +def a = 4 +print a +a = a+2+a +print a +----- +def a +print 4 +a = 4+2+4 +print a \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial3.test b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial3.test new file mode 100644 index 000000000000..2999557ea797 --- /dev/null +++ b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial3.test @@ -0,0 +1,5 @@ +def a = 3 + +a=3 +----- +FAIL: Variable 'a' is never used \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial4.test b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial4.test new file mode 100644 index 000000000000..647afd130c3f --- /dev/null +++ b/plugins/groovy/testdata/groovy/refactoring/inlineLocal/partial4.test @@ -0,0 +1,28 @@ +def a = 2 +print a +a = 4 + +if (b) { + print a +} +else { + print a + a = 5 + print a +} + +print a +----- +def a = 2 +print a + +if (b) { + print 4 +} +else { + print 4 + a = 5 + print a +} + +print a \ No newline at end of file