ExtractStreamMapAction: made stream-agnostic (with ability to extend to other chaining lambda APIs like Guava, RxJava, etc.); implemented for CompletableFuture; existing lambda is always preserved now.

This commit is contained in:
Tagir Valeev
2017-03-06 15:32:16 +07:00
parent bd3c0ddfba
commit 504a5727d6
43 changed files with 444 additions and 232 deletions
@@ -0,0 +1,76 @@
/*
* 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.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.chainCall.ChainCallExtractor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Tagir Valeev
*/
public class ExtractChainedMapAction extends PsiElementBaseIntentionAction {
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
PsiLocalVariable variable =
PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class, false, PsiStatement.class, PsiLambdaExpression.class);
if (!isApplicable(variable)) return false;
setText(CodeInsightBundle.message("intention.extract.map.step.text", variable.getName()));
return true;
}
@Contract("null -> false")
private static boolean isApplicable(PsiLocalVariable variable) {
if (variable == null || variable.getName() == null) return false;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return false;
PsiDeclarationStatement declaration = tryCast(variable.getParent(), PsiDeclarationStatement.class);
if (declaration == null || declaration.getDeclaredElements().length != 1) return false;
PsiCodeBlock block = tryCast(declaration.getParent(), PsiCodeBlock.class);
if (block == null) return false;
PsiLambdaExpression lambda = tryCast(block.getParent(), PsiLambdaExpression.class);
if (ChainCallExtractor.findExtractor(lambda, initializer, variable.getType()) == null) return false;
PsiParameter parameter = lambda.getParameterList().getParameters()[0];
return ReferencesSearch.search(parameter).forEach(
(Processor<PsiReference>)ref -> PsiTreeUtil.isAncestor(initializer, ref.getElement(), false));
}
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
PsiLocalVariable variable =
PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class, false, PsiStatement.class, PsiLambdaExpression.class);
ChainCallExtractor.extractMappingStep(project, variable);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return CodeInsightBundle.message("intention.extract.map.step.family");
}
}
@@ -1,171 +0,0 @@
/*
* 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.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
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.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.StreamApiUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Tagir Valeev
*/
public class ExtractStreamMapAction extends PsiElementBaseIntentionAction {
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
PsiLocalVariable variable =
PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class, false, PsiStatement.class, PsiLambdaExpression.class);
if (!isApplicable(variable)) return false;
setText(CodeInsightBundle.message("intention.extract.map.step.text", variable.getName()));
return true;
}
@Contract("null -> false")
private static boolean isApplicable(PsiLocalVariable variable) {
if (variable == null || variable.getName() == null) return false;
if (!StreamApiUtil.isSupportedStreamElement(variable.getType())) return false;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return false;
PsiDeclarationStatement declaration = tryCast(variable.getParent(), PsiDeclarationStatement.class);
if (declaration == null || declaration.getDeclaredElements().length != 1) return false;
PsiCodeBlock block = tryCast(declaration.getParent(), PsiCodeBlock.class);
if (block == null) return false;
PsiLambdaExpression lambda = tryCast(block.getParent(), PsiLambdaExpression.class);
if (lambda == null) return false;
PsiParameterList parameters = lambda.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiExpressionList args = tryCast(lambda.getParent(), PsiExpressionList.class);
if (args == null || args.getExpressions().length != 1) return false;
PsiMethodCallExpression call = tryCast(args.getParent(), PsiMethodCallExpression.class);
if (call == null ||
!InlineStreamMapAction.NEXT_METHODS.contains(call.getMethodExpression().getReferenceName()) ||
call.getMethodExpression().getQualifierExpression() == null) {
return false;
}
PsiMethod method = call.resolveMethod();
if (method == null ||
method.getParameterList().getParametersCount() != 1 ||
!InheritanceUtil.isInheritor(method.getContainingClass(), CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
return false;
}
PsiParameter parameter = parameters.getParameters()[0];
if (ExpressionUtils.isReferenceTo(initializer, parameter) && parameter.getType().equals(variable.getType())) {
// If conversion is applied in this case, then extracted previous step will be silently removed.
// While this is correct, it may confuse the user. Having "Local variable is redundant" warning with "inline" fix is enough here.
return false;
}
if (method.getName().startsWith("flatMap")) {
PsiType outType = StreamApiUtil.getStreamElementType(call.getType());
// flatMap from primitive type works only if the stream element type matches
if (variable.getType() instanceof PsiPrimitiveType && !variable.getType().equals(outType)) return false;
}
return ReferencesSearch.search(parameter).forEach(
(Processor<PsiReference>)ref -> PsiTreeUtil.isAncestor(initializer, ref.getElement(), false));
}
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
PsiLocalVariable variable =
PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class, false, PsiStatement.class, PsiLambdaExpression.class);
if (variable == null) return;
String name = variable.getName();
if (name == null) return;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return;
PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class);
if (lambda == null) return;
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
String methodName = call.getMethodExpression().getReferenceName();
if (methodName == null) return;
PsiParameter parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters());
if (parameter == null) return;
PsiType outType = StreamApiUtil.getStreamElementType(call.getType(), false);
String mapOperation = StreamApiUtil.generateMapOperation(parameter, variable.getType(), initializer);
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
if (!mapOperation.isEmpty()) {
qualifier = (PsiExpression)qualifier.replace(factory.createExpressionFromText(qualifier.getText() + mapOperation, qualifier));
}
parameter = (PsiParameter)parameter.replace(factory.createParameter(variable.getName(), variable.getType(), parameter));
variable.delete();
LambdaRefactoringUtil.simplifyToExpressionLambda(lambda);
if (methodName.startsWith("map")) {
String replacement = StreamApiUtil.generateMapOperation(parameter, outType, lambda.getBody());
if (!replacement.isEmpty()) {
call = (PsiMethodCallExpression)call.replace(factory.createExpressionFromText(qualifier.getText() + replacement, call));
qualifier = call.getMethodExpression().getQualifierExpression();
}
else {
qualifier = (PsiExpression)call.replace(qualifier);
}
}
else {
PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
if (methodName.startsWith("flatMap")) {
String targetName = "flatMap";
if (!(typeElement.getType() instanceof PsiPrimitiveType)) {
if (PsiType.INT.equals(outType)) {
targetName = "flatMapToInt";
}
else if (PsiType.LONG.equals(outType)) {
targetName = "flatMapToLong";
}
else if (PsiType.DOUBLE.equals(outType)) {
targetName = "flatMapToDouble";
}
}
ExpressionUtils.bindCallTo(call, targetName);
}
typeElement.delete();
}
}
if (qualifier instanceof PsiMethodCallExpression) {
PsiLambdaExpression newLambda =
tryCast(ArrayUtil.getFirstElement(((PsiMethodCallExpression)qualifier).getArgumentList().getExpressions()),
PsiLambdaExpression.class);
if (newLambda != null) {
LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(newLambda);
}
}
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return CodeInsightBundle.message("intention.extract.map.step.family");
}
}
@@ -45,7 +45,7 @@ public class InlineStreamMapAction extends PsiElementBaseIntentionAction {
private static final Set<String> MAP_METHODS =
StreamEx.of("map", "mapToInt", "mapToLong", "mapToDouble", "mapToObj", "boxed", "asLongStream", "asDoubleStream").toSet();
static final Set<String> NEXT_METHODS = StreamEx
public static final Set<String> NEXT_METHODS = StreamEx
.of("flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble", "forEach", "forEachOrdered", "anyMatch", "noneMatch", "allMatch")
.append(MAP_METHODS).toSet();
@@ -0,0 +1,138 @@
/*
* 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.chainCall;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Tagir Valeev
*/
public interface ChainCallExtractor {
ExtensionPointName<ChainCallExtractor> KEY = ExtensionPointName.create("com.intellij.java.refactoring.chainCallExtractor");
/**
* Returns true if the mapping chain call can be extracted from lambda passed to the given call.
*
* @param call call to check
* @param expression expression to extract
* @param expressionType resulting type of the extracted expression
* @return true if this extractor can create a mapping step from given expression
*/
boolean canExtractChainCall(@NotNull PsiMethodCallExpression call, PsiExpression expression, PsiType expressionType);
/**
* Returns chain call string representation (starting from "." like {@code .map(x -> x.getName())}).
*
* @param variable variable to be used as chain call input
* @param expression mapping expression
* @param expressionType target expression type
* @return chain call. Result is correct only if {@link #canExtractChainCall} was checked before
* for given expression and expressionType
*/
String buildChainCall(PsiVariable variable, PsiExpression expression, PsiType expressionType);
/**
* Returns new name for the existing call from which element is to be extracted. Sometimes it should be renamed
* depending on the new element type (e.g. {@code map} to {@code mapToObj}).
*
* @param call call to fix the name
* @param newElementType new element type (to be passed as lambda parameter)
* @return new call name. Default implementation returns current name.
*/
default String fixCallName(PsiMethodCallExpression call, PsiType newElementType) {
return call.getMethodExpression().getReferenceName();
}
@Contract("null, _, _ -> null")
static ChainCallExtractor findExtractor(@Nullable PsiLambdaExpression lambda, PsiExpression expression, PsiType targetType) {
if (lambda == null) return null;
PsiParameterList parameters = lambda.getParameterList();
if (parameters.getParametersCount() != 1) return null;
PsiExpressionList args = tryCast(lambda.getParent(), PsiExpressionList.class);
if (args == null || args.getExpressions().length != 1) return null;
PsiParameter parameter = parameters.getParameters()[0];
if (ExpressionUtils.isReferenceTo(expression, parameter) && parameter.getType().equals(targetType)) {
// No-op extraction is useless
return null;
}
PsiMethodCallExpression call = tryCast(args.getParent(), PsiMethodCallExpression.class);
if (call == null) return null;
for(ChainCallExtractor extractor : KEY.getExtensions()) {
if(extractor.canExtractChainCall(call, expression, targetType)) {
return extractor;
}
}
return null;
}
static PsiLambdaExpression extractMappingStep(@NotNull Project project, PsiLocalVariable variable) {
if (variable == null) return null;
String name = variable.getName();
if (name == null) return null;
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return null;
PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class);
if (lambda == null) return null;
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class);
if (call == null) return null;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return null;
String methodName = call.getMethodExpression().getReferenceName();
if (methodName == null) return null;
PsiParameter parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters());
if (parameter == null) return null;
ChainCallExtractor extractor = findExtractor(lambda, initializer, variable.getType());
if (extractor == null) return null;
String newMethodName = extractor.fixCallName(call, variable.getType());
String mapOperation = extractor.buildChainCall(parameter, initializer, variable.getType());
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
if (!mapOperation.isEmpty()) {
qualifier = (PsiExpression)qualifier.replace(factory.createExpressionFromText(qualifier.getText() + mapOperation, qualifier));
}
parameter = (PsiParameter)parameter.replace(factory.createParameter(variable.getName(), variable.getType(), parameter));
variable.delete();
LambdaRefactoringUtil.simplifyToExpressionLambda(lambda);
PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
typeElement.delete();
}
ExpressionUtils.bindCallTo(call, newMethodName);
if (qualifier instanceof PsiMethodCallExpression) {
PsiLambdaExpression newLambda =
tryCast(ArrayUtil.getFirstElement(((PsiMethodCallExpression)qualifier).getArgumentList().getExpressions()),
PsiLambdaExpression.class);
if (newLambda != null) {
LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(newLambda);
}
}
call = (PsiMethodCallExpression)CodeStyleManager.getInstance(project).reformat(call);
return tryCast(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()), PsiLambdaExpression.class);
}
}
@@ -0,0 +1,51 @@
/*
* 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.chainCall;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import org.jetbrains.annotations.NotNull;
/**
* @author Tagir Valeev
*/
public class CompletionStageChainCallExtractor implements ChainCallExtractor {
@Override
public boolean canExtractChainCall(@NotNull PsiMethodCallExpression call, PsiExpression expression, PsiType expressionType) {
if (expressionType instanceof PsiPrimitiveType) return false;
String methodName = call.getMethodExpression().getReferenceName();
if (!"thenApply".equals(methodName) && !"thenAccept".equals(methodName) && !"thenCompose".equals(methodName)) {
return false;
}
if (call.getMethodExpression().getQualifierExpression() == null) return false;
PsiMethod method = call.resolveMethod();
return method != null &&
method.getParameterList().getParametersCount() == 1 &&
InheritanceUtil.isInheritor(method.getContainingClass(), "java.util.concurrent.CompletionStage");
}
@Override
public String buildChainCall(PsiVariable variable, PsiExpression expression, PsiType expressionType) {
if(expression instanceof PsiArrayInitializerExpression) {
expression = RefactoringUtil.convertInitializerToNormalExpression(expression, expressionType);
}
String typeArgument = OptionalUtil.getMapTypeArgument(expression, expressionType);
return "." + typeArgument + "thenApply" +
"(" + variable.getName() + "->" + expression.getText() + ")";
}
}
@@ -0,0 +1,68 @@
/*
* 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.chainCall;
import com.intellij.codeInsight.intention.impl.InlineStreamMapAction;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.siyeh.ig.psiutils.StreamApiUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* @author Tagir Valeev
*/
public class StreamChainCallExtractor implements ChainCallExtractor {
@Override
public boolean canExtractChainCall(@NotNull PsiMethodCallExpression call, PsiExpression expression, PsiType expressionType) {
if (!StreamApiUtil.isSupportedStreamElement(expressionType) ||
!InlineStreamMapAction.NEXT_METHODS.contains(call.getMethodExpression().getReferenceName()) ||
call.getMethodExpression().getQualifierExpression() == null) {
return false;
}
PsiMethod method = call.resolveMethod();
if (method == null ||
method.getParameterList().getParametersCount() != 1 ||
!InheritanceUtil.isInheritor(method.getContainingClass(), CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
return false;
}
if (method.getName().startsWith("flatMap")) {
PsiType outType = StreamApiUtil.getStreamElementType(call.getType());
// flatMap from primitive type works only if the stream element type matches
if (expressionType instanceof PsiPrimitiveType && !expressionType.equals(outType)) return false;
}
return true;
}
@Override
public String fixCallName(PsiMethodCallExpression call, PsiType inType) {
PsiType outType = StreamApiUtil.getStreamElementType(call.getType(), false);
String methodName = Objects.requireNonNull(call.getMethodExpression().getReferenceName());
if (methodName.startsWith("flatMap")) {
return Objects.requireNonNull(StreamApiUtil.getFlatMapOperationName(inType, outType));
}
if (methodName.startsWith("map")) {
return StreamApiUtil.getMapOperationName(inType, outType);
}
return methodName;
}
@Override
public String buildChainCall(PsiVariable variable, PsiExpression expression, PsiType expressionType) {
return StreamApiUtil.generateMapOperation(variable, expressionType, expression);
}
}
@@ -1,10 +1,10 @@
// "Extract variable 'lowerCase' to separate stream step" "true"
// "Extract variable 'lowerCase' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
boolean test(List<String> list) {
return list.stream().map(String::toLowerCase)
.anyMatch(lowerCase -> "test".equals(lowerCase));
.anyMatch(lowerCase -> "test".equals(lowerCase));
}
}
@@ -1,4 +1,4 @@
// "Extract variable 'arr' to separate stream step" "true"
// "Extract variable 'arr' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,9 +1,9 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
long[] testAsLongStream(int[] x) {
return Arrays.stream(x).map(i -> i * 2).asLongStream().toArray();
return Arrays.stream(x).map(i -> i * 2).mapToLong(l -> l).toArray();
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
Object[] testBoxed(int[] x) {
return Arrays.stream(x).asLongStream().boxed().toArray();
return Arrays.stream(x).asLongStream().mapToObj(l -> l).toArray();
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void testFlatMap() {
Stream.of("xyz").mapToInt(String::length).flatMap(y -> IntStream.range(0, y)).forEach(System.out::println);
Stream.of("xyz").mapToInt(String::length).flatMap(y -> IntStream.range(0, y)).forEach(System.out::println);
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void testFlatMap() {
Stream.of("xyz").map(String::length).flatMapToInt(y -> IntStream.range(0, y)).forEach(System.out::println);
Stream.of("xyz").map(String::length).flatMapToInt(y -> IntStream.range(0, y)).forEach(System.out::println);
}
}
@@ -1,9 +1,9 @@
// "Extract variable 's' to separate stream step" "true"
// "Extract variable 's' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void testFlatMap() {
IntStream.of(1, 2, 3).mapToObj(String::valueOf).flatMapToInt(s -> IntStream.range(0, s.length())).forEach(System.out::println);
IntStream.of(1, 2, 3).mapToObj(String::valueOf).flatMapToInt(s -> IntStream.range(0, s.length())).forEach(System.out::println);
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'i' to separate stream step" "true"
// "Extract variable 'i' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void test2(List<String> list) {
list.stream().mapToInt(String::length).forEach(i -> System.out.println(i));
list.stream().mapToInt(String::length).forEach(i -> System.out.println(i));
}
}
@@ -1,10 +1,10 @@
// "Extract variable 'fn' to separate stream step" "true"
// "Extract variable 'fn' to separate mapping method" "true"
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Test {
void testFunction() {
Stream.of("a", "b", "c").<Supplier<String>>map(x -> x::trim).map(fn -> fn.get()).forEach(System.out::println);
Stream.of("a", "b", "c").<Supplier<String>>map(x -> x::trim).map(fn -> fn.get()).forEach(System.out::println);
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'set' to separate stream step" "true"
// "Extract variable 'set' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void testMap(List<Map<String, String>> list) {
list.stream().map(Map::keySet).flatMap(set -> set.stream()).forEach(System.out::println);
list.stream().map(Map::keySet).flatMap(set -> set.stream()).forEach(System.out::println);
}
}
@@ -1,9 +1,9 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
void testRemoveMap() {
Stream.of("xyz").map(x -> x + x).forEach(System.out::println);
Stream.of("xyz").map(x -> x + x).map(y -> y).forEach(System.out::println);
}
}
@@ -1,4 +1,4 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -0,0 +1,10 @@
// "Extract variable 'lc' to separate mapping method" "true"
import java.util.concurrent.CompletableFuture;
public class Test {
public static void main(String[] args) {
CompletableFuture.completedFuture(" XYZ ")
.thenApply(x -> x.trim() + "|" + x.trim()).thenApply(String::toLowerCase)
.thenAccept(lc -> System.out.println(lc));
}
}
@@ -1,4 +1,4 @@
// "Extract variable 'lowerCase' to separate stream step" "true"
// "Extract variable 'lowerCase' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'lowerCase' to separate stream step" "false"
// "Extract variable 'lowerCase' to separate mapping method" "false"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'arr' to separate stream step" "true"
// "Extract variable 'arr' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'y' to separate stream step" "false"
// "Extract variable 'y' to separate mapping method" "false"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 's' to separate stream step" "true"
// "Extract variable 's' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'i' to separate stream step" "true"
// "Extract variable 'i' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'i' to separate stream step" "false"
// "Extract variable 'i' to separate mapping method" "false"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'fn' to separate stream step" "true"
// "Extract variable 'fn' to separate mapping method" "true"
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'set' to separate stream step" "true"
// "Extract variable 'set' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'y' to separate stream step" "true"
// "Extract variable 'y' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -1,4 +1,4 @@
// "Extract variable 'l' to separate stream step" "true"
// "Extract variable 'l' to separate mapping method" "true"
import java.util.*;
import java.util.stream.*;
@@ -0,0 +1,13 @@
// "Extract variable 'lc' to separate mapping method" "true"
import java.util.concurrent.CompletableFuture;
public class Test {
public static void main(String[] args) {
CompletableFuture.completedFuture(" XYZ ")
.thenApply(x -> x.trim()+"|"+x.trim())
.thenAccept(s -> {
String <caret>lc = s.toLowerCase();
System.out.println(lc);
});
}
}
@@ -17,7 +17,7 @@ package com.intellij.codeInsight.intention;
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
public class ExtractStreamMapActionTest extends LightIntentionActionTestCase {
public class ExtractChainedMapActionTest extends LightIntentionActionTestCase {
public void test() throws Exception { doAllTests(); }
@@ -177,8 +177,8 @@ 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.extract.map.step.family=Extract to separate stream step
intention.extract.map.step.text=Extract variable ''{0}'' to separate stream step
intention.extract.map.step.family=Extract to separate mapping method
intention.extract.map.step.text=Extract variable ''{0}'' to separate mapping method
intention.compose.function.text=Replace nested function call with andThen call
intention.compose.function.family=Replace nested function call with composition
intention.introduce.variable.text=Introduce local variable
@@ -102,20 +102,7 @@ public class StreamApiUtil {
return ".asDoubleStream()";
}
}
String operationName = "map";
if(outType instanceof PsiPrimitiveType) {
if(!outType.equals(inType)) {
if(PsiType.INT.equals(outType)) {
operationName = "mapToInt";
} else if(PsiType.LONG.equals(outType)) {
operationName = "mapToLong";
} else if(PsiType.DOUBLE.equals(outType)) {
operationName = "mapToDouble";
}
}
} else if(inType instanceof PsiPrimitiveType) {
operationName = "mapToObj";
}
String operationName = getMapOperationName(inType, outType);
if(outType != null && mapper instanceof PsiArrayInitializerExpression) {
mapper = RefactoringUtil.convertInitializerToNormalExpression((PsiExpression)mapper, outType);
}
@@ -123,4 +110,38 @@ public class StreamApiUtil {
return "." + typeArgument + operationName +
"(" + variable.getName() + "->" + mapper.getText() + ")";
}
@NotNull
public static String getMapOperationName(PsiType inType, @Nullable PsiType outType) {
if(outType instanceof PsiPrimitiveType) {
if(!outType.equals(inType)) {
if(PsiType.INT.equals(outType)) {
return "mapToInt";
} else if(PsiType.LONG.equals(outType)) {
return "mapToLong";
} else if(PsiType.DOUBLE.equals(outType)) {
return "mapToDouble";
}
}
} else if(inType instanceof PsiPrimitiveType) {
return "mapToObj";
}
return "map";
}
@Nullable
public static String getFlatMapOperationName(PsiType inType, PsiType outType) {
if (!(inType instanceof PsiPrimitiveType)) {
if (PsiType.INT.equals(outType)) {
return "flatMapToInt";
}
else if (PsiType.LONG.equals(outType)) {
return "flatMapToLong";
}
else if (PsiType.DOUBLE.equals(outType)) {
return "flatMapToDouble";
}
} else if (!inType.equals(outType)) return null;
return "flatMap";
}
}
@@ -0,0 +1,6 @@
<html>
<body>
This intention extracts the variable declared inside lambda operation to the separate mapping step
using <code>Stream.map()</code>, <code>CompletableFuture.thenApply()</code> or similar operation.
</body>
</html>
@@ -1,5 +0,0 @@
<html>
<body>
This intention extracts the variable declared inside lambda operation to the separate mapping step using Stream.map() or similar operation.
</body>
</html>
+6 -1
View File
@@ -329,6 +329,8 @@
<extensionPoint name="documentationDelegateProvider" interface="com.intellij.codeInsight.javadoc.DocumentationDelegateProvider"/>
<extensionPoint name="java.inspection.bulkMethodInfo" interface="com.intellij.codeInspection.bulkOperation.BulkMethodInfoProvider"/>
<extensionPoint name="java.refactoring.chainCallExtractor" interface="com.intellij.refactoring.chainCall.ChainCallExtractor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@@ -939,7 +941,7 @@
<category>Java/Streams</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.ExtractStreamMapAction</className>
<className>com.intellij.codeInsight.intention.impl.ExtractChainedMapAction</className>
<category>Java/Streams</category>
</intentionAction>
<intentionAction>
@@ -1897,6 +1899,9 @@
<java.inspection.bulkMethodInfo implementation="com.intellij.codeInspection.bulkOperation.JdkBulkMethodInfoProvider"/>
<java.refactoring.chainCallExtractor implementation="com.intellij.refactoring.chainCall.StreamChainCallExtractor"/>
<java.refactoring.chainCallExtractor implementation="com.intellij.refactoring.chainCall.CompletionStageChainCallExtractor"/>
<diff.lang.DiffIgnoredRangeProvider implementation="com.intellij.diff.lang.JavaDiffIgnoredRangeProvider"/>
</extensions>