From 0e44858a99413d49f7a34c1c3a318e633f28e700 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 6 Mar 2017 14:04:56 +0700 Subject: [PATCH] Extract variable to chained method call --- .../IntroduceVariablePostfixTemplate.java | 5 +- .../ChainCallInplaceIntroducer.java | 85 +++++++++++ .../IntroduceVariableBase.java | 135 ++++++++++++++---- .../IntroduceVariableHandler.java | 7 +- .../JavaVariableInplaceIntroducer.java | 23 ++- .../noWritable_after.java | 4 +- .../streamMultiline.java | 11 ++ .../streamMultiline_after.java | 11 ++ .../streamMultiple.java | 7 + .../streamMultiple_after.java | 7 + .../streamSimple.java | 7 + .../streamSimple_after.java | 7 + .../writable_after.java | 6 +- .../AbstractJavaInplaceIntroduceTest.java | 4 +- .../InplaceIntroduceVariableTest.java | 36 +++-- .../refactoring/IntroduceVariableTest.java | 9 +- .../MockIntroduceVariableHandler.java | 6 +- .../introduce/inplace/OccurrencesChooser.java | 55 +++++-- .../util/resources/misc/registry.properties | 6 +- 19 files changed, 347 insertions(+), 84 deletions(-) create mode 100644 java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline_after.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple_after.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple.java create mode 100644 java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple_after.java diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/IntroduceVariablePostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/IntroduceVariablePostfixTemplate.java index bf13b104684e..0133c96ec01c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/IntroduceVariablePostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/IntroduceVariablePostfixTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,7 +21,6 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiType; -import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduceVariable.InputValidator; import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler; import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings; @@ -53,7 +52,7 @@ public class IntroduceVariablePostfixTemplate extends PostfixTemplateWithExpress public final IntroduceVariableSettings getSettings(Project project, Editor editor, final PsiExpression expr, PsiExpression[] occurrences, TypeSelectorManagerImpl typeSelectorManager, boolean declareFinalIfAll, boolean anyAssignmentLHS, InputValidator validator, - PsiElement anchor, OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, JavaReplaceChoice replaceChoice) { return new IntroduceVariableSettings() { @Override public String getEnteredName() { diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java new file mode 100644 index 000000000000..c538743e341b --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ChainCallInplaceIntroducer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2017 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 com.intellij.refactoring.introduceVariable; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.chainCall.ChainCallExtractor; +import com.intellij.refactoring.ui.TypeSelectorManagerImpl; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.Nullable; + +/** + * @author Tagir Valeev + */ +public class ChainCallInplaceIntroducer extends JavaVariableInplaceIntroducer { + private PsiParameter myParameter; + private PsiMethodCallExpression myCall; + + public ChainCallInplaceIntroducer(Project project, + IntroduceVariableSettings settings, + PsiElement chosenAnchor, + Editor editor, + PsiExpression expr, + PsiExpression[] occurrences, + TypeSelectorManagerImpl selectorManager, + String title) { + super(project, settings, chosenAnchor, editor, expr, true, occurrences, selectorManager, title); + } + + @Nullable + @Override + protected PsiVariable getVariable() { + if (myParameter != null && myParameter.isValid()) { + return myParameter; + } + return super.getVariable(); + } + + @Nullable + @Override + protected PsiElement checkLocalScope() { + return myCall; + } + + @Nullable + @Override + protected PsiVariable introduceVariable() { + PsiVariable variable = super.introduceVariable(); + if (variable instanceof PsiLocalVariable) { + PsiLambdaExpression lambda = ApplicationManager.getApplication().runWriteAction( + (Computable)() -> ChainCallExtractor.extractMappingStep(myProject, (PsiLocalVariable)variable)); + if (lambda != null) { + PsiParameter parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters()); + myParameter = parameter; + myCall = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class); + myExprMarker = null; + myExpr = null; + setAdvertisementText(null); + PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); + return parameter; + } + else if (!variable.isValid()) { + return null; + } + } + return variable; + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java index 63848bfa3a56..05f745235aee 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java @@ -54,8 +54,10 @@ import com.intellij.psi.impl.source.jsp.jspJava.JspCodeBlock; import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.*; import com.intellij.refactoring.*; +import com.intellij.refactoring.chainCall.ChainCallExtractor; import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduceField.ElementToWorkOn; @@ -70,12 +72,14 @@ import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager; import com.intellij.refactoring.util.occurrences.NotInSuperCallOccurrenceFilter; import com.intellij.util.ArrayUtilRt; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.text.MessageFormat; import java.util.*; /** @@ -83,6 +87,37 @@ import java.util.*; * Date: Nov 15, 2002 */ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { + public enum JavaReplaceChoice implements OccurrencesChooser.BaseReplaceChoice { + NO("Replace this occurrence only"), NO_WRITE("Replace all occurrences but write"), ALL("Replace all {0} occurrences"), + NO_CHAIN("Create variable inside current lambda"), CHAIN("Extract as separate step"), + CHAIN_ALL("Replace all {0} occurrences and extract as separate step"); + + private final String myDescription; + + JavaReplaceChoice(String description) { + myDescription = description; + } + + public String getDescription() { + return myDescription; + } + + @Override + public boolean isMultiple() { + return this == NO_WRITE || this == ALL || this == CHAIN_ALL; + } + + @Override + public boolean isAll() { + return this == ALL || this == CHAIN_ALL; + } + + @Override + public String formatDescription(int occurrencesCount) { + return MessageFormat.format(getDescription(), occurrencesCount); + } + } + private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase"); @NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements"; @NonNls private static final String REFACTORING_ID = "refactoring.extractVariable"; @@ -610,20 +645,20 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return false; - final LinkedHashMap> occurrencesMap = occurrencesInfo.buildOccurrencesMap(expr); + final LinkedHashMap> occurrencesMap = occurrencesInfo.buildOccurrencesMap(expr); final boolean inFinalContext = occurrenceManager.isInFinalContext(); final InputValidator validator = new InputValidator(this, project, anchorStatementIfAll, anchorStatement, occurrenceManager); final TypeSelectorManagerImpl typeSelectorManager = new TypeSelectorManagerImpl(project, originalType, expr, occurrences); final boolean[] wasSucceed = new boolean[]{true}; - final Pass callback = new Pass() { + final Pass callback = new Pass() { @Override - public void pass(final OccurrencesChooser.ReplaceChoice choice) { + public void pass(final JavaReplaceChoice choice) { boolean hasWriteAccess = occurrencesInfo.myHasWriteAccess; List nonWrite = occurrencesInfo.myNonWrite; if (choice != null) { - final boolean noWriteChoice = choice == OccurrencesChooser.ReplaceChoice.NO_WRITE; - final boolean allChoice = choice == OccurrencesChooser.ReplaceChoice.ALL; + final boolean noWriteChoice = choice == JavaReplaceChoice.NO_WRITE; + final boolean allChoice = choice.isAll(); final boolean replaceAll = allChoice || noWriteChoice; typeSelectorManager.setAllOccurrences(replaceAll); @@ -638,13 +673,24 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { .filter(occurrence -> !(expr.equals(occurrence) && expr.getParent() instanceof PsiExpressionStatement)) .filter(occurrence -> allChoice || (noWriteChoice && !PsiUtil.isAccessedForWriting(occurrence)) || expr.equals(occurrence)) .toArray(PsiExpression[]::new); - myInplaceIntroducer = new JavaVariableInplaceIntroducer(project, - settings, - chosenAnchor, - editor, expr, cantChangeFinalModifier, - allOccurrences, - typeSelectorManager, - REFACTORING_NAME); + if (choice == JavaReplaceChoice.CHAIN || choice == JavaReplaceChoice.CHAIN_ALL) { + myInplaceIntroducer = new ChainCallInplaceIntroducer(project, + settings, + chosenAnchor, + editor, expr, + allOccurrences, + typeSelectorManager, + REFACTORING_NAME); + } + else { + myInplaceIntroducer = new JavaVariableInplaceIntroducer(project, + settings, + chosenAnchor, + editor, expr, cantChangeFinalModifier, + allOccurrences, + typeSelectorManager, + REFACTORING_NAME); + } if (myInplaceIntroducer.startInplaceIntroduceTemplate()) { return; } @@ -653,6 +699,9 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { CommandProcessor.getInstance().executeCommand( project, () -> { + if (!anchorStatement.isValid()) { + return; + } final Editor topLevelEditor ; if (!InjectedLanguageManager.getInstance(project).isInjectedFragment(anchorStatement.getContainingFile())) { topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(editor); @@ -694,17 +743,20 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { callback.pass(null); } else { - OccurrencesChooser.ReplaceChoice choice = getOccurrencesChoice(); + JavaReplaceChoice choice = getOccurrencesChoice(); if (choice != null) { callback.pass(choice); } else { - OccurrencesChooser.simpleChooser(editor).showChooser(callback, occurrencesMap); + String title = occurrencesInfo.myChainCallCandidate && occurrences.length == 1 + ? "Lambda chain detected" + : OccurrencesChooser.DEFAULT_CHOOSER_TITLE; + OccurrencesChooser.simpleChooser(editor).showChooser(callback, occurrencesMap, title); } } return wasSucceed[0]; } - protected OccurrencesChooser.ReplaceChoice getOccurrencesChoice() { + protected JavaReplaceChoice getOccurrencesChoice() { return null; } @@ -1096,13 +1148,12 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { boolean anyAssignmentLHS, final InputValidator validator, PsiElement anchor, - final OccurrencesChooser.ReplaceChoice replaceChoice) { - final boolean replaceAll = - replaceChoice == OccurrencesChooser.ReplaceChoice.ALL || replaceChoice == OccurrencesChooser.ReplaceChoice.NO_WRITE; + final JavaReplaceChoice replaceChoice) { + final boolean replaceAll = replaceChoice.isMultiple(); final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr, anchor); final String variableName = suggestedName.names.length > 0 ? suggestedName.names[0] : ""; final boolean declareFinal = replaceAll && declareFinalIfAll || !anyAssignmentLHS && createFinals(project); - final boolean replaceWrite = anyAssignmentLHS && replaceChoice == OccurrencesChooser.ReplaceChoice.ALL; + final boolean replaceWrite = anyAssignmentLHS && replaceChoice.isAll(); return new IntroduceVariableSettings() { @Override public String getEnteredName() { @@ -1192,11 +1243,14 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { } static class OccurrencesInfo { + static final boolean CHAIN_ALLOWED = Registry.is("java.extract.variable.chaining.method"); + List myOccurrences; List myNonWrite; boolean myCantReplaceAll; boolean myCantReplaceAllButWrite; boolean myHasWriteAccess; + boolean myChainCallCandidate; public OccurrencesInfo(PsiExpression[] occurrences) { myOccurrences = Arrays.asList(occurrences); @@ -1214,18 +1268,45 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { } } myHasWriteAccess = myOccurrences.size() > myNonWrite.size() && myOccurrences.size() > 1; + myChainCallCandidate = CHAIN_ALLOWED && isChainCallCandidate(); + } + + private boolean isChainCallCandidate() { + if (myHasWriteAccess || myOccurrences.isEmpty()) return false; + // The whole lambda body selected + PsiExpression expression = myOccurrences.get(0); + if (myOccurrences.size() == 1 && expression.getParent() instanceof PsiLambdaExpression) return false; + PsiElement parent = PsiTreeUtil.findCommonParent(myOccurrences); + if (parent == null) return false; + PsiType type = expression.getType(); + PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, true, PsiStatement.class); + if (ChainCallExtractor.findExtractor(lambda, expression, type) == null) return false; + PsiParameter parameter = lambda.getParameterList().getParameters()[0]; + return ReferencesSearch.search(parameter).forEach((Processor)ref -> + myOccurrences.stream().anyMatch(expr -> PsiTreeUtil.isAncestor(expr, ref.getElement(), false))); } @NotNull - private LinkedHashMap> buildOccurrencesMap(PsiExpression expr) { - final LinkedHashMap> occurrencesMap = ContainerUtil.newLinkedHashMap(); - occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO, Collections.singletonList(expr)); - if (myHasWriteAccess && !myCantReplaceAllButWrite) { - occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO_WRITE, myNonWrite); - } + LinkedHashMap> buildOccurrencesMap(PsiExpression expr) { + final LinkedHashMap> occurrencesMap = ContainerUtil.newLinkedHashMap(); + if (myChainCallCandidate) { + if (myOccurrences.size() > 1 && !myCantReplaceAll) { + occurrencesMap.put(JavaReplaceChoice.NO, Collections.singletonList(expr)); + occurrencesMap.put(JavaReplaceChoice.ALL, myOccurrences); + occurrencesMap.put(JavaReplaceChoice.CHAIN_ALL, myOccurrences); + } else { + occurrencesMap.put(JavaReplaceChoice.NO_CHAIN, Collections.singletonList(expr)); + occurrencesMap.put(JavaReplaceChoice.CHAIN, Collections.singletonList(expr)); + } + } else { + occurrencesMap.put(JavaReplaceChoice.NO, Collections.singletonList(expr)); + if (myHasWriteAccess && !myCantReplaceAllButWrite) { + occurrencesMap.put(JavaReplaceChoice.NO_WRITE, myNonWrite); + } - if (myOccurrences.size() > 1 && !myCantReplaceAll) { - occurrencesMap.put(OccurrencesChooser.ReplaceChoice.ALL, myOccurrences); + if (myOccurrences.size() > 1 && !myCantReplaceAll) { + occurrencesMap.put(JavaReplaceChoice.ALL, myOccurrences); + } } return occurrencesMap; } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableHandler.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableHandler.java index afd5fc258a3f..3aec4d3fb41c 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -29,7 +29,6 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.RefactoringBundle; -import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.ui.ConflictsDialog; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; import com.intellij.refactoring.util.CommonRefactoringUtil; @@ -51,9 +50,9 @@ public class IntroduceVariableHandler extends IntroduceVariableBase { boolean declareFinalIfAll, boolean anyAssignmentLHS, final InputValidator validator, - PsiElement anchor, OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, JavaReplaceChoice replaceChoice) { if (replaceChoice == null && ApplicationManager.getApplication().isUnitTestMode()) { - replaceChoice = OccurrencesChooser.ReplaceChoice.NO; + replaceChoice = JavaReplaceChoice.NO; } if (replaceChoice != null) { return super.getSettings(project, editor, expr, occurrences, typeSelectorManager, declareFinalIfAll, anyAssignmentLHS, validator, diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java index 5e77b0356269..270cc341ea30 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -396,13 +396,9 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer @Override protected PsiVariable createFieldToStartTemplateOn(String[] names, PsiType psiType) { - PsiVariable variable = IntroduceVariableBase.introduce(myProject, myExpr, myEditor, myChosenAnchor.getElement(), getOccurrences(), mySettings); - final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(variable, PsiDeclarationStatement.class); - myPointer = declarationStatement != null ? SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(declarationStatement) : null; - myEditor.putUserData(ReassignVariableUtil.DECLARATION_KEY, myPointer); - setAdvertisementText(getAdvertisementText(declarationStatement, variable.getType(), myHasTypeSuggestion)); + PsiVariable variable = introduceVariable(); - PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); + if (variable == null) return null; final PsiVariable restoredVar = getVariable(); if (restoredVar != null) { @@ -433,6 +429,19 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer return variable; } + @Nullable + protected PsiVariable introduceVariable() { + PsiVariable variable = IntroduceVariableBase + .introduce(myProject, myExpr, myEditor, myChosenAnchor.getElement(), getOccurrences(), mySettings); + final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(variable, PsiDeclarationStatement.class); + myPointer = declarationStatement != null ? SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(declarationStatement) : null; + myEditor.putUserData(ReassignVariableUtil.DECLARATION_KEY, myPointer); + setAdvertisementText(getAdvertisementText(declarationStatement, variable.getType(), myHasTypeSuggestion)); + + PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument()); + return variable; + } + @Override protected int getCaretOffset() { final PsiVariable variable = getVariable(); diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/noWritable_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/noWritable_after.java index 803af2c8f1da..b1fe74839469 100644 --- a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/noWritable_after.java +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/noWritable_after.java @@ -2,7 +2,7 @@ class C { { int[] a = new int[1]; a[1] = 42; - int x = a[1]; - System.out.println(x); + int i = a[1]; + System.out.println(i); } } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline.java new file mode 100644 index 000000000000..004ceb3abba8 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline.java @@ -0,0 +1,11 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().forEach(s -> System.out.println( + new StringBuilder().append(String.format("[%s]", s)) + .append("oops") + .append("argh") + )); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline_after.java new file mode 100644 index 000000000000..f5aa94d939c0 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiline_after.java @@ -0,0 +1,11 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().map(s -> String.format("[%s]", s)).forEach(format -> System.out.println( + new StringBuilder().append(format) + .append("oops") + .append("argh") + )); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple.java new file mode 100644 index 000000000000..607b7a56a488 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple.java @@ -0,0 +1,7 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().mapToInt(x -> x.length() + 10 * x.length()).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple_after.java new file mode 100644 index 000000000000..1874950cb4e3 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamMultiple_after.java @@ -0,0 +1,7 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().mapToInt(String::length).map(length -> length + 10 * length).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple.java new file mode 100644 index 000000000000..c367e9ddd8d2 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple.java @@ -0,0 +1,7 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().forEach(s -> System.out.println(s.toLowerCase())); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple_after.java new file mode 100644 index 000000000000..e36681dd0871 --- /dev/null +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/streamSimple_after.java @@ -0,0 +1,7 @@ +import java.util.List; + +public class StreamExtract { + void test(List list) { + list.stream().map(String::toLowerCase).forEach(s1 -> System.out.println(s1)); + } +} diff --git a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/writable_after.java b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/writable_after.java index 66a0280705f0..2955328975b8 100644 --- a/java/java-tests/testData/refactoring/inplaceIntroduceVariable/writable_after.java +++ b/java/java-tests/testData/refactoring/inplaceIntroduceVariable/writable_after.java @@ -1,8 +1,8 @@ class C { { int[] a = new int[1]; - int x = a[1]; - x = 42; - System.out.println(x); + int i = a[1]; + i = 42; + System.out.println(i); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/AbstractJavaInplaceIntroduceTest.java b/java/java-tests/testSrc/com/intellij/refactoring/AbstractJavaInplaceIntroduceTest.java index d8ccf02640bf..c938fe039a3d 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/AbstractJavaInplaceIntroduceTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/AbstractJavaInplaceIntroduceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -54,7 +54,7 @@ public abstract class AbstractJavaInplaceIntroduceTest extends AbstractInplaceIn @Override protected Sdk getProjectJDK() { - return IdeaTestUtil.getMockJdk17(); + return IdeaTestUtil.getMockJdk18(); } @Override diff --git a/java/java-tests/testSrc/com/intellij/refactoring/InplaceIntroduceVariableTest.java b/java/java-tests/testSrc/com/intellij/refactoring/InplaceIntroduceVariableTest.java index 44a86d4a2388..e6898610e537 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/InplaceIntroduceVariableTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/InplaceIntroduceVariableTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -31,7 +31,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; -import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; +import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler; import com.intellij.testFramework.MapDataContext; import org.jetbrains.annotations.NotNull; @@ -117,7 +117,7 @@ public class InplaceIntroduceVariableTest extends AbstractJavaInplaceIntroduceTe } public void testPlaceInsideLambdaBodyMultipleOccurrences1() throws Exception { - doTestReplaceChoice(OccurrencesChooser.ReplaceChoice.ALL, new Pass() { + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.ALL, new Pass() { @Override public void pass(AbstractInplaceIntroducer inplaceIntroduceFieldPopup) { type("expr"); @@ -190,19 +190,31 @@ public class InplaceIntroduceVariableTest extends AbstractJavaInplaceIntroduceTe } public void testWritable() throws Exception { - doTestReplaceChoice(OccurrencesChooser.ReplaceChoice.ALL); + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.ALL); } public void testNoWritable() throws Exception { - doTestReplaceChoice(OccurrencesChooser.ReplaceChoice.NO_WRITE); + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.NO_WRITE); } public void testAllInsertFinal() throws Exception { - doTestReplaceChoice(OccurrencesChooser.ReplaceChoice.ALL); + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.ALL); } public void testAllIncomplete() throws Exception { - doTestReplaceChoice(OccurrencesChooser.ReplaceChoice.ALL); + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.ALL); + } + + public void testStreamSimple() throws Exception { + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.CHAIN); + } + + public void testStreamMultiple() throws Exception { + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.CHAIN_ALL); + } + + public void testStreamMultiline() throws Exception { + doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice.CHAIN); } public void testBrokenFormattingWithInValidation() throws Exception { @@ -273,11 +285,11 @@ public class InplaceIntroduceVariableTest extends AbstractJavaInplaceIntroduceTe } } - private void doTestReplaceChoice(OccurrencesChooser.ReplaceChoice choice) { + private void doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice choice) { doTestReplaceChoice(choice, null); } - private void doTestReplaceChoice(OccurrencesChooser.ReplaceChoice choice, Pass pass) { + private void doTestReplaceChoice(IntroduceVariableBase.JavaReplaceChoice choice, Pass pass) { String name = getTestName(true); configureByFile(getBasePath() + name + getExtension()); final boolean enabled = getEditor().getSettings().isVariableInplaceRenameEnabled(); @@ -318,9 +330,9 @@ public class InplaceIntroduceVariableTest extends AbstractJavaInplaceIntroduceTe } public static class MyIntroduceVariableHandler extends IntroduceVariableHandler implements MyIntroduceHandler { - private OccurrencesChooser.ReplaceChoice myChoice = null; + private JavaReplaceChoice myChoice = null; - public void setChoice(OccurrencesChooser.ReplaceChoice choice) { + public void setChoice(JavaReplaceChoice choice) { myChoice = choice; } @@ -335,7 +347,7 @@ public class InplaceIntroduceVariableTest extends AbstractJavaInplaceIntroduceTe } @Override - protected OccurrencesChooser.ReplaceChoice getOccurrencesChoice() { + protected JavaReplaceChoice getOccurrencesChoice() { return myChoice; } diff --git a/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java b/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java index 67f74b3d377d..94d47ab7c5ff 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -23,7 +23,6 @@ import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiType; -import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduceVariable.InputValidator; import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings; @@ -365,7 +364,7 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase { boolean declareFinalIfAll, boolean anyAssignmentLHS, InputValidator validator, - PsiElement anchor, final OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, final JavaReplaceChoice replaceChoice) { final PsiType type = typeSelectorManager.getDefaultType(); assertTrue(type.getPresentableText(), type.getPresentableText().equals(expectedTypeName)); assertEquals("path", IntroduceVariableBase.getSuggestedName(type, expr).names[0]); @@ -384,7 +383,7 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase { boolean declareFinalIfAll, boolean anyAssignmentLHS, InputValidator validator, - PsiElement anchor, final OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, final JavaReplaceChoice replaceChoice) { final PsiType type = typeSelectorManager.getDefaultType(); assertTrue(type.getPresentableText(), type.getPresentableText().equals("B")); return super.getSettings(project, editor, expr, occurrences, typeSelectorManager, declareFinalIfAll, anyAssignmentLHS, @@ -552,7 +551,7 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase { boolean declareFinalIfAll, boolean anyAssignmentLHS, InputValidator validator, - PsiElement anchor, final OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, final JavaReplaceChoice replaceChoice) { final PsiType[] types = typeSelectorManager.getTypesForAll(); assertTrue(types[0].getPresentableText(), types[0].getPresentableText().equals("B")); assertTrue(types[1].getPresentableText(), types[1].getPresentableText().equals("A")); diff --git a/java/java-tests/testSrc/com/intellij/refactoring/MockIntroduceVariableHandler.java b/java/java-tests/testSrc/com/intellij/refactoring/MockIntroduceVariableHandler.java index cc8680399198..c26835fb113f 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/MockIntroduceVariableHandler.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/MockIntroduceVariableHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,12 +20,10 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiType; -import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduceVariable.InputValidator; import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; -import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NonNls; import static org.junit.Assert.assertTrue; @@ -66,7 +64,7 @@ class MockIntroduceVariableHandler extends IntroduceVariableBase { final boolean declareFinalIfAll, boolean anyAssignmentLHS, InputValidator validator, - PsiElement anchor, final OccurrencesChooser.ReplaceChoice replaceChoice) { + PsiElement anchor, final JavaReplaceChoice replaceChoice) { final PsiType type = myLookForType ? findType(typeSelectorManager.getTypesForAll(), typeSelectorManager.getDefaultType()) : typeSelectorManager.getDefaultType(); assertTrue(type.getInternalCanonicalText(), type.getInternalCanonicalText().equals(myExpectedTypeCanonicalName)); diff --git a/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/OccurrencesChooser.java b/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/OccurrencesChooser.java index 5726c3056023..e7217e3b3316 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/OccurrencesChooser.java +++ b/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/OccurrencesChooser.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -44,7 +44,17 @@ import java.util.List; // This prevents languages with polyadic expressions or sequences // from reusing it, use simpleChooser instead. public abstract class OccurrencesChooser { - public enum ReplaceChoice { + public static final String DEFAULT_CHOOSER_TITLE = "Multiple occurrences found"; + + public interface BaseReplaceChoice { + boolean isMultiple(); + + boolean isAll(); + + String formatDescription(int occurrencesCount); + } + + public enum ReplaceChoice implements BaseReplaceChoice { NO("Replace this occurrence only"), NO_WRITE("Replace all occurrences but write"), ALL("Replace all {0} occurrences"); private final String myDescription; @@ -56,6 +66,21 @@ public abstract class OccurrencesChooser { public String getDescription() { return myDescription; } + + @Override + public boolean isMultiple() { + return this == NO_WRITE || this == ALL; + } + + @Override + public boolean isAll() { + return this == ALL; + } + + @Override + public String formatDescription(int occurrencesCount) { + return MessageFormat.format(getDescription(), occurrencesCount); + } } public static OccurrencesChooser simpleChooser(Editor editor) { @@ -89,15 +114,21 @@ public abstract class OccurrencesChooser { } public void showChooser(final Pass callback, final Map> occurrencesMap) { + showChooser(callback, occurrencesMap, DEFAULT_CHOOSER_TITLE); + } + + public void showChooser(final Pass callback, + final Map> occurrencesMap, + String title) { if (occurrencesMap.size() == 1) { callback.pass(occurrencesMap.keySet().iterator().next()); return; } - final DefaultListModel model = new DefaultListModel(); - for (ReplaceChoice choice : occurrencesMap.keySet()) { + final DefaultListModel model = new DefaultListModel<>(); + for (C choice : occurrencesMap.keySet()) { model.addElement(choice); } - final JList list = new JBList(model); + final JList list = new JBList<>(model); list.setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(final JList list, @@ -106,13 +137,9 @@ public abstract class OccurrencesChooser { final boolean isSelected, final boolean cellHasFocus) { final Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - final ReplaceChoice choices = (ReplaceChoice)value; + @SuppressWarnings("unchecked") final C choices = (C)value; if (choices != null) { - String text = choices.getDescription(); - if (choices == ReplaceChoice.ALL) { - text = MessageFormat.format(text, occurrencesMap.get(choices).size()); - } - setText(text); + setText(choices.formatDescription(occurrencesMap.get(choices).size())); } return rendererComponent; } @@ -120,7 +147,7 @@ public abstract class OccurrencesChooser { list.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(final ListSelectionEvent e) { - final ReplaceChoice value = (ReplaceChoice)list.getSelectedValue(); + final C value = list.getSelectedValue(); if (value == null) return; dropHighlighters(); final MarkupModel markupModel = myEditor.getMarkupModel(); @@ -136,11 +163,11 @@ public abstract class OccurrencesChooser { }); JBPopupFactory.getInstance().createListPopupBuilder(list) - .setTitle("Multiple occurrences found") + .setTitle(title) .setMovable(true) .setResizable(false) .setRequestFocus(true) - .setItemChoosenCallback(() -> callback.pass((ReplaceChoice)list.getSelectedValue())) + .setItemChoosenCallback(() -> callback.pass(list.getSelectedValue())) .addListener(new JBPopupAdapter() { @Override public void onClosed(LightweightWindowEvent event) { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 8e0fcdac30b1..927f70846fc5 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1046,4 +1046,8 @@ jest.debugger.use.inspect.description=Use --inspect for debugging Jest tests wit JavaScript.WebPack.With.JsonSchema=true JavaScript.WebPack.With.JsonSchema.description=Use JSON-Schema powered support for webpack config files ide.diagnostics.suggest.sending.all.attachments=false -ide.diagnostics.suggest.sending.all.attachments.description=Suggest the user to send all error attachments by default \ No newline at end of file +ide.diagnostics.suggest.sending.all.attachments.description=Suggest the user to send all error attachments by default + +java.extract.variable.chaining.method=true +java.extract.variable.chaining.method.description=Allows "Extract variable" to create separate call chain step +java.extract.variable.chaining.method.restartRequired=true