mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
IDEA-165790 Stream API migration: support distinct() with collect(toList())
This commit is contained in:
@@ -15,21 +15,14 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.streamMigration;
|
||||
|
||||
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.CollectionStream;
|
||||
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus;
|
||||
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.Operation;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.controlFlow.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
@@ -48,7 +41,7 @@ abstract class BaseStreamApiMigration {
|
||||
|
||||
static PsiElement replaceWithNumericAddition(PsiLoopStatement loopStatement,
|
||||
PsiVariable var,
|
||||
StringBuilder builder,
|
||||
String streamText,
|
||||
PsiType expressionType) {
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loopStatement.getProject());
|
||||
restoreComments(loopStatement, loopStatement.getBody());
|
||||
@@ -57,11 +50,11 @@ abstract class BaseStreamApiMigration {
|
||||
PsiExpression initializer = var.getInitializer();
|
||||
if (ExpressionUtils.isZero(initializer)) {
|
||||
PsiType type = var.getType();
|
||||
String replacement = (type.equals(expressionType) ? "" : "(" + type.getCanonicalText() + ") ") + builder;
|
||||
String replacement = (type.equals(expressionType) ? "" : "(" + type.getCanonicalText() + ") ") + streamText;
|
||||
return replaceInitializer(loopStatement, var, initializer, replacement, status);
|
||||
}
|
||||
}
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(var.getName() + "+=" + builder + ";", loopStatement));
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(var.getName() + "+=" + streamText + ";", loopStatement));
|
||||
}
|
||||
|
||||
static PsiElement replaceInitializer(PsiLoopStatement loopStatement,
|
||||
@@ -91,25 +84,6 @@ abstract class BaseStreamApiMigration {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static StringBuilder generateStream(@NotNull Operation lastOperation) {
|
||||
return generateStream(lastOperation, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static StringBuilder generateStream(@NotNull Operation lastOperation, boolean noStreamForEmpty) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
if(noStreamForEmpty && lastOperation instanceof CollectionStream) {
|
||||
return buffer.append(lastOperation.getExpression().getText());
|
||||
}
|
||||
List<String> replacements =
|
||||
StreamEx.iterate(lastOperation, Objects::nonNull, Operation::getPreviousOp).map(Operation::createReplacement).toList();
|
||||
for(ListIterator<String> it = replacements.listIterator(replacements.size()); it.hasPrevious(); ) {
|
||||
buffer.append(it.previous());
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void removeLoop(@NotNull PsiLoopStatement statement) {
|
||||
PsiElement parent = statement.getParent();
|
||||
if (parent instanceof PsiLabeledStatement) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder builder = generateStream(new MapOp(tb.getLastOperation(), itemToAdd, tb.getVariable(), addedType));
|
||||
StringBuilder builder = new StringBuilder(tb.add(new MapOp(itemToAdd, tb.getVariable(), addedType)).generate());
|
||||
|
||||
if (variable != null) {
|
||||
InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(variable, loopStatement);
|
||||
@@ -143,7 +143,7 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
downstreamCollector = CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS + ".mapping(" +
|
||||
tb.getVariable().getName() + "->" + itemToAdd.getText() + "," + downstreamCollector + ")";
|
||||
}
|
||||
StringBuilder builder = generateStream(tb.getLastOperation());
|
||||
StringBuilder builder = new StringBuilder(tb.generate());
|
||||
builder.append(".collect(" + CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS + ".groupingBy(")
|
||||
.append(LambdaUtil.createLambda(tb.getVariable(), computeArgs[0]));
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
@@ -212,7 +212,7 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
collector.append(",()->").append(initializer.getText());
|
||||
}
|
||||
collector.append(")");
|
||||
String callText = generateStream(tb.getLastOperation()).append(".collect(").append(collector).append(")").toString();
|
||||
String callText = tb.generate() + ".collect(" + collector + ")";
|
||||
return replaceInitializer(loopStatement, variable, initializer, callText, status);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class CountMigration extends BaseStreamApiMigration {
|
||||
PsiElement element = ((PsiReferenceExpression)operand).resolve();
|
||||
if (!(element instanceof PsiLocalVariable)) return null;
|
||||
PsiLocalVariable var = (PsiLocalVariable)element;
|
||||
StringBuilder builder = generateStream(tb.getLastOperation()).append(".count()");
|
||||
return replaceWithNumericAddition(tb.getMainLoop(), var, builder, PsiType.LONG);
|
||||
return replaceWithNumericAddition(tb.getMainLoop(), var, tb.generate() + ".count()", PsiType.LONG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ class FindFirstMigration extends BaseStreamApiMigration {
|
||||
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
|
||||
PsiStatement statement = tb.getSingleStatement();
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
|
||||
StringBuilder builder = generateStream(tb.getLastOperation());
|
||||
String stream = builder.append(".findFirst()").toString();
|
||||
PsiLoopStatement loopStatement = tb.getMainLoop();
|
||||
if (statement instanceof PsiReturnStatement) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)statement;
|
||||
@@ -45,7 +43,7 @@ class FindFirstMigration extends BaseStreamApiMigration {
|
||||
if (nextReturnStatement == null) return null;
|
||||
PsiExpression orElseExpression = nextReturnStatement.getReturnValue();
|
||||
if (!ExpressionUtils.isSimpleExpression(orElseExpression)) return null;
|
||||
stream = generateOptionalUnwrap(stream, tb, value, orElseExpression, PsiTypesUtil.getMethodReturnType(returnStatement));
|
||||
String stream = generateOptionalUnwrap(tb, value, orElseExpression, PsiTypesUtil.getMethodReturnType(returnStatement));
|
||||
restoreComments(loopStatement, body);
|
||||
boolean sibling = nextReturnStatement.getParent() == loopStatement.getParent();
|
||||
PsiElement replacement = loopStatement.replace(elementFactory.createStatementFromText("return " + stream + ";", loopStatement));
|
||||
@@ -63,7 +61,7 @@ class FindFirstMigration extends BaseStreamApiMigration {
|
||||
PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
|
||||
restoreComments(loopStatement, body);
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(
|
||||
stream + ".ifPresent(" + LambdaUtil.createLambda(tb.getVariable(), expression) + ");", loopStatement));
|
||||
tb.generate() + ".findFirst().ifPresent(" + LambdaUtil.createLambda(tb.getVariable(), expression) + ");", loopStatement));
|
||||
}
|
||||
PsiExpression lValue = assignment.getLExpression();
|
||||
if (!(lValue instanceof PsiReferenceExpression)) return null;
|
||||
@@ -77,7 +75,7 @@ class FindFirstMigration extends BaseStreamApiMigration {
|
||||
if (status != InitializerUsageStatus.UNKNOWN) {
|
||||
PsiExpression initializer = var.getInitializer();
|
||||
if (initializer != null) {
|
||||
String replacementText = generateOptionalUnwrap(stream, tb, value, initializer, var.getType());
|
||||
String replacementText = generateOptionalUnwrap(tb, value, initializer, var.getType());
|
||||
return replaceInitializer(loopStatement, var, initializer, replacementText, status);
|
||||
}
|
||||
}
|
||||
@@ -86,16 +84,17 @@ class FindFirstMigration extends BaseStreamApiMigration {
|
||||
if(prevRValue != null) {
|
||||
maybeAssignment.delete();
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(
|
||||
var.getName() + " = " + generateOptionalUnwrap(stream, tb, value, prevRValue, var.getType()) + ";", loopStatement));
|
||||
var.getName() + " = " + generateOptionalUnwrap(tb, value, prevRValue, var.getType()) + ";", loopStatement));
|
||||
}
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(
|
||||
var.getName() + " = " + generateOptionalUnwrap(stream, tb, value, lValue, var.getType()) + ";", loopStatement));
|
||||
var.getName() + " = " + generateOptionalUnwrap(tb, value, lValue, var.getType()) + ";", loopStatement));
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateOptionalUnwrap(String qualifier, TerminalBlock tb,
|
||||
private static String generateOptionalUnwrap(TerminalBlock tb,
|
||||
PsiExpression trueExpression, PsiExpression falseExpression,
|
||||
PsiType targetType) {
|
||||
String qualifier = tb.generate() + ".findFirst()";
|
||||
return OptionalUtil.generateOptionalUnwrap(qualifier, tb.getVariable(), trueExpression, falseExpression, targetType, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.intellij.codeInspection.streamMigration;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -37,14 +38,12 @@ class ForEachMigration extends BaseStreamApiMigration {
|
||||
|
||||
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
|
||||
|
||||
StringBuilder buffer = generateStream(tb.getLastOperation(), true);
|
||||
String stream = tb.generate(true)+"."+getReplacement()+"(";
|
||||
PsiElement block = tb.convertToElement(elementFactory);
|
||||
|
||||
buffer.append(".").append(getReplacement()).append("(");
|
||||
|
||||
final String functionalExpressionText = tb.getVariable().getName() + " -> " + wrapInBlock(block);
|
||||
PsiExpressionStatement callStatement = (PsiExpressionStatement)elementFactory
|
||||
.createStatementFromText(buffer.toString() + functionalExpressionText + ");", loopStatement);
|
||||
.createStatementFromText(stream + functionalExpressionText + ");", loopStatement);
|
||||
callStatement = (PsiExpressionStatement)loopStatement.replace(callStatement);
|
||||
|
||||
final PsiExpressionList argumentList = ((PsiCallExpression)callStatement.getExpression()).getArgumentList();
|
||||
@@ -56,11 +55,12 @@ class ForEachMigration extends BaseStreamApiMigration {
|
||||
((PsiFunctionalExpression)expressions[0]).getFunctionalInterfaceType() == null) {
|
||||
callStatement =
|
||||
(PsiExpressionStatement)callStatement.replace(elementFactory.createStatementFromText(
|
||||
buffer.toString() + "(" + tb.getVariable().getText() + ") -> " + wrapInBlock(block) + ");", callStatement));
|
||||
stream + "(" + tb.getVariable().getText() + ") -> " + wrapInBlock(block) + ");", callStatement));
|
||||
}
|
||||
return callStatement;
|
||||
}
|
||||
|
||||
@Contract("null -> !null")
|
||||
private static String wrapInBlock(PsiElement block) {
|
||||
if (block instanceof PsiExpressionStatement) {
|
||||
return ((PsiExpressionStatement)block).getExpression().getText();
|
||||
|
||||
@@ -39,7 +39,6 @@ class MatchMigration extends BaseStreamApiMigration {
|
||||
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
|
||||
PsiLoopStatement loopStatement = tb.getMainLoop();
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
|
||||
StringBuilder builder = generateStream(tb.getLastOperation());
|
||||
if(tb.getSingleStatement() instanceof PsiReturnStatement) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
|
||||
PsiExpression value = returnStatement.getReturnValue();
|
||||
@@ -50,7 +49,7 @@ class MatchMigration extends BaseStreamApiMigration {
|
||||
PsiExpression returnValue = nextReturnStatement.getReturnValue();
|
||||
if(returnValue == null) return null;
|
||||
String methodName = foundResult ? "anyMatch" : "noneMatch";
|
||||
String streamText = addTerminalOperation(builder.toString(), methodName, loopStatement, tb);
|
||||
String streamText = addTerminalOperation(methodName, loopStatement, tb);
|
||||
restoreComments(loopStatement, body);
|
||||
if (nextReturnStatement.getParent() == loopStatement.getParent()) {
|
||||
if(!ExpressionUtils.isLiteral(returnValue, !foundResult)) {
|
||||
@@ -72,7 +71,7 @@ class MatchMigration extends BaseStreamApiMigration {
|
||||
return null;
|
||||
}
|
||||
restoreComments(loopStatement, body);
|
||||
String streamText = addTerminalOperation(builder.toString(), "anyMatch", loopStatement, tb);
|
||||
String streamText = addTerminalOperation("anyMatch", loopStatement, tb);
|
||||
PsiStatement statement = statements[0];
|
||||
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement);
|
||||
if(assignment != null) {
|
||||
@@ -109,8 +108,8 @@ class MatchMigration extends BaseStreamApiMigration {
|
||||
return loopStatement.replace(elementFactory.createStatementFromText(replacement, loopStatement));
|
||||
}
|
||||
|
||||
private static String addTerminalOperation(String origStream, String methodName, @NotNull PsiElement contextElement,
|
||||
@NotNull TerminalBlock tb) {
|
||||
private static String addTerminalOperation(String methodName, @NotNull PsiElement contextElement, @NotNull TerminalBlock tb) {
|
||||
String origStream = tb.generate();
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(contextElement.getProject());
|
||||
PsiExpression stream = elementFactory.createExpressionFromText(origStream, contextElement);
|
||||
LOG.assertTrue(stream instanceof PsiMethodCallExpression);
|
||||
|
||||
@@ -624,7 +624,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
// do not replace for(T e : arr) {} with Arrays.stream(arr).forEach(e -> {}) even if flag is set
|
||||
if (SUGGEST_FOREACH && exitPoints.isEmpty() && nonFinalVariables.isEmpty() &&
|
||||
(tb.hasOperations() ||
|
||||
(!(tb.getSource() instanceof ArrayStream) && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, loop))))) {
|
||||
(!(tb.getLastOperation() instanceof ArrayStream) && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, loop))))) {
|
||||
return new ForEachMigration("forEach");
|
||||
}
|
||||
if (!tb.hasOperations() && !REPLACE_TRIVIAL_FOREACH) return null;
|
||||
@@ -721,9 +721,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
}
|
||||
}
|
||||
if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) {
|
||||
Operation lastOp = tb.getLastOperation();
|
||||
if (!REPLACE_TRIVIAL_FOREACH && lastOp instanceof StreamSource ||
|
||||
(lastOp instanceof FilterOp && lastOp.getPreviousOp() instanceof StreamSource)) {
|
||||
if (!REPLACE_TRIVIAL_FOREACH && !tb.hasOperations() ||
|
||||
(tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) {
|
||||
return null;
|
||||
}
|
||||
return new MatchMigration("anyMatch");
|
||||
@@ -841,12 +840,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
static abstract class Operation {
|
||||
final PsiExpression myExpression;
|
||||
final PsiVariable myVariable;
|
||||
final @Nullable Operation myPreviousOp;
|
||||
|
||||
protected Operation(@Nullable Operation previousOp, PsiExpression expression, PsiVariable variable) {
|
||||
protected Operation(PsiExpression expression, PsiVariable variable) {
|
||||
myExpression = expression;
|
||||
myVariable = variable;
|
||||
myPreviousOp = previousOp;
|
||||
}
|
||||
|
||||
void cleanUp() {}
|
||||
@@ -855,17 +852,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
return myVariable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Operation getPreviousOp() {
|
||||
return myPreviousOp;
|
||||
}
|
||||
|
||||
PsiExpression getExpression() {
|
||||
return myExpression;
|
||||
}
|
||||
|
||||
StreamEx<PsiExpression> expressions() {
|
||||
return StreamEx.of(myExpression);
|
||||
return StreamEx.ofNullable(myExpression);
|
||||
}
|
||||
|
||||
abstract String createReplacement();
|
||||
@@ -878,8 +870,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
static class FilterOp extends Operation {
|
||||
private final boolean myNegated;
|
||||
|
||||
FilterOp(@Nullable Operation previousOp, PsiExpression condition, PsiVariable variable, boolean negated) {
|
||||
super(previousOp, condition, variable);
|
||||
FilterOp(PsiExpression condition, PsiVariable variable, boolean negated) {
|
||||
super(condition, variable);
|
||||
myNegated = negated;
|
||||
}
|
||||
|
||||
@@ -906,7 +898,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
private final PsiVariable myMatchVariable;
|
||||
|
||||
CompoundFilterOp(FilterOp source, FlatMapOp flatMapOp) {
|
||||
super(flatMapOp.myPreviousOp, source.getExpression(), flatMapOp.myVariable, source.myNegated);
|
||||
super(source.getExpression(), flatMapOp.myVariable, source.myNegated);
|
||||
myMatchVariable = source.myVariable;
|
||||
myFlatMapOp = flatMapOp;
|
||||
}
|
||||
@@ -931,8 +923,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
static class MapOp extends Operation {
|
||||
private final @Nullable PsiType myType;
|
||||
|
||||
MapOp(@Nullable Operation previousOp, PsiExpression expression, PsiVariable variable, @Nullable PsiType targetType) {
|
||||
super(previousOp, expression, variable);
|
||||
MapOp(PsiExpression expression, PsiVariable variable, @Nullable PsiType targetType) {
|
||||
super(expression, variable);
|
||||
myType = targetType;
|
||||
}
|
||||
|
||||
@@ -980,8 +972,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
static class FlatMapOp extends Operation {
|
||||
private final StreamSource mySource;
|
||||
|
||||
FlatMapOp(@Nullable Operation previousOp, StreamSource source, PsiVariable variable) {
|
||||
super(previousOp, source.getExpression(), variable);
|
||||
FlatMapOp(StreamSource source, PsiVariable variable) {
|
||||
super(source.getExpression(), variable);
|
||||
mySource = source;
|
||||
}
|
||||
|
||||
@@ -1021,13 +1013,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
private final PsiLocalVariable myCounterVariable;
|
||||
private final int myDelta;
|
||||
|
||||
LimitOp(@Nullable Operation previousOp,
|
||||
PsiVariable variable,
|
||||
LimitOp(PsiVariable variable,
|
||||
PsiExpression countExpression,
|
||||
PsiExpression limitExpression,
|
||||
PsiLocalVariable counterVariable,
|
||||
int delta) {
|
||||
super(previousOp, limitExpression, variable);
|
||||
super(limitExpression, variable);
|
||||
LOG.assertTrue(delta >= 0);
|
||||
myDelta = delta;
|
||||
myCounter = countExpression;
|
||||
@@ -1073,11 +1064,22 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
}
|
||||
}
|
||||
|
||||
static class DistinctOp extends Operation {
|
||||
protected DistinctOp(PsiVariable variable) {
|
||||
super(null, variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
String createReplacement() {
|
||||
return ".distinct()";
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class StreamSource extends Operation {
|
||||
private final PsiLoopStatement myLoop;
|
||||
|
||||
protected StreamSource(PsiLoopStatement loop, PsiVariable variable, PsiExpression expression) {
|
||||
super(null, expression, variable);
|
||||
super(expression, variable);
|
||||
myLoop = loop;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ class SumMigration extends BaseStreamApiMigration {
|
||||
addend = JavaPsiFacade.getElementFactory(project).createExpressionFromText(
|
||||
"(" + type.getCanonicalText() + ")" + ParenthesesUtils.getText(addend, ParenthesesUtils.MULTIPLICATIVE_PRECEDENCE), addend);
|
||||
}
|
||||
StringBuilder builder = generateStream(new MapOp(tb.getLastOperation(), addend, tb.getVariable(), type));
|
||||
builder.append(".sum()");
|
||||
return replaceWithNumericAddition(tb.getMainLoop(), var, builder, type);
|
||||
String stream = tb.add(new MapOp(addend, tb.getVariable(), type)).generate()+".sum()";
|
||||
return replaceWithNumericAddition(tb.getMainLoop(), var, stream, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,19 +22,17 @@ import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.IntArrayList;
|
||||
import com.siyeh.ig.psiutils.ComparisonUtils;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import one.util.streamex.MoreCollectors;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.*;
|
||||
@@ -47,12 +45,14 @@ import static com.intellij.codeInspection.streamMigration.StreamApiMigrationInsp
|
||||
class TerminalBlock {
|
||||
private static final Logger LOG = Logger.getInstance(TerminalBlock.class);
|
||||
|
||||
private final @NotNull Operation myPreviousOp;
|
||||
private final @NotNull PsiVariable myVariable;
|
||||
private final @NotNull Operation[] myOperations;
|
||||
private final @NotNull PsiStatement[] myStatements;
|
||||
|
||||
// At least one previous operation is present (stream source)
|
||||
private TerminalBlock(@NotNull Operation previousOp, @NotNull PsiVariable variable, @NotNull PsiStatement... statements) {
|
||||
private TerminalBlock(@NotNull Operation[] operations, @NotNull PsiVariable variable, @NotNull PsiStatement... statements) {
|
||||
// At least one operation is present (stream source)
|
||||
LOG.assertTrue(operations.length > 0);
|
||||
for(Operation operation : operations) Objects.requireNonNull(operation);
|
||||
for(PsiStatement statement : statements) Objects.requireNonNull(statement);
|
||||
myVariable = variable;
|
||||
while(true) {
|
||||
@@ -63,7 +63,15 @@ class TerminalBlock {
|
||||
} else break;
|
||||
}
|
||||
myStatements = statements;
|
||||
myPreviousOp = previousOp;
|
||||
myOperations = operations;
|
||||
}
|
||||
|
||||
private TerminalBlock(@Nullable TerminalBlock previousBlock,
|
||||
@NotNull Operation operation,
|
||||
@NotNull PsiVariable variable,
|
||||
@NotNull PsiStatement... statements) {
|
||||
this(previousBlock == null ? new Operation[]{operation} : ArrayUtil.append(previousBlock.myOperations, operation), variable,
|
||||
statements);
|
||||
}
|
||||
|
||||
Collection<PsiStatement> findExitPoints(ControlFlow controlFlow) {
|
||||
@@ -89,8 +97,7 @@ class TerminalBlock {
|
||||
PsiStatement statement = getSingleStatement();
|
||||
if(statement instanceof PsiExpressionStatement) {
|
||||
PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
|
||||
if(wantedType.isInstance(expression))
|
||||
return wantedType.cast(expression);
|
||||
return ObjectUtils.tryCast(expression, wantedType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -110,7 +117,7 @@ class TerminalBlock {
|
||||
if(ifStatement.getElseBranch() == null && ifStatement.getCondition() != null) {
|
||||
PsiStatement thenBranch = ifStatement.getThenBranch();
|
||||
if(thenBranch != null) {
|
||||
return new TerminalBlock(new FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, false), myVariable, thenBranch);
|
||||
return new TerminalBlock(this, new FilterOp(ifStatement.getCondition(), myVariable, false), myVariable, thenBranch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +141,7 @@ class TerminalBlock {
|
||||
} else {
|
||||
statements = Arrays.copyOfRange(myStatements, 1, myStatements.length);
|
||||
}
|
||||
return new TerminalBlock(new FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, true), myVariable, statements);
|
||||
return new TerminalBlock(this, new FilterOp(ifStatement.getCondition(), myVariable, true), myVariable, statements);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -160,8 +167,8 @@ class TerminalBlock {
|
||||
// otherwise it would be necessary to create bogus step like
|
||||
// .mapToObj(var -> collection.stream()).flatMap(Function.identity())
|
||||
if(myVariable.getType() instanceof PsiPrimitiveType && !myVariable.getType().equals(source.getVariable().getType())) return null;
|
||||
FlatMapOp op = new FlatMapOp(myPreviousOp, source, myVariable);
|
||||
TerminalBlock withFlatMap = new TerminalBlock(op, source.getVariable(), body);
|
||||
FlatMapOp op = new FlatMapOp(source, myVariable);
|
||||
TerminalBlock withFlatMap = new TerminalBlock(this, op, source.getVariable(), body);
|
||||
if(!VariableAccessUtils.variableIsUsed(myVariable, body)) {
|
||||
return withFlatMap;
|
||||
} else {
|
||||
@@ -175,7 +182,7 @@ class TerminalBlock {
|
||||
PsiStatement lastStatement = statements[statements.length-1];
|
||||
if (lastStatement instanceof PsiBreakStatement && op.breaksMe((PsiBreakStatement)lastStatement) &&
|
||||
ReferencesSearch.search(withFlatMapFilter.getVariable(), new LocalSearchScope(statements)).findFirst() == null) {
|
||||
return new TerminalBlock(new CompoundFilterOp((FilterOp)withFlatMapFilter.getLastOperation(), op),
|
||||
return new TerminalBlock(this, new CompoundFilterOp((FilterOp)withFlatMapFilter.getLastOperation(), op),
|
||||
myVariable, Arrays.copyOfRange(statements, 0, statements.length-1));
|
||||
}
|
||||
}
|
||||
@@ -195,8 +202,8 @@ class TerminalBlock {
|
||||
PsiExpression initializer = declaredVar.getInitializer();
|
||||
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
|
||||
if (initializer != null && ReferencesSearch.search(myVariable, new LocalSearchScope(leftOver)).findFirst() == null) {
|
||||
MapOp op = new MapOp(myPreviousOp, initializer, myVariable, declaredVar.getType());
|
||||
return new TerminalBlock(op, declaredVar, leftOver);
|
||||
MapOp op = new MapOp(initializer, myVariable, declaredVar.getType());
|
||||
return new TerminalBlock(this, op, declaredVar, leftOver);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,8 +212,8 @@ class TerminalBlock {
|
||||
PsiExpression rValue = ExpressionUtils.getAssignmentTo(first, myVariable);
|
||||
if(rValue != null) {
|
||||
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
|
||||
MapOp op = new MapOp(myPreviousOp, rValue, myVariable, myVariable.getType());
|
||||
return new TerminalBlock(op, myVariable, leftOver);
|
||||
MapOp op = new MapOp(rValue, myVariable, myVariable.getType());
|
||||
return new TerminalBlock(this, op, myVariable, leftOver);
|
||||
}
|
||||
}
|
||||
TerminalBlock withLimit = tryPeelLimit(true);
|
||||
@@ -233,7 +240,7 @@ class TerminalBlock {
|
||||
count--;
|
||||
}
|
||||
statements = Arrays.copyOfRange(myStatements, 0, count);
|
||||
tb = new TerminalBlock(myPreviousOp, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter();
|
||||
tb = new TerminalBlock(myOperations, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter();
|
||||
}
|
||||
if (tb == null || !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), getMainLoop())) return this;
|
||||
FilterOp filter = tb.getLastOperation(FilterOp.class);
|
||||
@@ -282,9 +289,7 @@ class TerminalBlock {
|
||||
delta++;
|
||||
}
|
||||
|
||||
Operation prev = filter.getPreviousOp();
|
||||
LOG.assertTrue(prev != null);
|
||||
TerminalBlock block = new TerminalBlock(prev, myVariable, statements);
|
||||
TerminalBlock block = new TerminalBlock(Arrays.copyOf(tb.myOperations, tb.myOperations.length-1), myVariable, statements);
|
||||
if (incrementedValue == null) {
|
||||
// when countExpression does not change the counter, we may try to continue extracting ops from the remaining statement
|
||||
// this is helpful to cover cases like for(...) { if(...) list.add(x); if(list.size == limit) break; }
|
||||
@@ -294,18 +299,75 @@ class TerminalBlock {
|
||||
block = newBlock;
|
||||
}
|
||||
}
|
||||
LimitOp limitOp = new LimitOp(block.getLastOperation(), block.getVariable(), countExpression, limit, var, delta);
|
||||
return new TerminalBlock(limitOp, block.getVariable(), block.getStatements());
|
||||
return block.add(new LimitOp(block.getVariable(), countExpression, limit, var, delta));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiLocalVariable extractCollectionAdditionVariable() {
|
||||
PsiMethodCallExpression call = getSingleMethodCall();
|
||||
if (!isCallOf(call, CommonClassNames.JAVA_UTIL_COLLECTION, "add")) return null;
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
if (args.length != 1 || !ExpressionUtils.isReferenceTo(args[0], myVariable)) return null;
|
||||
PsiLocalVariable collectionVariable = extractCollectionVariable(call.getMethodExpression().getQualifierExpression());
|
||||
if (collectionVariable == null || getInitializerUsageStatus(collectionVariable, getMainLoop()) == InitializerUsageStatus.UNKNOWN) {
|
||||
return null;
|
||||
}
|
||||
return collectionVariable;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TerminalBlock replace(Operation orig, Operation replacement) {
|
||||
int idx = ArrayUtil.indexOf(myOperations, orig);
|
||||
if(idx == -1) {
|
||||
throw new NoSuchElementException(orig.toString());
|
||||
}
|
||||
Operation[] ops = myOperations.clone();
|
||||
ops[idx] = replacement;
|
||||
return new TerminalBlock(ops, myVariable, myStatements);
|
||||
}
|
||||
|
||||
TerminalBlock add(Operation op) {
|
||||
return new TerminalBlock(this, op, myVariable, myStatements);
|
||||
}
|
||||
|
||||
private TerminalBlock tryExtractDistinct() {
|
||||
PsiLocalVariable collectionVariable = extractCollectionAdditionVariable();
|
||||
if(collectionVariable == null) return this;
|
||||
for(int idx = myOperations.length-1; idx > 0; idx--) {
|
||||
Operation op = myOperations[idx];
|
||||
if (op instanceof FilterOp) {
|
||||
FilterOp filter = (FilterOp)op;
|
||||
PsiExpression condition = filter.getExpression();
|
||||
if (BoolUtils.isNegation(condition)) {
|
||||
if (filter.isNegated()) continue;
|
||||
condition = BoolUtils.getNegated(condition);
|
||||
}
|
||||
else if (!filter.isNegated()) continue;
|
||||
if (!(condition instanceof PsiMethodCallExpression)) continue;
|
||||
PsiMethodCallExpression conditionCall = (PsiMethodCallExpression)condition;
|
||||
if (!ExpressionUtils.isReferenceTo(conditionCall.getMethodExpression().getQualifierExpression(), collectionVariable) ||
|
||||
!isCallOf(conditionCall, CommonClassNames.JAVA_UTIL_COLLECTION, "contains")) {
|
||||
continue;
|
||||
}
|
||||
PsiExpression[] conditionArgs = conditionCall.getArgumentList().getExpressions();
|
||||
if (conditionArgs.length == 1 && ExpressionUtils.isReferenceTo(conditionArgs[0], myVariable)) {
|
||||
return replace(op, new DistinctOp(myVariable));
|
||||
}
|
||||
} else if (!(op instanceof LimitOp)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Operation getLastOperation() {
|
||||
return myPreviousOp;
|
||||
return myOperations[myOperations.length-1];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
<T extends Operation> T getLastOperation(Class<T> clazz) {
|
||||
return clazz.isInstance(myPreviousOp) ? clazz.cast(myPreviousOp) : null;
|
||||
return ObjectUtils.tryCast(getLastOperation(), clazz);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -332,7 +394,7 @@ class TerminalBlock {
|
||||
}
|
||||
|
||||
boolean hasOperations() {
|
||||
return !(myPreviousOp instanceof StreamSource);
|
||||
return myOperations.length > 1;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
@@ -341,28 +403,18 @@ class TerminalBlock {
|
||||
|
||||
@NotNull
|
||||
StreamEx<Operation> operations() {
|
||||
return StreamEx.iterate(myPreviousOp, Objects::nonNull, Operation::getPreviousOp);
|
||||
}
|
||||
|
||||
Collection<Operation> getOperations() {
|
||||
ArrayDeque<Operation> ops = new ArrayDeque<>();
|
||||
operations().forEach(ops::addFirst);
|
||||
return ops;
|
||||
}
|
||||
|
||||
StreamSource getSource() {
|
||||
return operations().select(StreamSource.class).collect(MoreCollectors.onlyOne()).orElseThrow(IllegalStateException::new);
|
||||
return StreamEx.ofReversed(myOperations);
|
||||
}
|
||||
|
||||
PsiLoopStatement getMainLoop() {
|
||||
return getSource().getLoop();
|
||||
return ((StreamSource)myOperations[0]).getLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stream of physical expressions used in intermediate operations in arbitrary order
|
||||
*/
|
||||
StreamEx<PsiExpression> intermediateExpressions() {
|
||||
return operations().remove(StreamSource.class::isInstance).flatMap(Operation::expressions);
|
||||
return StreamEx.of(myOperations, 1, myOperations.length).flatMap(Operation::expressions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,9 +441,20 @@ class TerminalBlock {
|
||||
return block;
|
||||
}
|
||||
|
||||
String generate() {
|
||||
return generate(false);
|
||||
}
|
||||
|
||||
String generate(boolean noStreamForEmpty) {
|
||||
if(noStreamForEmpty && myOperations.length == 1 && myOperations[0] instanceof CollectionStream) {
|
||||
return myOperations[0].getExpression().getText();
|
||||
}
|
||||
return StreamEx.of(myOperations).map(Operation::createReplacement).joining();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static TerminalBlock from(StreamSource source, @NotNull PsiStatement body) {
|
||||
return new TerminalBlock(source, source.myVariable, body).extractOperations().tryPeelLimit(false);
|
||||
return new TerminalBlock(null, source, source.myVariable, body).extractOperations().tryPeelLimit(false).tryExtractDistinct();
|
||||
}
|
||||
|
||||
boolean dependsOn(PsiExpression qualifier) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Replace with collect" "true"
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
List<String> test(List<String> list) {
|
||||
List<String> result = list.stream().distinct().filter(s -> !s.contains("foo")).collect(Collectors.toList());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace with collect" "true"
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
List<String> test(Map<String, List<String>> map, int limit) {
|
||||
List<String> list = map.entrySet().stream().filter(entry -> entry.getValue() != null).flatMap(entry -> entry.getValue().stream()).distinct().limit(limit).collect(Collectors.toList());
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// "Replace with collect" "true"
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
List<String> test(List<String> list) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for(String s : li<caret>st) {
|
||||
if(result.contains(s)) {
|
||||
continue;
|
||||
}
|
||||
if(s.contains("foo")) {
|
||||
continue;
|
||||
}
|
||||
result.add(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// "Replace with collect" "true"
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Main {
|
||||
List<String> test(Map<String, List<String>> map, int limit) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Map.Entry<String, List<String>> entry : map.<caret>entrySet()) {
|
||||
if(entry.getValue() != null) {
|
||||
for(String str : entry.getValue()) {
|
||||
if (!list.contains(str)) {
|
||||
list.add(str);
|
||||
}
|
||||
if (list.size() >= limit) return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// "Replace with collect" "false"
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
List<String> test(List<String> list) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String s : li<caret>st) {
|
||||
if (result.contains(s)) {
|
||||
continue;
|
||||
}
|
||||
if (s.contains("foo")) {
|
||||
continue;
|
||||
}
|
||||
result.add(s + s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user