StreamApiMigrationInspection: support limit count with separate variable

This commit is contained in:
Tagir Valeev
2016-12-22 12:33:15 +07:00
parent 7a4f20aacb
commit e2b7d60680
9 changed files with 205 additions and 119 deletions
@@ -28,6 +28,7 @@ import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,7 +55,6 @@ class CollectMigration extends BaseStreamApiMigration {
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiLoopStatement loopStatement = tb.getMainLoop();
tb = tb.tryPeelLimit();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiMethodCallExpression call = tb.getSingleMethodCall();
if (call == null) return null;
@@ -97,7 +97,7 @@ class CollectMigration extends BaseStreamApiMigration {
return toArrayConversion;
}
PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(loopStatement, PsiComment.class, PsiWhiteSpace.class);
String comparatorText = StreamApiMigrationInspection.tryExtractSortComparatorText(nextStatement, variable);
String comparatorText = tryExtractSortComparatorText(nextStatement, variable);
if(comparatorText != null) {
builder.append(".sorted(").append(comparatorText).append(")");
nextStatement.delete();
@@ -278,4 +278,43 @@ class CollectMigration extends BaseStreamApiMigration {
return "toCollection(() -> " + initializer.getText() + ")";
}
}
/**
*
* @param element sort statement candidate (must be PsiExpressionStatement)
* @param list list which should be sorted
* @return comparator string representation, empty string if natural order is used or null if given statement is not sort statement
*/
@Contract(value = "null, _ -> null")
private static String tryExtractSortComparatorText(PsiElement element, PsiVariable list) {
if(!(element instanceof PsiExpressionStatement)) return null;
PsiExpression expression = ((PsiExpressionStatement)element).getExpression();
if(!(expression instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
if(!"sort".equals(methodExpression.getReferenceName())) return null;
PsiMethod method = methodCall.resolveMethod();
if(method == null) return null;
PsiClass containingClass = method.getContainingClass();
if(containingClass == null) return null;
PsiExpression listExpression = null;
PsiExpression comparatorExpression = null;
if(CommonClassNames.JAVA_UTIL_COLLECTIONS.equals(containingClass.getQualifiedName())) {
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length == 1) {
listExpression = args[0];
} else if(args.length == 2) {
listExpression = args[0];
comparatorExpression = args[1];
} else return null;
} else if(InheritanceUtil.isInheritor(containingClass, CommonClassNames.JAVA_UTIL_LIST)) {
listExpression = methodExpression.getQualifierExpression();
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length != 1) return null;
comparatorExpression = args[0];
}
if(!(listExpression instanceof PsiReferenceExpression) || !((PsiReferenceExpression)listExpression).isReferenceTo(list)) return null;
if(comparatorExpression == null || ExpressionUtils.isNullLiteral(comparatorExpression)) return "";
return comparatorExpression.getText();
}
}
@@ -15,7 +15,6 @@
*/
package com.intellij.codeInspection.streamMigration;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.LimitOp;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
@@ -29,18 +28,16 @@ class CountMigration extends BaseStreamApiMigration {
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
tb = tb.tryPeelLimit();
PsiExpression expression = tb.getSingleExpression(PsiExpression.class);
StreamApiMigrationInspection.Operation lastOperation = tb.getLastOperation();
if (expression == null && lastOperation instanceof LimitOp) {
expression = ((LimitOp)lastOperation).getCountExpression();
if (expression == null) {
expression = tb.getCountExpression();
}
PsiExpression operand = StreamApiMigrationInspection.extractIncrementedLValue(expression);
if (!(operand instanceof PsiReferenceExpression)) return null;
PsiElement element = ((PsiReferenceExpression)operand).resolve();
if (!(element instanceof PsiLocalVariable)) return null;
PsiLocalVariable var = (PsiLocalVariable)element;
StringBuilder builder = generateStream(lastOperation).append(".count()");
StringBuilder builder = generateStream(tb.getLastOperation()).append(".count()");
return replaceWithNumericAddition(tb.getMainLoop(), var, builder, PsiType.LONG);
}
}
@@ -65,7 +65,7 @@ class MigrateToStreamFix implements LocalQuickFix {
TerminalBlock tb = TerminalBlock.from(source, body);
PsiElement result = myMigration.migrate(project, body, tb);
if(result != null) {
source.cleanUpSource();
tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp);
simplifyAndFormat(project, result);
}
}
@@ -306,11 +306,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
private static boolean isCountOperation(List<PsiVariable> nonFinalVariables, TerminalBlock tb) {
PsiLocalVariable variable = getIncrementedVariable(tb.getSingleExpression(PsiExpression.class), tb, nonFinalVariables);
LimitOp limitOp = tb.getLastOperation(LimitOp.class);
if (limitOp == null) {
PsiExpression counter = tb.getCountExpression();
if (counter == null) {
return variable != null;
}
PsiExpression counter = PsiUtil.skipParenthesizedExprDown(limitOp.getCountExpression());
if (tb.isEmpty()) {
// like "if(++count == limit) break"
variable = getIncrementedVariable(counter, tb, nonFinalVariables);
@@ -330,12 +329,11 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
if (tb.dependsOn(qualifierExpression)) return false;
LimitOp limitOp = tb.getLastOperation(LimitOp.class);
PsiExpression count = tb.getCountExpression();
PsiClass qualifierClass = extractQualifierClass(tb, call);
if (qualifierClass != null) {
if (limitOp == null) return true;
if (count == null) return true;
// like "list.add(x); if(list.size() >= limit) break;"
PsiExpression count = limitOp.getCountExpression();
if(!(count instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression sizeCall = (PsiMethodCallExpression)count;
PsiExpression sizeQualifier = sizeCall.getMethodExpression().getQualifierExpression();
@@ -343,7 +341,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(sizeQualifier, qualifierExpression) &&
InheritanceUtil.isInheritor(qualifierClass, CommonClassNames.JAVA_UTIL_LIST);
}
if (qualifierExpression instanceof PsiMethodCallExpression && limitOp == null) {
if (qualifierExpression instanceof PsiMethodCallExpression && count == null) {
PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)qualifierExpression;
if (isCallOf(qualifierCall, CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent")) {
PsiExpression[] args = qualifierCall.getArgumentList().getExpressions();
@@ -577,7 +575,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
if(source == null) return;
if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return;
TerminalBlock tb = TerminalBlock.from(source, body);
if(tb.isEmpty()) return;
BaseStreamApiMigration migration = findMigration(statement, body, tb);
if(migration != null) {
@@ -609,21 +606,16 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
.remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder)
.remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList();
TerminalBlock tbWithLimit = tb.tryPeelLimit();
List<PsiVariable> nonFinalVariablesWithLimit = nonFinalVariables;
if(tbWithLimit != tb) {
nonFinalVariablesWithLimit = StreamEx.of(nonFinalVariables)
.remove(variable -> isVariableSuitableForStream(variable, loop, tbWithLimit)).toList();
}
if (isCountOperation(nonFinalVariablesWithLimit, tbWithLimit)) {
if (isCountOperation(nonFinalVariables, tb)) {
return new CountMigration();
}
if (isCollectCall(tb) && nonFinalVariables.isEmpty()) {
return findCollectMigration(loop, tb);
}
if (tb.getCountExpression() != null || tb.isEmpty()) return null;
if (getAccumulatedVariable(tb, nonFinalVariables) != null) {
return new SumMigration();
}
if (isCollectCall(tbWithLimit) && nonFinalVariablesWithLimit.isEmpty()) {
return findCollectMigration(loop, tbWithLimit);
}
if (isCollectMapCall(tb) && nonFinalVariables.isEmpty() && (REPLACE_TRIVIAL_FOREACH || tb.hasOperations())) {
return new CollectMigration("collect");
}
@@ -697,7 +689,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
else
methodName = "collect";
} else {
if (!SUGGEST_FOREACH || tb.getLastOperation() instanceof LimitOp) return null;
if (!SUGGEST_FOREACH || tb.getCountExpression() != null) return null;
methodName = "forEach";
}
}
@@ -774,45 +766,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
}
/**
*
* @param element sort statement candidate (must be PsiExpressionStatement)
* @param list list which should be sorted
* @return comparator string representation, empty string if natural order is used or null if given statement is not sort statement
*/
@Contract(value = "null, _ -> null")
static String tryExtractSortComparatorText(PsiElement element, PsiVariable list) {
if(!(element instanceof PsiExpressionStatement)) return null;
PsiExpression expression = ((PsiExpressionStatement)element).getExpression();
if(!(expression instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
if(!"sort".equals(methodExpression.getReferenceName())) return null;
PsiMethod method = methodCall.resolveMethod();
if(method == null) return null;
PsiClass containingClass = method.getContainingClass();
if(containingClass == null) return null;
PsiExpression listExpression = null;
PsiExpression comparatorExpression = null;
if(CommonClassNames.JAVA_UTIL_COLLECTIONS.equals(containingClass.getQualifiedName())) {
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length == 1) {
listExpression = args[0];
} else if(args.length == 2) {
listExpression = args[0];
comparatorExpression = args[1];
} else return null;
} else if(InheritanceUtil.isInheritor(containingClass, CommonClassNames.JAVA_UTIL_LIST)) {
listExpression = methodExpression.getQualifierExpression();
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length != 1) return null;
comparatorExpression = args[0];
}
if(!(listExpression instanceof PsiReferenceExpression) || !((PsiReferenceExpression)listExpression).isReferenceTo(list)) return null;
if(comparatorExpression == null || ExpressionUtils.isNullLiteral(comparatorExpression)) return "";
return comparatorExpression.getText();
}
@Nullable
static PsiMethodCallExpression extractToArrayExpression(PsiLoopStatement statement, PsiMethodCallExpression expression) {
// return collection.toArray() or collection.toArray(new Type[0]) or collection.toArray(new Type[collection.size()]);
@@ -896,6 +849,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
myPreviousOp = previousOp;
}
void cleanUp() {}
public PsiVariable getVariable() {
return myVariable;
}
@@ -1063,13 +1018,20 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
static class LimitOp extends Operation {
private final PsiExpression myCounter;
private final PsiLocalVariable myCounterVariable;
private final int myDelta;
LimitOp(@Nullable Operation previousOp, PsiExpression counter, PsiExpression expression, PsiVariable variable, int delta) {
super(previousOp, expression, variable);
LimitOp(@Nullable Operation previousOp,
PsiVariable variable,
PsiExpression countExpression,
PsiExpression limitExpression,
PsiLocalVariable counterVariable,
int delta) {
super(previousOp, limitExpression, variable);
LOG.assertTrue(delta >= 0);
myDelta = delta;
myCounter = counter;
myCounter = countExpression;
myCounterVariable = counterVariable;
}
@Override
@@ -1077,10 +1039,26 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return ".limit(" + getLimitExpression() + ")";
}
PsiLocalVariable getCounterVariable() {
return myCounterVariable;
}
PsiExpression getCountExpression() {
return myCounter;
}
@Override
void cleanUp() {
if(myCounterVariable != null) {
myCounterVariable.delete();
}
}
@Override
boolean isWriteAllowed(PsiVariable variable, PsiExpression reference) {
return variable == myCounterVariable && PsiTreeUtil.isAncestor(myCounter, reference, false);
}
private String getLimitExpression() {
if(myDelta == 0) {
return myExpression.getText();
@@ -1103,9 +1081,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
myLoop = loop;
}
void cleanUpSource() {
}
PsiLoopStatement getLoop() {
return myLoop;
}
@@ -1137,7 +1112,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Override
void cleanUpSource() {
void cleanUp() {
myVariable.delete();
}
@@ -1301,7 +1276,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
// check that increment is like for(...;...;i++)
if(!(forStatement.getUpdate() instanceof PsiExpressionStatement)) return null;
PsiExpression lValue = extractIncrementedLValue(((PsiExpressionStatement)forStatement.getUpdate()).getExpression());
if(!(lValue instanceof PsiReferenceExpression) || !((PsiReferenceExpression)lValue).isReferenceTo(counter)) return null;
if(!ExpressionUtils.isReferenceTo(lValue, counter)) return null;
// check that condition is like for(...;i<bound;...) or for(...;i<=bound;...)
if(!(forStatement.getCondition() instanceof PsiBinaryExpression)) return null;
@@ -37,6 +37,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import static com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.*;
/**
* This immutable class represents the code which should be performed
* as a part of forEach operation of resulting stream possibly with
@@ -45,12 +47,12 @@ import java.util.Objects;
class TerminalBlock {
private static final Logger LOG = Logger.getInstance(TerminalBlock.class);
private final @NotNull StreamApiMigrationInspection.Operation myPreviousOp;
private final @NotNull Operation myPreviousOp;
private final @NotNull PsiVariable myVariable;
private final @NotNull PsiStatement[] myStatements;
// At least one previous operation is present (stream source)
private TerminalBlock(@NotNull StreamApiMigrationInspection.Operation previousOp, @NotNull PsiVariable variable, @NotNull PsiStatement... statements) {
private TerminalBlock(@NotNull Operation previousOp, @NotNull PsiVariable variable, @NotNull PsiStatement... statements) {
for(PsiStatement statement : statements) Objects.requireNonNull(statement);
myVariable = variable;
while(true) {
@@ -108,7 +110,7 @@ class TerminalBlock {
if(ifStatement.getElseBranch() == null && ifStatement.getCondition() != null) {
PsiStatement thenBranch = ifStatement.getThenBranch();
if(thenBranch != null) {
return new TerminalBlock(new StreamApiMigrationInspection.FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, false), myVariable, thenBranch);
return new TerminalBlock(new FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, false), myVariable, thenBranch);
}
}
}
@@ -132,8 +134,7 @@ class TerminalBlock {
} else {
statements = Arrays.copyOfRange(myStatements, 1, myStatements.length);
}
return new TerminalBlock(new StreamApiMigrationInspection.FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, true),
myVariable, statements);
return new TerminalBlock(new FilterOp(myPreviousOp, ifStatement.getCondition(), myVariable, true), myVariable, statements);
}
}
return null;
@@ -146,20 +147,20 @@ class TerminalBlock {
* @return extracted operation or null if extraction is not possible
*/
@Nullable
TerminalBlock extractOperation() {
private TerminalBlock extractOperation() {
TerminalBlock withFilter = extractFilter();
if(withFilter != null) return withFilter;
// extract flatMap
if(getSingleStatement() instanceof PsiLoopStatement) {
PsiLoopStatement loopStatement = (PsiLoopStatement)getSingleStatement();
StreamApiMigrationInspection.StreamSource source = StreamApiMigrationInspection.StreamSource.tryCreate(loopStatement);
StreamSource source = StreamSource.tryCreate(loopStatement);
final PsiStatement body = loopStatement.getBody();
if(source == null || body == null) return null;
// flatMap from primitive to primitive is supported only if primitive types match
// 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;
StreamApiMigrationInspection.FlatMapOp op = new StreamApiMigrationInspection.FlatMapOp(myPreviousOp, source, myVariable);
FlatMapOp op = new FlatMapOp(myPreviousOp, source, myVariable);
TerminalBlock withFlatMap = new TerminalBlock(op, source.getVariable(), body);
if(!VariableAccessUtils.variableIsUsed(myVariable, body)) {
return withFlatMap;
@@ -174,7 +175,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 StreamApiMigrationInspection.CompoundFilterOp((StreamApiMigrationInspection.FilterOp)withFlatMapFilter.getLastOperation(), op),
return new TerminalBlock(new CompoundFilterOp((FilterOp)withFlatMapFilter.getLastOperation(), op),
myVariable, Arrays.copyOfRange(statements, 0, statements.length-1));
}
}
@@ -190,12 +191,11 @@ class TerminalBlock {
PsiElement element = elements[0];
if(element instanceof PsiLocalVariable) {
PsiLocalVariable declaredVar = (PsiLocalVariable)element;
if(StreamApiMigrationInspection.isSupported(declaredVar.getType())) {
if (isSupported(declaredVar.getType())) {
PsiExpression initializer = declaredVar.getInitializer();
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
if (initializer != null && ReferencesSearch.search(myVariable, new LocalSearchScope(leftOver)).findFirst() == null) {
StreamApiMigrationInspection.MapOp
op = new StreamApiMigrationInspection.MapOp(myPreviousOp, initializer, myVariable, declaredVar.getType());
MapOp op = new MapOp(myPreviousOp, initializer, myVariable, declaredVar.getType());
return new TerminalBlock(op, declaredVar, leftOver);
}
}
@@ -205,11 +205,12 @@ class TerminalBlock {
PsiExpression rValue = ExpressionUtils.getAssignmentTo(first, myVariable);
if(rValue != null) {
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
StreamApiMigrationInspection.MapOp op = new StreamApiMigrationInspection.MapOp(myPreviousOp, rValue, myVariable, myVariable.getType());
MapOp op = new MapOp(myPreviousOp, rValue, myVariable, myVariable.getType());
return new TerminalBlock(op, myVariable, leftOver);
}
}
return null;
TerminalBlock withLimit = tryPeelLimit(true);
return withLimit == this ? null : withLimit;
}
/**
@@ -218,18 +219,24 @@ class TerminalBlock {
* <p>It's not guaranteed that the peeled condition actually could be translated to the limit operation:
* additional checks will be necessary</p>
*
* @param dedicatedCounter whether peeled counter must be an increment statement for dedicated local variable
* @return new terminal block with additional limit operation or self if peeling is failed.
*/
TerminalBlock tryPeelLimit() {
private TerminalBlock tryPeelLimit(boolean dedicatedCounter) {
if(myStatements.length == 0) return this;
TerminalBlock tb = this;
PsiStatement[] statements = {};
if(myStatements.length > 1) {
statements = new PsiStatement[]{myStatements[0]};
tb = new TerminalBlock(myPreviousOp, myVariable, Arrays.copyOfRange(myStatements, 1, myStatements.length)).extractFilter();
int count = myStatements.length - 1;
if (myStatements[count] instanceof PsiBreakStatement || myStatements[count] instanceof PsiReturnStatement) {
// to support conditions like if(...) continue; break; or if(...) continue; return ...;
count--;
}
statements = Arrays.copyOfRange(myStatements, 0, count);
tb = new TerminalBlock(myPreviousOp, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter();
}
if (tb == null || !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), getMainLoop())) return this;
StreamApiMigrationInspection.FilterOp filter = tb.getLastOperation(StreamApiMigrationInspection.FilterOp.class);
FilterOp filter = tb.getLastOperation(FilterOp.class);
if(filter == null) return this;
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(filter.getExpression());
if(!(condition instanceof PsiBinaryExpression)) return this;
@@ -258,6 +265,15 @@ class TerminalBlock {
}
PsiExpression countExpression = PsiUtil.skipParenthesizedExprDown(flipped ? binOp.getROperand() : binOp.getLOperand());
if(countExpression == null || VariableAccessUtils.variableIsUsed(myVariable, countExpression)) return this;
PsiExpression incrementedValue = extractIncrementedLValue(countExpression);
PsiLocalVariable var = null;
if (dedicatedCounter) {
if (!(incrementedValue instanceof PsiReferenceExpression)) return this;
PsiElement element = ((PsiReferenceExpression)incrementedValue).resolve();
if (!(element instanceof PsiLocalVariable)) return this;
var = (PsiLocalVariable)element;
if (!ExpressionUtils.isZero(var.getInitializer()) || ReferencesSearch.search(var).findAll().size() != 1) return this;
}
PsiExpression limit = flipped ? binOp.getLOperand() : binOp.getROperand();
if(!ExpressionUtils.isSimpleExpression(limit) || VariableAccessUtils.variableIsUsed(myVariable, limit)) return this;
PsiType type = limit.getType();
@@ -266,33 +282,41 @@ class TerminalBlock {
delta++;
}
StreamApiMigrationInspection.Operation prev = filter.getPreviousOp();
Operation prev = filter.getPreviousOp();
LOG.assertTrue(prev != null);
TerminalBlock block = new TerminalBlock(prev, myVariable, statements);
if(StreamApiMigrationInspection.extractIncrementedLValue(countExpression) == null) {
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; }
while (true) {
TerminalBlock newBlock = block.extractOperation();
if (newBlock == null || newBlock.getLastOperation() instanceof StreamApiMigrationInspection.FlatMapOp) break;
if (newBlock == null || newBlock.getLastOperation() instanceof FlatMapOp) break;
block = newBlock;
}
}
StreamApiMigrationInspection.LimitOp
limitOp = new StreamApiMigrationInspection.LimitOp(block.getLastOperation(), countExpression, limit, block.getVariable(), delta);
LimitOp limitOp = new LimitOp(block.getLastOperation(), block.getVariable(), countExpression, limit, var, delta);
return new TerminalBlock(limitOp, block.getVariable(), block.getStatements());
}
@NotNull
public StreamApiMigrationInspection.Operation getLastOperation() {
Operation getLastOperation() {
return myPreviousOp;
}
@Nullable
public <T extends StreamApiMigrationInspection.Operation> T getLastOperation(Class<T> clazz) {
<T extends Operation> T getLastOperation(Class<T> clazz) {
return clazz.isInstance(myPreviousOp) ? clazz.cast(myPreviousOp) : null;
}
@Nullable
PsiExpression getCountExpression() {
LimitOp limitOp = getLastOperation(LimitOp.class);
if (limitOp != null && limitOp.getCounterVariable() == null) {
return limitOp.getCountExpression();
}
return null;
}
/**
* Extract all possible intermediate operations
* @return the terminal block with all possible terminal operations extracted (may return this if no operations could be extracted)
@@ -303,50 +327,49 @@ class TerminalBlock {
}
@NotNull
public PsiVariable getVariable() {
PsiVariable getVariable() {
return myVariable;
}
public boolean hasOperations() {
return !(myPreviousOp instanceof StreamApiMigrationInspection.StreamSource);
boolean hasOperations() {
return !(myPreviousOp instanceof StreamSource);
}
public boolean isEmpty() {
boolean isEmpty() {
return myStatements.length == 0;
}
@NotNull
StreamEx<StreamApiMigrationInspection.Operation> operations() {
return StreamEx.iterate(myPreviousOp, Objects::nonNull, StreamApiMigrationInspection.Operation::getPreviousOp);
StreamEx<Operation> operations() {
return StreamEx.iterate(myPreviousOp, Objects::nonNull, Operation::getPreviousOp);
}
public Collection<StreamApiMigrationInspection.Operation> getOperations() {
ArrayDeque<StreamApiMigrationInspection.Operation> ops = new ArrayDeque<>();
Collection<Operation> getOperations() {
ArrayDeque<Operation> ops = new ArrayDeque<>();
operations().forEach(ops::addFirst);
return ops;
}
public StreamApiMigrationInspection.StreamSource getSource() {
return operations().select(StreamApiMigrationInspection.StreamSource.class).collect(MoreCollectors.onlyOne())
.orElseThrow(IllegalStateException::new);
StreamSource getSource() {
return operations().select(StreamSource.class).collect(MoreCollectors.onlyOne()).orElseThrow(IllegalStateException::new);
}
public PsiLoopStatement getMainLoop() {
PsiLoopStatement getMainLoop() {
return getSource().getLoop();
}
/**
* @return stream of physical expressions used in intermediate operations in arbitrary order
*/
public StreamEx<PsiExpression> intermediateExpressions() {
return operations().remove(StreamApiMigrationInspection.StreamSource.class::isInstance).flatMap(StreamApiMigrationInspection.Operation::expressions);
StreamEx<PsiExpression> intermediateExpressions() {
return operations().remove(StreamSource.class::isInstance).flatMap(Operation::expressions);
}
/**
* @return stream of physical expressions used in stream source and intermediate operations in arbitrary order
*/
public StreamEx<PsiExpression> intermediateAndSourceExpressions() {
return operations().flatMap(StreamApiMigrationInspection.Operation::expressions);
StreamEx<PsiExpression> intermediateAndSourceExpressions() {
return operations().flatMap(Operation::expressions);
}
/**
@@ -355,7 +378,7 @@ class TerminalBlock {
* @param factory factory to use to create new element if necessary
* @return the PsiElement
*/
public PsiElement convertToElement(PsiElementFactory factory) {
PsiElement convertToElement(PsiElementFactory factory) {
if (myStatements.length == 1) {
return myStatements[0];
}
@@ -367,8 +390,8 @@ class TerminalBlock {
}
@NotNull
public static TerminalBlock from(StreamApiMigrationInspection.StreamSource source, @NotNull PsiStatement body) {
return new TerminalBlock(source, source.myVariable, body).extractOperations();
static TerminalBlock from(StreamSource source, @NotNull PsiStatement body) {
return new TerminalBlock(source, source.myVariable, body).extractOperations().tryPeelLimit(false);
}
boolean dependsOn(PsiExpression qualifier) {
@@ -0,0 +1,10 @@
// "Replace with findFirst()" "true"
import java.util.Arrays;
import java.util.Objects;
public class Main {
public String test(String[] array) {
return Arrays.stream(array).limit(11).filter(Objects::nonNull).findFirst().orElse("");
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public Set<String> test(String[] array) {
Set<String> set = Arrays.stream(array).filter(Objects::nonNull).limit(10).collect(Collectors.toSet());
return set;
}
}
@@ -0,0 +1,14 @@
// "Replace with findFirst()" "true"
public class Main {
public String test(String[] array) {
int count = 0;
for(String str : a<caret>rray) {
if (str != null) {
return str;
}
if(++count > 10) return "";
}
return "";
}
}
@@ -0,0 +1,17 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
public Set<String> test(String[] array) {
int count = 0;
Set<String> set = new HashSet<>();
for(String str : a<caret>rray) {
if (str != null) {
set.add(str);
if(++count == 10) break;
}
}
return set;
}
}