mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
StreamApiMigrationInspection: support limit count with separate variable
This commit is contained in:
+41
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+41
-66
@@ -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) {
|
||||
|
||||
+10
@@ -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("");
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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 "";
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user