BaseStreamApiMigration (PsiStatement loop, PsiElement body, TerminalBlock tb, boolean suggestForeach, boolean replaceTrivialForEach) { final ControlFlow controlFlow; try { controlFlow = ControlFlowFactory.getInstance(loop.getProject()) .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); } catch (AnalysisCanceledException ignored) { return null; } int startOffset = controlFlow.getStartOffset(body); int endOffset = controlFlow.getEndOffset(body); if (startOffset < 0 || endOffset < 0) return null; PsiElement surrounder = PsiTreeUtil.getParentOfType(loop, PsiLambdaExpression.class, PsiClass.class); final List<PsiVariable> nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) .remove(variable -> variable instanceof PsiField) .remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder) .remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList(); if (isCountOperation(nonFinalVariables, tb)) { return new CountMigration(true); } CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables); if (terminal != null) { boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); // Don't suggest to convert the loop which can be trivially replaced via addAll: // this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection if (addAll) return null; boolean shouldWarn = replaceTrivialForEach || tb.hasOperations() || tb.getLastOperation() instanceof BufferedReaderLines || !terminal.isTrivial(); return new CollectMigration(shouldWarn, terminal.getMethodName()); } if (JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) { return new JoiningMigration(true); } if (tb.getCountExpression() != null || tb.isEmpty()) return null; if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { return new ToArrayMigration(true); } if (getAccumulatedVariable(tb, nonFinalVariables, SUM_OPERATION) != null) { return new SumMigration(true); } FindExtremumMigration.ExtremumTerminal extremumTerminal = FindExtremumMigration.extract(tb, nonFinalVariables); if (extremumTerminal != null) { return new FindExtremumMigration(true, FindExtremumMigration.getOperation(extremumTerminal.isMax())); } for (OperationReductionMigration.ReductionOperation reductionOperation : OperationReductionMigration.OPERATIONS) { if (getAccumulatedVariable(tb, nonFinalVariables, reductionOperation) != null) { return new OperationReductionMigration(true, reductionOperation); } } Collection<PsiStatement> exitPoints = tb.findExitPoints(controlFlow); if (exitPoints == null) return null; boolean onlyNonLabeledContinue = ContainerUtil.and( exitPoints, statement -> statement instanceof PsiContinueStatement && ((PsiContinueStatement)statement).getLabelIdentifier() == null); if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) { boolean shouldWarn = suggestForeach && (replaceTrivialForEach || tb.hasOperations() || ForEachMigration.tryExtractMapExpression(tb) != null || !isTrivial(tb)); return new ForEachMigration(shouldWarn, "forEach"); } if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { return findMigrationForReturn(loop, tb, replaceTrivialForEach); } // Source and intermediate ops should not refer to non-final variables if (tb.intermediateAndSourceExpressions() .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) .map(PsiReferenceExpression::resolve).select(PsiVariable.class).anyMatch(nonFinalVariables::contains)) { return null; } PsiStatement[] statements = tb.getStatements(); if (statements.length == 2) { PsiStatement breakStatement = statements[1]; if (loop instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(breakStatement, (PsiLoopStatement)loop) && exitPoints.size() == 1 && exitPoints.contains(breakStatement)) { return findMigrationForBreak(tb, nonFinalVariables, statements[0], replaceTrivialForEach); } } return null; }