mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
+76
@@ -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");
|
||||
}
|
||||
}
|
||||
-171
@@ -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");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
+51
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
-1
@@ -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.*;
|
||||
|
||||
|
||||
+2
-2
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
-1
@@ -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.*;
|
||||
|
||||
|
||||
+10
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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.*;
|
||||
|
||||
|
||||
+13
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user