extract simplify forEach inspection: IDEA-CR-24052

This commit is contained in:
Roman Ivanov
2017-09-01 17:26:19 +07:00
parent 0f2ac1a485
commit 029b97749b
47 changed files with 958 additions and 287 deletions
@@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable;
* @author Tagir Valeev
*/
abstract class BaseStreamApiMigration {
private final boolean myShouldWarn;
private boolean myShouldWarn;
private final String myReplacement;
protected BaseStreamApiMigration(boolean shouldWarn, String replacement) {
@@ -40,19 +40,23 @@ abstract class BaseStreamApiMigration {
return myReplacement;
}
abstract PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb);
abstract PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb);
public boolean isShouldWarn() {
return myShouldWarn;
}
static PsiElement replaceWithOperation(PsiLoopStatement loopStatement,
public void setShouldWarn(boolean shouldWarn) {
myShouldWarn = shouldWarn;
}
static PsiElement replaceWithOperation(PsiStatement loopStatement,
PsiVariable var,
String streamText,
PsiType expressionType,
OperationReductionMigration.ReductionOperation reductionOperation) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loopStatement.getProject());
restoreComments(loopStatement, loopStatement.getBody());
restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement);
InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement);
if (status != InitializerUsageStatus.UNKNOWN) {
PsiExpression initializer = var.getInitializer();
@@ -67,7 +71,7 @@ abstract class BaseStreamApiMigration {
loopStatement));
}
static PsiElement replaceInitializer(PsiLoopStatement loopStatement,
static PsiElement replaceInitializer(PsiStatement loopStatement,
PsiVariable var,
PsiExpression initializer,
String replacement,
@@ -90,11 +94,11 @@ abstract class BaseStreamApiMigration {
@Nullable
static PsiElement replaceWithFindExtremum(@NotNull PsiLoopStatement loopStatement,
static PsiElement replaceWithFindExtremum(@NotNull PsiStatement loopStatement,
@NotNull PsiVariable extremumHolder,
@NotNull String streamText,
@Nullable PsiVariable keyExtremum) {
restoreComments(loopStatement, loopStatement.getBody());
restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement);
if(keyExtremum != null) {
keyExtremum.delete();
}
@@ -102,14 +106,16 @@ abstract class BaseStreamApiMigration {
return replaceInitializer(loopStatement, extremumHolder, extremumHolder.getInitializer(), streamText, status);
}
static void restoreComments(PsiLoopStatement loopStatement, PsiStatement body) {
final PsiElement parent = loopStatement.getParent();
for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) {
parent.addBefore(comment, loopStatement);
static void restoreComments(PsiStatement statement, PsiElement body) {
if(statement instanceof PsiLoopStatement || statement instanceof PsiExpressionStatement) {
final PsiElement parent = statement.getParent();
for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) {
parent.addBefore(comment, statement);
}
}
}
static void removeLoop(@NotNull PsiLoopStatement statement) {
static void removeLoop(@NotNull PsiStatement statement) {
PsiElement parent = statement.getParent();
if (parent instanceof PsiLabeledStatement) {
parent.delete();
@@ -75,8 +75,8 @@ class CollectMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiLoopStatement loopStatement = tb.getMainLoop();
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiStatement loopStatement = tb.getStreamSourceStatement();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
CollectTerminal terminal = extractCollectTerminal(tb, null);
if (terminal == null) return null;
@@ -139,12 +139,12 @@ class CollectMigration extends BaseStreamApiMigration {
CollectTerminal terminal = StreamEx.of(extractors).map(extractor -> extractor.apply(tb, call)).nonNull().findFirst().orElse(null);
if (terminal != null) {
if (terminal.getStatus() == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null;
terminal = includePostStatements(terminal, tb.getMainLoop());
terminal = includePostStatements(terminal, tb.getStreamSourceStatement());
}
return terminal;
}
static CollectTerminal includePostStatements(CollectTerminal terminal, PsiLoopStatement loop) {
static CollectTerminal includePostStatements(CollectTerminal terminal, PsiStatement loop) {
List<BiFunction<CollectTerminal, PsiElement, CollectTerminal>> wrappers =
Arrays.asList(SortingTerminal::tryWrap, ToArrayTerminal::tryWrap, NewListTerminal::tryWrap);
PsiElement nextStatement = loop;
@@ -194,9 +194,9 @@ class CollectMigration extends BaseStreamApiMigration {
abstract static class CollectTerminal {
private final PsiLocalVariable myTargetVariable;
private final InitializerUsageStatus myStatus;
final PsiLoopStatement myLoop;
final PsiStatement myLoop;
protected CollectTerminal(PsiLocalVariable variable, PsiLoopStatement loop, InitializerUsageStatus status) {
protected CollectTerminal(PsiLocalVariable variable, PsiStatement loop, InitializerUsageStatus status) {
myTargetVariable = variable;
myLoop = loop;
myStatus = status;
@@ -235,7 +235,7 @@ class CollectMigration extends BaseStreamApiMigration {
AddingTerminal(@NotNull PsiLocalVariable target,
PsiVariable element,
PsiMethodCallExpression addCall,
PsiLoopStatement loop,
PsiStatement loop,
InitializerUsageStatus status) {
super(target, loop, hasLambdaCompatibleEmptyInitializer(target) ? status : ControlFlowUtils.InitializerUsageStatus.UNKNOWN);
myTargetType = target.getType();
@@ -288,8 +288,8 @@ class CollectMigration extends BaseStreamApiMigration {
PsiExpression count = tb.getCountExpression();
PsiLocalVariable variable = extractQualifierVariable(tb, call);
if (variable != null) {
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop());
AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getMainLoop(), status);
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement());
AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getStreamSourceStatement(), status);
if (count == null) return terminal;
// like "list.add(x); if(list.size() >= limit) break;"
if (!(count instanceof PsiMethodCallExpression)) return null;
@@ -346,7 +346,7 @@ class CollectMigration extends BaseStreamApiMigration {
AddingAllTerminal(PsiLocalVariable target,
PsiVariable element,
PsiMethodCallExpression addAllCall,
PsiLoopStatement loop,
PsiStatement loop,
InitializerUsageStatus status) {
super(target, element, null, loop, status);
myAddAllCall = addAllCall;
@@ -380,8 +380,8 @@ class CollectMigration extends BaseStreamApiMigration {
if (collectionReference == null || tb.dependsOn(collectionReference)) return null;
PsiLocalVariable target = tryCast(collectionReference.resolve(), PsiLocalVariable.class);
if (target == null || StreamEx.of(args).skip(1).anyMatch(arg -> VariableAccessUtils.variableIsUsed(target, arg))) return null;
InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getMainLoop());
return new AddingAllTerminal(target, tb.getVariable(), call, tb.getMainLoop(), status);
InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getStreamSourceStatement());
return new AddingAllTerminal(target, tb.getVariable(), call, tb.getStreamSourceStatement(), status);
}
}
@@ -447,7 +447,7 @@ class CollectMigration extends BaseStreamApiMigration {
PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
if (valueType == null) return null;
AddingTerminal adding = new AddingTerminal(valueType, body, tb.getVariable(), call);
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop());
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement());
return new GroupingTerminal(adding, variable, args[0], status);
}
}
@@ -464,7 +464,7 @@ class CollectMigration extends BaseStreamApiMigration {
ToMapTerminal(PsiMethodCallExpression call,
PsiVariable elementVariable,
PsiLocalVariable variable,
PsiLoopStatement loop,
PsiStatement loop,
InitializerUsageStatus status) {
super(variable, loop, status);
myMapUpdateCall = call;
@@ -517,8 +517,8 @@ class CollectMigration extends BaseStreamApiMigration {
}
PsiLocalVariable variable = extractQualifierVariable(tb, call);
if (!hasLambdaCompatibleEmptyInitializer(variable)) return null;
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop());
return new ToMapTerminal(call, tb.getVariable(), variable, tb.getMainLoop(), status);
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement());
return new ToMapTerminal(call, tb.getVariable(), variable, tb.getStreamSourceStatement(), status);
}
}
@@ -31,7 +31,7 @@ class CountMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiExpression expression = tb.getSingleExpression(PsiExpression.class);
if (expression == null) {
expression = tb.getCountExpression();
@@ -41,6 +41,6 @@ class CountMigration extends BaseStreamApiMigration {
PsiElement element = ((PsiReferenceExpression)operand).resolve();
if (!(element instanceof PsiLocalVariable)) return null;
PsiLocalVariable var = (PsiLocalVariable)element;
return replaceWithOperation(tb.getMainLoop(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION);
return replaceWithOperation(tb.getStreamSourceStatement(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION);
}
}
@@ -46,7 +46,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
ExtremumTerminal terminal = extract(tb, null);
if (terminal == null) return null;
return terminal.replace();
@@ -187,7 +187,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
static private boolean mayChangeBeforeLoop(@NotNull PsiVariable variable,
@NotNull TerminalBlock terminalBlock) {
ControlFlowUtils.InitializerUsageStatus status =
ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getMainLoop());
ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getStreamSourceStatement());
return status.equals(ControlFlowUtils.InitializerUsageStatus.UNKNOWN);
}
@@ -286,7 +286,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
if (method == null) return null;
String inFilterOperation = myMax ? ">=" : "<=";
PsiLoopStatement loop = myTerminalBlock.getMainLoop();
PsiStatement loop = myTerminalBlock.getStreamSourceStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject());
String extremumInitializer = myExtremumKeyInitializer.getText();
PsiExpression condition =
@@ -304,7 +304,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
comparator = comparatorName;
}
String stream = blockWithFilter.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)";
return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, myExtremumKey);
return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, myExtremumKey);
}
@Override
@@ -473,7 +473,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
terminalBlock = blockWithMap;
}
String inFilterOperation = myMax ? ">=" : "<=";
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject());
String extremumInitializer = myExtremumInitializer.getText();
Object nonFilterableInitialValue = getNonFilterableInitialValue(type, myMax);
@@ -573,7 +573,7 @@ class FindExtremumMigration extends BaseStreamApiMigration {
comparator = comparatorName;
}
String stream = myTerminalBlock.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)";
return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, null);
return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, null);
}
@Override
@@ -34,10 +34,10 @@ class FindFirstMigration extends BaseStreamApiMigration {
FindFirstMigration(boolean shouldWarn) {super(shouldWarn, "findFirst()");}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiStatement statement = tb.getSingleStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
PsiLoopStatement loopStatement = tb.getMainLoop();
PsiStatement loopStatement = tb.getStreamSourceStatement();
if (statement instanceof PsiReturnStatement) {
PsiReturnStatement returnStatement = (PsiReturnStatement)statement;
PsiExpression value = returnStatement.getReturnValue();
@@ -57,8 +57,8 @@ class ForEachMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiLoopStatement loopStatement = tb.getMainLoop();
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiStatement loopStatement = tb.getStreamSourceStatement();
restoreComments(loopStatement, body);
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
@@ -26,6 +26,7 @@ import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.siyeh.ig.callMatcher.CallMatcher;
@@ -37,6 +38,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil.isEffectivelyFinal;
@@ -53,12 +55,12 @@ public class JoiningMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
JoiningTerminal terminal = extractTerminal(tb, null);
if(terminal == null) return null;
TerminalBlock block = terminal.getTerminalBlock();
PsiLoopStatement loopStatement = block.getMainLoop();
PsiStatement loopStatement = block.getStreamSourceStatement();
String stream = terminal.generateStreamCode();
restoreComments(loopStatement, body);
PsiLocalVariable builder = terminal.getBuilder();
@@ -171,10 +173,22 @@ public class JoiningMigration extends BaseStreamApiMigration {
}
}
private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiLoopStatement loop) {
NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(loop, PsiMember.class, PsiLambdaExpression.class);
return ReferencesSearch.search(variable)
.forEach(reference -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound);
private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiStatement sourceStatement) {
NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(sourceStatement, PsiMember.class, PsiLambdaExpression.class);
Predicate<PsiReference> referenceBoundPredicate;
if (sourceStatement instanceof PsiLoopStatement) {
referenceBoundPredicate =
(reference) -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound;
}
else {
referenceBoundPredicate = (reference) -> {
PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(reference.getElement(), PsiLambdaExpression.class);
return PsiTreeUtil.getParentOfType(lambda, PsiMember.class, PsiLambdaExpression.class) == loopBound;
};
}
return ReferencesSearch.search(variable).forEach((Processor<PsiReference>)reference -> referenceBoundPredicate.test(reference)) &&
FinalUtils.canBeFinal(variable);
}
String generateTerminal() {
@@ -197,6 +211,11 @@ public class JoiningMigration extends BaseStreamApiMigration {
}
String generateIntermediate() {
if (TypeUtils.isJavaLangString(myLoopVariable.getType()) &&
myMainJoinParts.size() == 1 &&
myMainJoinParts.get(0) instanceof PsiReferenceExpression) {
return "";
}
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myLoopVariable.getProject());
String joinTransformation = getExpressionText(myMainJoinParts);
PsiExpression mapping = elementFactory.createExpressionFromText(joinTransformation, myLoopVariable);
@@ -664,12 +683,13 @@ public class JoiningMigration extends BaseStreamApiMigration {
if(declarations == null) return null;
PsiMethodCallExpression beforeLoopAppend = getCallBeforeStatement(firstAppendSuccessor, targetBuilder, APPEND, declarations);
if(!canBeMadeNonFinal(targetBuilder, terminalBlock.getMainLoop())) return null;
if (!canBeMadeNonFinal(targetBuilder, terminalBlock.getStreamSourceStatement())) return null;
List<PsiElement> refs = StreamEx.of(ReferencesSearch.search(targetBuilder).findAll())
.map(PsiReference::getElement)
.remove(e -> PsiTreeUtil.isAncestor(targetBuilder, e, false) ||
PsiTreeUtil.isAncestor(terminalBlock.getMainLoop(), e, false))
PsiTreeUtil.isAncestor(terminalBlock.getStreamSourceStatement(), e, false))
.toList();
allowedReferencePlaces.add(afterLoopAppend);
@@ -732,7 +752,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
if (mainJoinParts == null || mainJoinParts.isEmpty()) return null;
PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0));
if (targetBuilder == null) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PrefixSuffixContext context =
PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList()));
if (context == null) return null;
@@ -776,7 +796,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
List<PsiExpression> mainJoinParts = extractJoinParts(withoutCondition);
if (mainJoinParts == null) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PrefixSuffixContext context =
PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList()));
if (context == null) return null;
@@ -846,7 +866,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0));
if (targetBuilder == null) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PrefixSuffixContext context =
PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(boolVar),
new HashSet<>(emptyList()));
@@ -896,7 +916,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
List<PsiExpression> mainJoinParts = joinData.getMainJoinParts();
List<PsiExpression> delimiterJoinParts = joinData.getDelimiterJoinParts();
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PsiIfStatement ifStatement = tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(loop), PsiIfStatement.class);
if(ifStatement == null) return null;
@@ -1019,7 +1039,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
PsiLocalVariable targetBuilder = extractStringBuilder(mainStatements.get(0));
if (targetBuilder == null) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PrefixSuffixContext context =
PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(delimiterVar),
new HashSet<>(emptyList()));
@@ -1103,7 +1123,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0));
if (targetBuilder == null) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PrefixSuffixContext context =
PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList()));
if (context == null) return null;
@@ -1180,7 +1200,7 @@ public class JoiningMigration extends BaseStreamApiMigration {
JoinData joinData = JoinData.extractLeftDelimiter(joinParts);
List<PsiExpression> delimiterJoinParts = joinData.getDelimiterJoinParts();
if (delimiterJoinParts.isEmpty()) return null;
PsiLoopStatement loop = terminalBlock.getMainLoop();
PsiStatement loop = terminalBlock.getStreamSourceStatement();
PsiLocalVariable variable = tryCast(terminalBlock.getVariable(), PsiLocalVariable.class);
if (variable == null) return null;
PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0));
@@ -36,29 +36,29 @@ class MatchMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiLoopStatement loopStatement = tb.getMainLoop();
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiStatement sourceStatement = tb.getStreamSourceStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
if(tb.getSingleStatement() instanceof PsiReturnStatement) {
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
if (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE)) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(loopStatement);
PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(sourceStatement);
if (nextReturnStatement != null) {
PsiExpression returnValue = nextReturnStatement.getReturnValue();
if(returnValue == null) return null;
String methodName = foundResult ? "anyMatch" : "noneMatch";
String streamText = addTerminalOperation(methodName, loopStatement, tb);
restoreComments(loopStatement, body);
if (nextReturnStatement.getParent() == loopStatement.getParent()) {
String streamText = addTerminalOperation(methodName, sourceStatement, tb);
restoreComments(sourceStatement, body);
if (nextReturnStatement.getParent() == sourceStatement.getParent()) {
if(!ExpressionUtils.isLiteral(returnValue, !foundResult)) {
streamText+= (foundResult ? "||" : "&&") + ParenthesesUtils.getText(returnValue, ParenthesesUtils.AND_PRECEDENCE);
}
removeLoop(loopStatement);
removeLoop(sourceStatement);
return returnValue.replace(elementFactory.createExpressionFromText(streamText, nextReturnStatement));
}
PsiElement result = loopStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", loopStatement));
PsiElement result = sourceStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", sourceStatement));
if(!isReachable(nextReturnStatement)) {
nextReturnStatement.delete();
}
@@ -67,11 +67,12 @@ class MatchMigration extends BaseStreamApiMigration {
}
}
PsiStatement[] statements = tb.getStatements();
if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], loopStatement)))) {
if (!(statements.length == 1 || (sourceStatement instanceof PsiLoopStatement && statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1],
(PsiLoopStatement)sourceStatement)))) {
return null;
}
restoreComments(loopStatement, body);
String streamText = addTerminalOperation("anyMatch", loopStatement, tb);
restoreComments(sourceStatement, body);
String streamText = addTerminalOperation("anyMatch", sourceStatement, tb);
PsiStatement statement = statements[0];
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement);
if(assignment != null) {
@@ -85,7 +86,7 @@ class MatchMigration extends BaseStreamApiMigration {
// for(....) if(...) {flag = true; break;}
PsiVariable var = (PsiVariable)maybeVar;
PsiExpression initializer = var.getInitializer();
InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement);
InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, sourceStatement);
if (initializer != null && status != ControlFlowUtils.InitializerUsageStatus.UNKNOWN) {
String replacement;
if (ExpressionUtils.isLiteral(initializer, Boolean.FALSE) &&
@@ -99,13 +100,13 @@ class MatchMigration extends BaseStreamApiMigration {
else {
replacement = streamText + "?" + rValue.getText() + ":" + initializer.getText();
}
return replaceInitializer(loopStatement, var, initializer, replacement, status);
return replaceInitializer(sourceStatement, var, initializer, replacement, status);
}
}
}
}
String replacement = "if(" + streamText + "){" + statement.getText() + "}";
return loopStatement.replace(elementFactory.createStatementFromText(replacement, loopStatement));
return sourceStatement.replace(elementFactory.createStatementFromText(replacement, sourceStatement));
}
private static String addTerminalOperation(String methodName, @NotNull PsiElement contextElement, @NotNull TerminalBlock tb) {
@@ -21,30 +21,33 @@ import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.StreamSource;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLoopStatement;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Tagir Valeev
*/
class MigrateToStreamFix implements LocalQuickFix {
private BaseStreamApiMigration myMigration;
@Nullable private final String myCustomName;
protected MigrateToStreamFix(BaseStreamApiMigration migration) {
protected MigrateToStreamFix(BaseStreamApiMigration migration, @Nullable String customName) {
myMigration = migration;
myCustomName = customName;
}
@Nls
@NotNull
@Override
public String getName() {
return "Replace with "+myMigration.getReplacement();
return myCustomName!= null? myCustomName: "Replace with "+myMigration.getReplacement();
}
@SuppressWarnings("DialogTitleCapitalization")
@@ -63,11 +66,29 @@ class MigrateToStreamFix implements LocalQuickFix {
PsiStatement body = loopStatement.getBody();
if(body == null || source == null) return;
TerminalBlock tb = TerminalBlock.from(source, body);
PsiElement result = myMigration.migrate(project, body, tb);
if(result != null) {
tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp);
simplifyAndFormat(project, result);
}
migrate(project, body, tb);
} else if(element instanceof PsiExpressionStatement) {
PsiMethodCallExpression call = tryCast(((PsiExpressionStatement)element).getExpression(), PsiMethodCallExpression.class);
if(call == null) return;
PsiLambdaExpression lambda = SimplifyForEachInspection.extractLambdaFromForEach(call);
if (lambda == null) return;
PsiElement lambdaBody = lambda.getBody();
SimplifyForEachInspection.ExistingStreamSource
source = SimplifyForEachInspection.ExistingStreamSource.extractSource(call, lambda);
if(source == null) return;
TerminalBlock terminalBlock = SimplifyForEachInspection.extractTerminalBlock(lambdaBody, source);
if (terminalBlock == null) return;
migrate(project, lambdaBody, terminalBlock);
}
}
private void migrate(@NotNull Project project, PsiElement block, TerminalBlock tb) {
PsiElement result = myMigration.migrate(project, block, tb);
if(result != null) {
tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp);
simplifyAndFormat(project, result);
}
}
@@ -40,7 +40,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if (assignment == null) return null;
PsiVariable var = StreamApiMigrationInspection.extractAccumulator(assignment, myReductionOperation.getCompoundAssignmentOp());
@@ -71,7 +71,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration {
+ String.format(Locale.ENGLISH, ".reduce(%s, (%s, %s) -> %s %s %s)",
identity, leftOperand, rightOperand, leftOperand,
myReductionOperation.getOperation(), rightOperand);
return replaceWithOperation(tb.getMainLoop(), var, stream, type, myReductionOperation);
return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, myReductionOperation);
}
static class ReductionOperation {
@@ -0,0 +1,195 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.streamMigration;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
public class SimplifyForEachInspection extends BaseJavaBatchLocalInspectionTool {
private static final CallMatcher.Simple ITERABLE_FOREACH =
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_ITERABLE, "forEach").parameterCount(1);
private static final CallMatcher.Simple STREAM_FOREACH_ORDERED =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEachOrdered").parameterCount(1);
private static final CallMatcher STREAM_FOREACH =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1);
private static final CallMatcher FOREACH = CallMatcher.anyOf(
STREAM_FOREACH,
ITERABLE_FOREACH
);
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return "forEach call can be simplified";
}
@NotNull
@Override
public String getShortName() {
return "SimplifyForEach";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
PsiFile file = holder.getFile();
VirtualFile virtualFile = file.getVirtualFile();
if (!PsiUtil.isLanguageLevel8OrHigher(file) || virtualFile == null ||
!FileIndexFacade.getInstance(holder.getProject()).isInSourceContent(virtualFile)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
PsiLambdaExpression lambda = extractLambdaFromForEach(call);
if (lambda == null) return;
PsiElement lambdaBody = lambda.getBody();
ExistingStreamSource
source = ExistingStreamSource.extractSource(call, lambda);
if (source == null) return;
TerminalBlock terminalBlock = extractTerminalBlock(lambdaBody, source);
if (terminalBlock == null) return;
PsiStatement mainStatement = source.getMainStatement();
BaseStreamApiMigration migration =
StreamApiMigrationInspection.findMigration(mainStatement, lambdaBody, terminalBlock, holder, true, true);
boolean opCountChanged = terminalBlock.getOperationCount() > 1;
boolean lastOpChanged = !(migration instanceof ForEachMigration);
if (opCountChanged || lastOpChanged) {
String customMessage;
if (opCountChanged && !lastOpChanged) {
customMessage = "Extract intermediate operations";
if (STREAM_FOREACH_ORDERED.test(call)) {
migration = new ForEachMigration(migration.isShouldWarn(), "forEachOrdered");
}
}
else {
customMessage = null;
}
if (migration != null) {
migration.setShouldWarn(true);
}
StreamApiMigrationInspection
.offerMigration(mainStatement, terminalBlock, migration, m -> getRange(call).shiftRight(-call.getTextOffset()), customMessage, false, holder);
}
}
};
}
@NotNull
private static TextRange getRange(PsiMethodCallExpression call) {
PsiReferenceExpression methodExpression = call.getMethodExpression();
return new TextRange(methodExpression.getTextOffset(), call.getArgumentList().getTextOffset());
}
@Nullable
static TerminalBlock extractTerminalBlock(@Nullable PsiElement lambdaBody,
@NotNull ExistingStreamSource source) {
if (lambdaBody instanceof PsiCodeBlock) {
return TerminalBlock.from(source, (PsiCodeBlock)lambdaBody);
}
if (lambdaBody instanceof PsiExpression) {
return TerminalBlock.fromStatements(source, new LightExpressionStatement((PsiExpression)lambdaBody));
}
return null;
}
@Nullable
static PsiLambdaExpression extractLambdaFromForEach(PsiMethodCallExpression call) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null ||
!FOREACH.test(call) ||
!InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_COLLECTION)) return null;
PsiExpression arg = call.getArgumentList().getExpressions()[0];
return tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiLambdaExpression.class);
}
static class LightExpressionStatement extends LightElement implements PsiExpressionStatement {
@NotNull private final PsiExpression myExpression;
protected LightExpressionStatement(@NotNull PsiExpression expression) {
super(expression.getManager(), JavaLanguage.INSTANCE);
myExpression = expression;
}
@NotNull
@Override
public PsiExpression getExpression() {
return myExpression;
}
@Override
public String toString() {
return myExpression.getText() + ";";
}
}
static class ExistingStreamSource extends StreamApiMigrationInspection.StreamSource {
private final boolean myIsCollectionForEach;
protected ExistingStreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression, boolean isCollectionForEach) {
super(mainStatement, variable, expression);
myIsCollectionForEach = isCollectionForEach;
}
@Override
String createReplacement() {
return myExpression.getText() + (myIsCollectionForEach? ".stream()" : "");
}
@Nullable
static ExistingStreamSource extractSource(PsiMethodCallExpression call, PsiLambdaExpression lambda) {
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length != 1) return null;
PsiParameter parameter = parameters[0];
boolean isCollectionForEach = ITERABLE_FOREACH.test(call);
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return null;
PsiStatement parent = tryCast(call.getParent(), PsiExpressionStatement.class);
if (parent == null) return null;
return new ExistingStreamSource(parent, parameter, qualifier, isCollectionForEach);
}
}
}
@@ -25,6 +25,7 @@ import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.FileIndexFacade;
@@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
@@ -41,6 +43,8 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
@@ -52,6 +56,7 @@ import javax.swing.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static com.intellij.codeInspection.streamMigration.OperationReductionMigration.SUM_OPERATION;
import static com.intellij.util.ObjectUtils.tryCast;
@@ -60,8 +65,10 @@ import static com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus.UNKN
public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(StreamApiMigrationInspection.class);
public boolean REPLACE_TRIVIAL_FOREACH;
public boolean SUGGEST_FOREACH;
private static final String SHORT_NAME = "Convert2streamapi";
@Nullable
@Override
@@ -94,7 +101,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
@NotNull
@Override
public String getShortName() {
return "Convert2streamapi";
return SHORT_NAME;
}
@NotNull
@@ -315,7 +322,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
return variable != null &&
ExpressionUtils.isZero(variable.getInitializer()) &&
ControlFlowUtils.getInitializerUsageStatus(variable, tb.getMainLoop()) != UNKNOWN;
ControlFlowUtils.getInitializerUsageStatus(variable, tb.getStreamSourceStatement()) != UNKNOWN;
}
private static boolean isTrivial(TerminalBlock tb) {
@@ -437,203 +444,222 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return;
TerminalBlock tb = TerminalBlock.from(source, body);
BaseStreamApiMigration migration = findMigration(statement, body, tb);
if (migration != null && (myIsOnTheFly || migration.isShouldWarn())) {
MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration)};
if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream()
fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered")));
}
ProblemHighlightType highlightType =
migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION;
myHolder.registerProblem(statement, "Can be replaced with '" + migration.getReplacement() + "' call",
highlightType, getRange(migration.isShouldWarn(), statement).shiftRight(-statement.getTextOffset()),
fixes);
}
BaseStreamApiMigration migration = findMigration(statement, body, tb, myHolder, SUGGEST_FOREACH, REPLACE_TRIVIAL_FOREACH);
offerMigration(statement, tb, migration, (streamApiMigration) -> getRange(streamApiMigration.isShouldWarn(), statement, myIsOnTheFly)
.shiftRight(-statement.getTextOffset()), null, myIsOnTheFly, myHolder);
}
}
@Nullable
private BaseStreamApiMigration findMigration(PsiLoopStatement loop, PsiStatement body, TerminalBlock tb) {
final ControlFlow controlFlow;
try {
controlFlow = ControlFlowFactory.getInstance(myHolder.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();
static void offerMigration(PsiStatement statement,
TerminalBlock tb,
BaseStreamApiMigration migration,
Function<BaseStreamApiMigration, TextRange> rangeSupplier,
@Nullable String customMessage,
boolean isOnTheFly,
ProblemsHolder holder) {
if (migration != null && (isOnTheFly || migration.isShouldWarn())) {
if (isCountOperation(nonFinalVariables, tb)) {
return new CountMigration(true);
}
if (nonFinalVariables.size() == 0) {
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 = REPLACE_TRIVIAL_FOREACH ||
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 = StreamEx.of(exitPoints).allMatch(statement -> statement instanceof PsiContinueStatement &&
((PsiContinueStatement)statement).getLabelIdentifier() == null);
if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) {
boolean shouldWarn = SUGGEST_FOREACH &&
(REPLACE_TRIVIAL_FOREACH ||
tb.hasOperations() ||
ForEachMigration.tryExtractMapExpression(tb) != null ||
!isTrivial(tb));
return new ForEachMigration(shouldWarn, "forEach");
}
if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
return findMigrationForReturn(loop, tb);
}
// 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 (ControlFlowUtils.statementBreaksLoop(breakStatement, loop) &&
exitPoints.size() == 1 &&
exitPoints.contains(breakStatement)) {
return findMigrationForBreak(tb, nonFinalVariables, statements[0]);
}
MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration, customMessage)};
if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream()
fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"), customMessage));
}
ProblemHighlightType highlightType =
migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION;
String message = customMessage != null ? customMessage : "Can be replaced with '" + migration.getReplacement() + "' call";
holder.registerProblem(statement, message, highlightType, rangeSupplier.apply(migration), fixes);
}
}
@Nullable
static BaseStreamApiMigration findMigration(PsiStatement loop,
PsiElement body,
TerminalBlock tb,
ProblemsHolder holder,
boolean suggestForeach,
boolean replaceTrivialForEach) {
final ControlFlow controlFlow;
try {
controlFlow = ControlFlowFactory.getInstance(holder.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();
@Nullable
private BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb, List<PsiVariable> nonFinalVariables, PsiStatement statement) {
boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations();
if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) {
return new MatchMigration(shouldWarn, "anyMatch");
if (isCountOperation(nonFinalVariables, tb)) {
return new CountMigration(true);
}
if (nonFinalVariables.size() == 0) {
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 (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) {
return new FindFirstMigration(shouldWarn);
}
if (nonFinalVariables.size() == 1) {
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement);
if (assignment == null) return null;
PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class);
if (lValue == null) return null;
PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class);
if (var == null || !nonFinalVariables.contains(var)) return null;
PsiExpression rValue = assignment.getRExpression();
if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null;
if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null;
return new FindFirstMigration(shouldWarn);
}
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 = StreamEx.of(exitPoints).allMatch(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;
}
@Nullable
private BaseStreamApiMigration findMigrationForReturn(PsiLoopStatement statement, TerminalBlock tb) {
boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations();
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement);
if (nextReturnStatement != null &&
(ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
String methodName;
if (foundResult) {
methodName = "anyMatch";
}
else {
methodName = "noneMatch";
FilterOp lastFilter = tb.getLastOperation(FilterOp.class);
if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) {
methodName = "allMatch";
}
}
if (nextReturnStatement.getParent() == statement.getParent() ||
ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
return new MatchMigration(shouldWarn, methodName);
}
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);
}
if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) {
if (!REPLACE_TRIVIAL_FOREACH && !tb.hasOperations() ||
(tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) {
return null;
}
return new MatchMigration(shouldWarn, "anyMatch");
}
if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())
&& (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) {
return new FindFirstMigration(shouldWarn);
}
return null;
}
return null;
}
@NotNull
private TextRange getRange(boolean shouldWarn, PsiLoopStatement statement) {
boolean wholeStatement =
myIsOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(getShortName(), statement));
if (statement instanceof PsiForeachStatement) {
PsiJavaToken rParenth = ((PsiForeachStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
PsiExpression iteratedValue = ((PsiForeachStatement)statement).getIteratedValue();
LOG.assertTrue(iteratedValue != null);
return iteratedValue.getTextRange();
}
else if (statement instanceof PsiForStatement) {
PsiJavaToken rParenth = ((PsiForStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
PsiStatement initialization = ((PsiForStatement)statement).getInitialization();
LOG.assertTrue(initialization != null);
return initialization.getTextRange();
}
else if (statement instanceof PsiWhileStatement) {
PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
return statement.getFirstChild().getTextRange();
@Nullable
private static BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb,
List<PsiVariable> nonFinalVariables,
PsiStatement statement,
boolean replaceTrivialForEach) {
boolean shouldWarn = replaceTrivialForEach || tb.hasOperations();
if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) {
return new MatchMigration(shouldWarn, "anyMatch");
}
if (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) {
return new FindFirstMigration(shouldWarn);
}
if (nonFinalVariables.size() == 1) {
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement);
if (assignment == null) return null;
PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class);
if (lValue == null) return null;
PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class);
if (var == null || !nonFinalVariables.contains(var)) return null;
PsiExpression rValue = assignment.getRExpression();
if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null;
if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null;
return new FindFirstMigration(shouldWarn);
}
return null;
}
@Nullable
private static BaseStreamApiMigration findMigrationForReturn(PsiStatement statement, TerminalBlock tb, boolean replaceTrivialForEach) {
boolean shouldWarn = replaceTrivialForEach || tb.hasOperations();
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement);
if (nextReturnStatement != null &&
(ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
String methodName;
if (foundResult) {
methodName = "anyMatch";
}
else {
throw new IllegalStateException("Unexpected statement type: " + statement);
methodName = "noneMatch";
FilterOp lastFilter = tb.getLastOperation(FilterOp.class);
if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) {
methodName = "allMatch";
}
}
if (nextReturnStatement.getParent() == statement.getParent() ||
ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
return new MatchMigration(shouldWarn, methodName);
}
}
if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) {
if (!replaceTrivialForEach && !tb.hasOperations() ||
(tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) {
return null;
}
return new MatchMigration(shouldWarn, "anyMatch");
}
if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())
&& (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) {
return new FindFirstMigration(shouldWarn);
}
return null;
}
@NotNull
private static TextRange getRange(boolean shouldWarn, PsiStatement statement, boolean isOnTheFly) {
boolean wholeStatement =
isOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(SHORT_NAME, statement));
if (statement instanceof PsiForeachStatement) {
PsiJavaToken rParenth = ((PsiForeachStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
PsiExpression iteratedValue = ((PsiForeachStatement)statement).getIteratedValue();
LOG.assertTrue(iteratedValue != null);
return iteratedValue.getTextRange();
}
else if (statement instanceof PsiForStatement) {
PsiJavaToken rParenth = ((PsiForStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
PsiStatement initialization = ((PsiForStatement)statement).getInitialization();
LOG.assertTrue(initialization != null);
return initialization.getTextRange();
}
else if (statement instanceof PsiWhileStatement) {
PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth();
if (wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
return statement.getFirstChild().getTextRange();
}
else {
throw new IllegalStateException("Unexpected statement type: " + statement);
}
}
@@ -649,7 +675,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
PsiReferenceExpression arrayReference = tryCast(arrayAccess.getArrayExpression(), PsiReferenceExpression.class);
if (arrayReference == null) return null;
PsiLocalVariable arrayVariable = tryCast(arrayReference.resolve(), PsiLocalVariable.class);
if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()) == UNKNOWN) return null;
if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement()) == UNKNOWN) {
return null;
}
PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class);
if (initializer == null) return null;
PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class);
@@ -843,7 +871,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
boolean breaksMe(PsiBreakStatement statement) {
return statement.findExitedStatement() == mySource.getLoop();
return statement.findExitedStatement() == mySource.getMainStatement();
}
}
@@ -915,15 +943,15 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
abstract static class StreamSource extends Operation {
private final PsiLoopStatement myLoop;
private final PsiStatement myMainStatement;
protected StreamSource(PsiLoopStatement loop, PsiVariable variable, PsiExpression expression) {
protected StreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression) {
super(expression, variable);
myLoop = loop;
myMainStatement = mainStatement;
}
PsiLoopStatement getLoop() {
return myLoop;
PsiStatement getMainStatement() {
return myMainStatement;
}
@Contract("null -> null")
@@ -1086,7 +1114,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
final PsiExpression myBound;
final boolean myIncluding;
private CountingLoopSource(PsiLoopStatement loop,
private CountingLoopSource(PsiStatement loop,
PsiVariable counter,
PsiExpression initializer,
PsiExpression bound,
@@ -1109,11 +1137,11 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
CountingLoopSource withBound(PsiExpression bound) {
return new CountingLoopSource(getLoop(), getVariable(), getExpression(), bound, myIncluding);
return new CountingLoopSource(getMainStatement(), getVariable(), getExpression(), bound, myIncluding);
}
CountingLoopSource withInitializer(PsiExpression expression) {
return new CountingLoopSource(getLoop(), getVariable(), expression, myBound, myIncluding);
return new CountingLoopSource(getMainStatement(), getVariable(), expression, myBound, myIncluding);
}
@Override
@@ -1139,4 +1167,5 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return new CountingLoopSource(forStatement, loop.getCounter(), loop.getInitializer(), loop.getBound(), loop.isIncluding());
}
}
}
@@ -32,7 +32,7 @@ class SumMigration extends BaseStreamApiMigration {
SumMigration(boolean shouldWarn) {super(shouldWarn, "sum()");}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if (assignment == null) return null;
PsiVariable var = StreamApiMigrationInspection.extractSumAccumulator(assignment);
@@ -51,6 +51,6 @@ class SumMigration extends BaseStreamApiMigration {
"(" + type.getCanonicalText() + ")" + ParenthesesUtils.getText(addend, ParenthesesUtils.MULTIPLICATIVE_PRECEDENCE), addend);
}
String stream = tb.add(new MapOp(addend, tb.getVariable(), type)).generate()+".sum()";
return replaceWithOperation(tb.getMainLoop(), var, stream, type, SUM_OPERATION);
return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, SUM_OPERATION);
}
}
@@ -100,6 +100,10 @@ class TerminalBlock {
return null;
}
int getOperationCount() {
return myOperations.length;
}
/**
* @return PsiMethodCallExpression if this TerminalBlock contains single method call, null otherwise
*/
@@ -191,12 +195,14 @@ class TerminalBlock {
}
if(myStatements.length >= 1) {
PsiStatement first = myStatements[0];
if(PsiUtil.isLanguageLevel9OrHigher(first.getContainingFile()) && first instanceof PsiIfStatement) {
if(PsiUtil.isLanguageLevel9OrHigher(myVariable.getContainingFile()) && first instanceof PsiIfStatement) {
PsiIfStatement ifStatement = (PsiIfStatement)first;
PsiExpression condition = ifStatement.getCondition();
if(ifStatement.getElseBranch() == null && condition != null) {
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
if(ControlFlowUtils.statementBreaksLoop(thenStatement, getMainLoop())) {
PsiStatement sourceStatement = getStreamSourceStatement();
if( sourceStatement instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(thenStatement,
(PsiLoopStatement)sourceStatement)) {
TakeWhileOp op = new TakeWhileOp(condition, myVariable, true);
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
return new TerminalBlock(this, op, myVariable, leftOver);
@@ -252,7 +258,9 @@ class TerminalBlock {
statements = Arrays.copyOfRange(myStatements, 0, count);
tb = new TerminalBlock(myOperations, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter();
}
if (tb == null || !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), getMainLoop())) return this;
PsiStatement sourceStatement = getStreamSourceStatement();
if (tb == null || (sourceStatement instanceof PsiLoopStatement && !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(),
(PsiLoopStatement)sourceStatement))) return this;
FilterOp filter = tb.getLastOperation(FilterOp.class);
if (filter == null) return this;
PsiBinaryExpression binOp = tryCast(PsiUtil.skipParenthesizedExprDown(filter.getExpression()), PsiBinaryExpression.class);
@@ -323,7 +331,7 @@ class TerminalBlock {
PsiExpressionList argumentList = initializer.getArgumentList();
if (argumentList == null ||
argumentList.getExpressions().length != 0 ||
ControlFlowUtils.getInitializerUsageStatus(var, getMainLoop()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) {
ControlFlowUtils.getInitializerUsageStatus(var, getStreamSourceStatement()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) {
return null;
}
return var;
@@ -428,8 +436,11 @@ class TerminalBlock {
return StreamEx.ofReversed(myOperations);
}
PsiLoopStatement getMainLoop() {
return ((StreamSource)myOperations[0]).getLoop();
/**
* @return generally {@link PsiLoopStatement} - main loop
*/
PsiStatement getStreamSourceStatement() {
return ((StreamSource)myOperations[0]).getMainStatement();
}
/**
@@ -494,7 +505,17 @@ class TerminalBlock {
@NotNull
static TerminalBlock from(StreamSource source, @NotNull PsiStatement body) {
return new TerminalBlock(null, source, source.myVariable, body).extractOperations().tryPeelLimit(false).tryExtractDistinct();
return fromStatements(source, body);
}
@NotNull
static TerminalBlock fromStatements(StreamSource source, @NotNull PsiStatement... statements) {
return new TerminalBlock(null, source, source.myVariable, statements).extractOperations().tryPeelLimit(false).tryExtractDistinct();
}
@NotNull
static TerminalBlock from(StreamSource source, @NotNull PsiCodeBlock block) {
return fromStatements(source, block.getStatements());
}
boolean dependsOn(PsiExpression qualifier) {
@@ -35,7 +35,7 @@ public class ToArrayMigration extends BaseStreamApiMigration {
}
@Override
PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) {
PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) {
PsiLocalVariable arrayVariable = StreamApiMigrationInspection.extractArray(tb);
if(arrayVariable == null) return null;
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
@@ -50,7 +50,7 @@ public class ToArrayMigration extends BaseStreamApiMigration {
if(loop == null) return null;
PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class);
if(arrayType == null) return null;
InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop());
InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement());
if(status == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null;
PsiType componentType = arrayType.getComponentType();
String supplier;
@@ -61,6 +61,6 @@ public class ToArrayMigration extends BaseStreamApiMigration {
}
MapOp mapping = new MapOp(rValue, tb.getVariable(), assignment.getType());
String replacementText = loop.withBound(dimension).createReplacement() + mapping.createReplacement() + ".toArray(" + supplier + ")";
return replaceInitializer(tb.getMainLoop(), arrayVariable, initializer, replacementText, status);
return replaceInitializer(tb.getStreamSourceStatement(), arrayVariable, initializer, replacementText, status);
}
}
@@ -0,0 +1,10 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
other.stream().filter(s -> s.length() > 2).forEach(System.out::println);
}
}
@@ -0,0 +1,12 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
// c1
//c2
other.stream().filter(s -> s.length() > 2).forEach(System.out::println);
}
}
@@ -0,0 +1,10 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test(List<String> strs) {
List<String> other = strs.stream().filter(s -> s.length() > 2).collect(Collectors.toList());
}
}
@@ -0,0 +1,12 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> strs;
List<String> other = new ArrayList<>();
strs = other.stream().collect(Collectors.toList());
}
}
@@ -0,0 +1,12 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> strs;
List<String> other = new ArrayList<>();
strs = other.stream().filter(s -> s.length() > 2).collect(Collectors.toList());
}
}
@@ -0,0 +1,10 @@
// "Replace with toArray" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
String[] arr = other.stream().filter(s -> s.length() > 2).sorted(String.CASE_INSENSITIVE_ORDER).toArray();
}
}
@@ -0,0 +1,12 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> strs;
List<String> other = new ArrayList<>();
strs = other.stream().filter(s -> s.length() > 2).sorted(String::compareTo).collect(Collectors.toList());
}
}
@@ -0,0 +1,10 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
other.stream().filter(s -> s.length() > 2).forEachOrdered(System.out::println);
}
}
@@ -0,0 +1,10 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test(List<String> other) {
List<String> strs = other.stream().collect(Collectors.toList());
}
}
@@ -0,0 +1,12 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> strs;
List<String> other = new ArrayList<>();
strs = other.stream().collect(Collectors.toList());
}
}
@@ -0,0 +1,10 @@
// "Replace with count()" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
int count = (int) strs.stream().count();
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
String sb = strs.stream().collect(Collectors.joining());
}
}
@@ -0,0 +1,9 @@
// "Replace with toArray" "true"
import java.util.*;
public class Main {
private void test(List<String> strs) {
String[] arr = strs.toArray();
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
import java.util.stream.Collectors;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
HashMap<Integer, String> map = other.stream().collect(Collectors.toMap(String::length, s -> s, (a, b) -> a, HashMap::new));
}
}
@@ -0,0 +1,12 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> {
if(s.length() > 2) System.out.println(s);
});
}
}
@@ -0,0 +1,12 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> { // c1
if(s.length() > 2) System.out.println(s); //c2
});
}
}
@@ -0,0 +1,14 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test(List<String> strs) {
List<String> other = new ArrayList<>();
strs.stream().forEach<caret>(s -> {
if(s.length() > 2) {
other.add(s);
}
});
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> {strs.add(s)});
}
}
@@ -0,0 +1,13 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> {
if(s.length() > 2) strs.add(s);
});
}
}
@@ -0,0 +1,17 @@
// "Replace with toArray" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> {
if(s.length() > 2) {
strs.add(s);
}
});
String[] arr = strs.toArray();
Arrays.sort(arr, String.CASE_INSENSITIVE_ORDER);
}
}
@@ -0,0 +1,16 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> {
if(s.length() > 2) {
strs.add(s);
}
});
strs.sort(String::compareTo);
}
}
@@ -0,0 +1,10 @@
// "Extract intermediate operations" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
other.stream().forEachOrdered<caret>(s -> {if(s.length() > 2) System.out.println(s);});
}
}
@@ -0,0 +1,10 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test(List<String> other) {
List<String> strs = new ArrayList<>();
other.stream().forEach<caret>(s -> {strs.add(s)});
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
List<String> other = new ArrayList<>();
other.stream().forEach<caret>(s -> strs.add(s));
}
}
@@ -0,0 +1,11 @@
// "Replace with count()" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
int count = 0;
strs.stream().forEach<caret>(x -> count++)
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> strs = new ArrayList<>();
StringBuilder sb = new StringBuilder();
strs.stream().forEach<caret>(x -> sb.append(x));
}
}
@@ -0,0 +1,11 @@
// "Replace with toArray" "true"
import java.util.*;
public class Main {
private void test(List<String> strs) {
List<String> tmp = new ArrayList<>();
strs.stream().forEach<caret>(x -> tmp.add(x));
String[] arr = tmp.toArray();
}
}
@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.*;
public class Main {
private void test() {
List<String> other = new ArrayList<>();
HashMap<Integer, String> map = new HashMap<Integer, String>();
other.stream().forEach<caret>(s -> map.putIfAbsent(s.length(), s));
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.streamMigration.SimplifyForEachInspection;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
public class SimplifyForEachInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new SimplifyForEachInspection()};
}
@Override
protected LanguageLevel getDefaultLanguageLevel() {
return LanguageLevel.JDK_1_8;
}
public void test() { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach";
}
}
@@ -765,7 +765,7 @@ public class ControlFlowUtils {
*/
private static boolean isVariableReferencedBeforeLoopEntry(final ControlFlow flow,
final int start,
final PsiLoopStatement loop,
final PsiStatement loop,
final PsiVariable variable) {
final int loopStart = flow.getStartOffset(loop);
final int loopEnd = flow.getEndOffset(loop);
@@ -812,7 +812,7 @@ public class ControlFlowUtils {
* @return initializer usage status for variable
*/
@NotNull
public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiLoopStatement loop) {
public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiStatement loop) {
if(!(var instanceof PsiLocalVariable) || var.getInitializer() == null) return UNKNOWN;
if(isDeclarationJustBefore(var, loop)) return DECLARED_JUST_BEFORE;
// Check that variable is declared in the same method or the same lambda expression
@@ -0,0 +1,9 @@
<html>
<body>
<p>
This inspection reports forEach which can be replaced with more concise method or intermediate steps can be extracted.
</p>
Stream API is not available under Java 1.7 or earlier JVMs
<small>New in 2017.3</small>
</body>
</html>
+3
View File
@@ -805,6 +805,9 @@
<localInspection groupPath="Java,Java language level migration aids" language="JAVA" shortName="Convert2streamapi" displayName="Loop can be collapsed with Stream API"
groupKey="group.names.language.level.specific.issues.and.migration.aids8" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="INFORMATION"
implementationClass="com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection"/>
<localInspection groupPath="Java,Java language level migration aids" language="JAVA" shortName="SimplifyForEach" displayName="forEach call can be simplified"
groupKey="group.names.language.level.specific.issues.and.migration.aids8" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.streamMigration.SimplifyForEachInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="StreamToLoop" displayName="Stream API call chain can be replaced with loop"
groupKey="group.names.code.style.issues" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="INFORMATION"
implementationClass="com.intellij.codeInspection.streamToLoop.StreamToLoopInspection"/>