diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/InlineStreamMapAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/InlineStreamMapAction.java new file mode 100644 index 000000000000..7dbb7f3a3fce --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/InlineStreamMapAction.java @@ -0,0 +1,300 @@ +/* + * Copyright 2000-2016 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.codeInsight.intention.impl; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.util.LambdaRefactoringUtil; +import com.intellij.util.IncorrectOperationException; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.style.MethodRefCanBeReplacedWithLambdaInspection; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public class InlineStreamMapAction extends PsiElementBaseIntentionAction { + private static final Logger LOG = Logger.getInstance(InlineStreamMapAction.class.getName()); + + private static final Set MAP_METHODS = + StreamEx.of("map", "mapToInt", "mapToLong", "mapToDouble", "mapToObj", "boxed", "asLongStream", "asDoubleStream").toSet(); + + private static final Set NEXT_METHODS = StreamEx + .of("flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble", "forEach", "forEachOrdered", "anyMatch", "noneMatch", "allMatch") + .append(MAP_METHODS).toSet(); + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull final PsiElement element) { + if (!(element instanceof PsiIdentifier)) return false; + final PsiElement parent = element.getParent(); + if (!(parent instanceof PsiReferenceExpression)) return false; + final PsiElement gParent = parent.getParent(); + if (!(gParent instanceof PsiMethodCallExpression)) return false; + PsiMethodCallExpression curCall = (PsiMethodCallExpression)gParent; + if (!isMapCall(curCall)) return false; + PsiMethodCallExpression nextCall = getNextExpressionToMerge(curCall); + if(nextCall == null) return false; + String key = curCall.getArgumentList().getExpressions().length == 0 || nextCall.getArgumentList().getExpressions().length == 0 ? + "intention.inline.map.merge.text" : "intention.inline.map.inline.text"; + setText(CodeInsightBundle.message(key, element.getText(), nextCall.getMethodExpression().getReferenceName())); + return true; + } + + private static boolean isMapCall(@NotNull PsiMethodCallExpression methodCallExpression) { + String name = methodCallExpression.getMethodExpression().getReferenceName(); + if (name == null || !MAP_METHODS.contains(name)) return false; + + final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); + final PsiExpression[] expressions = argumentList.getExpressions(); + if (!name.startsWith("map") && expressions.length == 0) return true; + if (expressions.length != 1) return false; + if (!isSupportedForConversion(expressions[0], true)) return false; + + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) return false; + final PsiClass containingClass = method.getContainingClass(); + return InheritanceUtil.isInheritor(containingClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM); + } + + private static boolean isSupportedForConversion(PsiExpression expression, boolean requireExpressionLambda) { + if(expression instanceof PsiLambdaExpression) { + PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)expression; + return lambdaExpression.getParameterList().getParametersCount() == 1 && + (!requireExpressionLambda || LambdaUtil.extractSingleExpressionFromBody(lambdaExpression.getBody()) != null); + } else if(expression instanceof PsiMethodReferenceExpression) { + PsiMethodReferenceExpression methodReference = (PsiMethodReferenceExpression)expression; + return !MethodRefCanBeReplacedWithLambdaInspection.isWithSideEffects(methodReference); + } + return false; + } + + @Nullable + private static PsiMethodCallExpression getNextExpressionToMerge(PsiMethodCallExpression methodCallExpression) { + PsiElement parent = methodCallExpression.getParent(); + if(!(parent instanceof PsiReferenceExpression)) return null; + PsiElement gParent = parent.getParent(); + if(!(gParent instanceof PsiMethodCallExpression)) return null; + String nextName = ((PsiReferenceExpression)parent).getReferenceName(); + PsiMethodCallExpression nextCall = (PsiMethodCallExpression)gParent; + if(nextName == null || !NEXT_METHODS.contains(nextName) || translateName(methodCallExpression, nextCall) == null) return null; + PsiExpressionList argumentList = (nextCall).getArgumentList(); + PsiExpression[] expressions = argumentList.getExpressions(); + if(expressions.length == 0) { + if (!nextName.equals("boxed") && !nextName.equals("asLongStream") && !nextName.equals("asDoubleStream")) return null; + return nextCall; + } + if (expressions.length != 1 || !isSupportedForConversion(expressions[0], false)) return null; + + return nextCall; + } + + /** + * Generate name of joint method call which combines two given calls + * + * @param prevCall previous call (assumed to be in MAP_METHODS) + * @param nextCall next call (assumed to be in NEXT_METHODS) + * @return a name of the resulting method + */ + @Nullable + private static String translateName(@NotNull PsiMethodCallExpression prevCall, @NotNull PsiMethodCallExpression nextCall) { + PsiMethod nextMethod = nextCall.resolveMethod(); + if (nextMethod == null) return null; + String nextName = nextMethod.getName(); + PsiMethod method = prevCall.resolveMethod(); + if (method == null) return null; + PsiClass prevClass = method.getContainingClass(); + if (prevClass == null) return null; + String prevClassName = prevClass.getQualifiedName(); + if (prevClassName == null) return null; + String prevName = method.getName(); + if (nextName.endsWith("Match") || nextName.startsWith("forEach")) return nextName; + if (nextName.equals("map")) { + return translateMap(prevName); + } + if (prevName.equals("map")) { + return translateMap(nextName); + } + if(MAP_METHODS.contains(nextName)) { + PsiType type = nextMethod.getReturnType(); + if(!(type instanceof PsiClassType)) return null; + PsiClass nextClass = ((PsiClassType)type).resolve(); + if(nextClass == null) return null; + String nextClassName = nextClass.getQualifiedName(); + if(nextClassName == null) return null; + if(prevClassName.equals(nextClassName)) return "map"; + switch(nextClassName) { + case CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM: + return "mapToInt"; + case CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM: + return "mapToLong"; + case CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM: + return "mapToDouble"; + case CommonClassNames.JAVA_UTIL_STREAM_STREAM: + return "mapToObj"; + default: + return null; + } + } + if(nextName.equals("flatMap") && prevClassName.equals(CommonClassNames.JAVA_UTIL_STREAM_STREAM)) { + String mapMethod = translateMap(prevName); + return "flatM"+mapMethod.substring(1); + } + return null; + } + + @NotNull + private static String translateMap(String nextMethod) { + switch (nextMethod) { + case "boxed": + return "mapToObj"; + case "asLongStream": + return "mapToLong"; + case "asDoubleStream": + return "mapToDouble"; + default: + return nextMethod; + } + } + + @Override + @NotNull + public String getFamilyName() { + return CodeInsightBundle.message("intention.inline.map.family"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { + PsiMethodCallExpression mapCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); + if(mapCall == null) return; + + PsiMethodCallExpression nextCall = getNextExpressionToMerge(mapCall); + if(nextCall == null) return; + + PsiExpression nextQualifier = nextCall.getMethodExpression().getQualifierExpression(); + if(nextQualifier == null) return; + + String newName = translateName(mapCall, nextCall); + if(newName == null) return; + + if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return; + + PsiLambdaExpression previousLambda = getLambda(mapCall); + + LOG.assertTrue(previousLambda != null); + PsiExpression previousBody = LambdaUtil.extractSingleExpressionFromBody(previousLambda.getBody()); + LOG.assertTrue(previousBody != null); + + PsiLambdaExpression lambda = getLambda(nextCall); + LOG.assertTrue(lambda != null); + + if(!lambda.isPhysical()) { + lambda = (PsiLambdaExpression)nextCall.getArgumentList().add(lambda); + } + PsiElement body = lambda.getBody(); + LOG.assertTrue(body != null); + + PsiParameter[] nextParameters = lambda.getParameterList().getParameters(); + LOG.assertTrue(nextParameters.length == 1); + PsiParameter[] prevParameters = previousLambda.getParameterList().getParameters(); + LOG.assertTrue(prevParameters.length == 1); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + for(PsiReference ref : ReferencesSearch.search(nextParameters[0], new LocalSearchScope(body)).findAll()) { + PsiElement e = ref.getElement(); + PsiExpression replacement = previousBody; + if (e.getParent() instanceof PsiExpression && + ParenthesesUtils.areParenthesesNeeded(previousBody, (PsiExpression)e.getParent(), false)) { + replacement = factory.createExpressionFromText("(a)", e); + PsiExpression parenthesized = ((PsiParenthesizedExpression)replacement).getExpression(); + LOG.assertTrue(parenthesized != null); + parenthesized.replace(previousBody); + } + e.replace(replacement); + } + nextParameters[0].replace(prevParameters[0]); + PsiElement nameElement = nextCall.getMethodExpression().getReferenceNameElement(); + if(nameElement != null && !nameElement.getText().equals(newName)) { + nameElement.replace(factory.createIdentifier(newName)); + } + PsiExpression prevQualifier = mapCall.getMethodExpression().getQualifierExpression(); + if(prevQualifier == null) { + nextQualifier.delete(); + } else { + nextQualifier.replace(prevQualifier); + } + CodeStyleManager.getInstance(project).reformat(lambda); + } + + @Nullable + private static PsiLambdaExpression getLambda(PsiMethodCallExpression call) { + PsiExpression[] expressions = call.getArgumentList().getExpressions(); + if(expressions.length == 1) { + PsiExpression expression = expressions[0]; + if(expression instanceof PsiLambdaExpression) return (PsiLambdaExpression)expression; + if(expression instanceof PsiMethodReferenceExpression) { + return LambdaRefactoringUtil.convertMethodReferenceToLambda((PsiMethodReferenceExpression)expression, false, true); + } + return null; + } + if(expressions.length != 0) return null; + PsiMethod method = call.resolveMethod(); + if(method == null) return null; + PsiClass containingClass = method.getContainingClass(); + if(containingClass == null) return null; + String className = containingClass.getQualifiedName(); + if(className == null) return null; + String varName; + String type; + switch (className) { + case CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM: + varName = "i"; + type = CommonClassNames.JAVA_LANG_INTEGER; + break; + case CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM: + varName = "l"; + type = CommonClassNames.JAVA_LANG_LONG; + break; + case CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM: + varName = "d"; + type = CommonClassNames.JAVA_LANG_DOUBLE; + break; + default: + return null; + } + varName = JavaCodeStyleManager.getInstance(call.getProject()).suggestUniqueVariableName(varName, call, true); + String expression; + if("boxed".equals(method.getName())) { + expression = varName+" -> ("+type+")"+varName; + } else if("asLongStream".equals(method.getName())) { + expression = varName+" -> (long)"+varName; + } else if("asDoubleStream".equals(method.getName())) { + expression = varName+" -> (double)"+varName; + } else return null; + PsiElementFactory factory = JavaPsiFacade.getElementFactory(call.getProject()); + return (PsiLambdaExpression)factory.createExpressionFromText(expression, call); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterAsLongStreamMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterAsLongStreamMap.java new file mode 100644 index 000000000000..54ce38e4db2a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterAsLongStreamMap.java @@ -0,0 +1,9 @@ +// "Merge 'asLongStream' call and 'map' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).mapToLong(i -> (long) i * 2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterBoxedForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterBoxedForEach.java new file mode 100644 index 000000000000..d2d8a82224de --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterBoxedForEach.java @@ -0,0 +1,9 @@ +// "Merge 'boxed' call and 'forEach' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).forEach((l) -> System.out.println((Long) l)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapBoxed.java new file mode 100644 index 000000000000..6f11313cefb4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapBoxed.java @@ -0,0 +1,9 @@ +// "Merge 'map' call and 'boxed' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().mapToObj(x -> (Long) (x * 2)).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapForEach.java new file mode 100644 index 000000000000..26e0d0f5d22f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapForEach.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'forEach' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5)).forEach((charSequence) -> System.out.println(charSequence.length())); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMap.java new file mode 100644 index 000000000000..b8d02f286f7c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMap.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5).length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapExpr.java new file mode 100644 index 000000000000..b8d02f286f7c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapExpr.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5).length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapMR.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapMR.java new file mode 100644 index 000000000000..c0fd79876619 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapMR.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map((cs) -> cs.subSequence(1, 5).length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapToInt.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapToInt.java new file mode 100644 index 000000000000..51f09ebafda9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapMapToInt.java @@ -0,0 +1,9 @@ +// "Inline 'map' body into the next 'mapToInt' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().mapToInt((cs) -> ((String) cs).length()).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntAsLongStream.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntAsLongStream.java new file mode 100644 index 000000000000..386a1c9a07ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntAsLongStream.java @@ -0,0 +1,9 @@ +// "Merge 'mapToInt' call and 'asLongStream' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToLong(s -> (long) s.length()).map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntFlatMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntFlatMap.java new file mode 100644 index 000000000000..84a406fbdd60 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/afterMapToIntFlatMap.java @@ -0,0 +1,9 @@ +// "Inline 'mapToInt' body into the next 'flatMap' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).flatMapToInt(s1 -> IntStream.range(0, s1.length())).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeAsLongStreamMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeAsLongStreamMap.java new file mode 100644 index 000000000000..b4da3d8be0b0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeAsLongStreamMap.java @@ -0,0 +1,9 @@ +// "Merge 'asLongStream' call and 'map' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeBoxedForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeBoxedForEach.java new file mode 100644 index 000000000000..236777cd7dcf --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeBoxedForEach.java @@ -0,0 +1,9 @@ +// "Merge 'boxed' call and 'forEach' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeFlatMapForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeFlatMapForEach.java new file mode 100644 index 000000000000..bcfaff7752f8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeFlatMapForEach.java @@ -0,0 +1,9 @@ +// "Inline 'flatMap' body into the next 'forEach' call" "false" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).flatMap(s -> IntStream.range(0, s)).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapBoxed.java new file mode 100644 index 000000000000..abe11d46b724 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapBoxed.java @@ -0,0 +1,9 @@ +// "Merge 'map' call and 'boxed' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapForEach.java new file mode 100644 index 000000000000..1f915897ee6e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapForEach.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'forEach' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5)).map(CharSequence::length).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMap.java new file mode 100644 index 000000000000..231cc075b2db --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMap.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5)).map(cs -> cs.length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapExpr.java new file mode 100644 index 000000000000..c85780d55a36 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapExpr.java @@ -0,0 +1,10 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> { + return cs.subSequence(1, 5); + }).map(cs -> cs.length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapMR.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapMR.java new file mode 100644 index 000000000000..a5651a40f0eb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapMR.java @@ -0,0 +1,8 @@ +// "Inline 'map' body into the next 'map' call" "true" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> cs.subSequence(1, 5)).map(CharSequence::length).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapToInt.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapToInt.java new file mode 100644 index 000000000000..62f169782191 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapToInt.java @@ -0,0 +1,9 @@ +// "Inline 'map' body into the next 'mapToInt' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapTwoExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapTwoExpr.java new file mode 100644 index 000000000000..ef277ee38ef9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapMapTwoExpr.java @@ -0,0 +1,11 @@ +// "Inline 'map' body into the next 'map' call" "false" +import java.util.List; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> { + cs = cs.subSequence(0, 10); + return cs.subSequence(1, 5); + }).map(cs -> cs.length()).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntAsLongStream.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntAsLongStream.java new file mode 100644 index 000000000000..a95324d26a1f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntAsLongStream.java @@ -0,0 +1,9 @@ +// "Merge 'mapToInt' call and 'asLongStream' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).asLongStream().map(x -> x*2).boxed().forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntFlatMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntFlatMap.java new file mode 100644 index 000000000000..3bd66dd69a3b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap/beforeMapToIntFlatMap.java @@ -0,0 +1,9 @@ +// "Inline 'mapToInt' body into the next 'flatMap' call" "true" +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public static void test(List list) { + list.stream().map(cs -> (String)cs).mapToInt(String::length).flatMap(s -> IntStream.range(0, s)).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/intention/InlineStreamMapActionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/intention/InlineStreamMapActionTest.java new file mode 100644 index 000000000000..a96409ec6b74 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/intention/InlineStreamMapActionTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2016 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.codeInsight.intention; + +import com.intellij.codeInsight.daemon.LightIntentionActionTestCase; + +public class InlineStreamMapActionTest extends LightIntentionActionTestCase { + + public void test() throws Exception { doAllTests(); } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/inlineStreamMap"; + } +} diff --git a/platform/core-api/src/com/intellij/psi/CommonClassNames.java b/platform/core-api/src/com/intellij/psi/CommonClassNames.java index f2da8bc222e4..f1ff9187bee8 100644 --- a/platform/core-api/src/com/intellij/psi/CommonClassNames.java +++ b/platform/core-api/src/com/intellij/psi/CommonClassNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -105,6 +105,9 @@ public interface CommonClassNames { @NonNls String JAVA_UTIL_STREAM_BASE_STREAM = "java.util.stream.BaseStream"; @NonNls String JAVA_UTIL_STREAM_STREAM = "java.util.stream.Stream"; + @NonNls String JAVA_UTIL_STREAM_INT_STREAM = "java.util.stream.IntStream"; + @NonNls String JAVA_UTIL_STREAM_LONG_STREAM = "java.util.stream.LongStream"; + @NonNls String JAVA_UTIL_STREAM_DOUBLE_STREAM = "java.util.stream.DoubleStream"; @NonNls String JAVA_UTIL_STREAM_COLLECTORS = "java.util.stream.Collectors"; @NonNls String JAVA_UTIL_FUNCTION_PREDICATE = "java.util.function.Predicate"; diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index af978ba4e90c..534ffab3da98 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -174,6 +174,9 @@ intention.split.filter.text=Split into filter's chain intention.split.filter.family=Split filter intention.merge.filter.text=Merge filter's chain intention.merge.filter.family=Merge filters +intention.inline.map.inline.text=Inline ''{0}'' body into the next ''{1}'' call +intention.inline.map.merge.text=Merge ''{0}'' call and ''{1}'' call +intention.inline.map.family=Inline stream mapping method intention.introduce.variable.text=Introduce local variable intention.encapsulate.field.text=Encapsulate field intention.implement.abstract.method.family=Implement Abstract Method diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/style/MethodRefCanBeReplacedWithLambdaInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/style/MethodRefCanBeReplacedWithLambdaInspection.java index bb34d5cb9e9b..4d755dcf77d1 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/style/MethodRefCanBeReplacedWithLambdaInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/style/MethodRefCanBeReplacedWithLambdaInspection.java @@ -74,6 +74,16 @@ public class MethodRefCanBeReplacedWithLambdaInspection extends BaseInspection { return null; } + public static boolean isWithSideEffects(PsiMethodReferenceExpression methodReferenceExpression) { + final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression(); + if (qualifierExpression != null) { + final List sideEffects = new ArrayList<>(); + SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); + return !sideEffects.isEmpty(); + } + return false; + } + private static class MethodRefToLambdaVisitor extends BaseInspectionVisitor { @Override public void visitMethodReferenceExpression(PsiMethodReferenceExpression methodReferenceExpression) { @@ -91,16 +101,6 @@ public class MethodRefCanBeReplacedWithLambdaInspection extends BaseInspection { if (onTheFly || ApplicationManager.getApplication().isUnitTestMode()) return SideEffectsMethodRefToLambdaFix::new; return null; } - - private static boolean isWithSideEffects(PsiMethodReferenceExpression methodReferenceExpression) { - final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression(); - if (qualifierExpression != null) { - final List sideEffects = new ArrayList<>(); - SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); - return !sideEffects.isEmpty(); - } - return false; - } } private static class MethodRefToLambdaFix extends InspectionGadgetsFix { diff --git a/resources-en/src/intentionDescriptions/InlineStreamMapAction/after.java.template b/resources-en/src/intentionDescriptions/InlineStreamMapAction/after.java.template new file mode 100644 index 000000000000..96a6374ab244 --- /dev/null +++ b/resources-en/src/intentionDescriptions/InlineStreamMapAction/after.java.template @@ -0,0 +1,7 @@ +import java.util.List; + +public class X { + boolean test(List list) { + return list.stream().anyMatch(s -> s.toLowerCase().equals("test")); + } +} \ No newline at end of file diff --git a/resources-en/src/intentionDescriptions/InlineStreamMapAction/before.java.template b/resources-en/src/intentionDescriptions/InlineStreamMapAction/before.java.template new file mode 100644 index 000000000000..7c507d8efd91 --- /dev/null +++ b/resources-en/src/intentionDescriptions/InlineStreamMapAction/before.java.template @@ -0,0 +1,7 @@ +import java.util.List; + +public class X { + boolean test(List list) { + return list.stream().map(s -> s.toLowerCase()).anyMatch(s -> s.equals("test")); + } +} \ No newline at end of file diff --git a/resources-en/src/intentionDescriptions/InlineStreamMapAction/description.html b/resources-en/src/intentionDescriptions/InlineStreamMapAction/description.html new file mode 100644 index 000000000000..60ad2cec8290 --- /dev/null +++ b/resources-en/src/intentionDescriptions/InlineStreamMapAction/description.html @@ -0,0 +1,7 @@ + + +This intention inlines Stream.map() and similar calls into the next stream operation when possible. + +As during normal variable inline this intention may change the code semantics if mapping result is used more than once and has side-effects. + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 21853176ce56..98d0f7f154f8 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -873,6 +873,10 @@ com.intellij.codeInsight.intention.impl.MergeFilterChainAction Java/Streams + + com.intellij.codeInsight.intention.impl.InlineStreamMapAction + Java/Streams + com.intellij.codeInsight.intention.impl.InvertIfConditionAction Java/Control Flow