IDEA-165790 Stream API migration: support distinct() with collect(toList())

This commit is contained in:
Tagir Valeev
2016-12-22 16:30:54 +07:00
parent 198c67d62f
commit 9cae3c25fa
14 changed files with 245 additions and 123 deletions

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}