StreamToLoopInspection: support Iterable.forEach()

This commit is contained in:
Tagir Valeev
2017-01-31 16:29:03 +03:00
parent a47088fa4f
commit c15f721206
4 changed files with 108 additions and 23 deletions
@@ -56,7 +56,7 @@ abstract class SourceOperation extends Operation {
static SourceOperation createSource(PsiMethodCallExpression call, boolean supportUnknownSources) {
PsiExpression[] args = call.getArgumentList().getExpressions();
PsiType callType = call.getType();
if(callType == null) return null;
if(callType == null || PsiType.VOID.equals(callType)) return null;
PsiMethod method = call.resolveMethod();
if(method == null) return null;
String name = method.getName();
@@ -84,25 +84,25 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
PsiMethod method = call.resolveMethod();
if(method == null) return;
PsiClass aClass = method.getContainingClass();
if(!InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) return;
PsiMethodCallExpression currentCall = call;
while(true) {
Operation op = createOperationFromCall(StreamVariable.STUB, currentCall, SUPPORT_UNKNOWN_SOURCES);
if(op == null) return;
if(op instanceof SourceOperation) {
TextRange range;
if(isOnTheFly && InspectionProjectProfileManager.isInformationLevel(getShortName(), call)) {
range = new TextRange(0, call.getTextLength());
} else {
range = nameElement.getTextRange().shiftRight(-call.getTextOffset());
}
holder.registerProblem(call, range, "Replace stream API chain with loop", new ReplaceStreamWithLoopFix());
return;
if (InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
if (extractOperations(StreamVariable.STUB, call, SUPPORT_UNKNOWN_SOURCES) != null) {
register(call, nameElement, "Replace Stream API chain with loop");
}
PsiExpression qualifier = currentCall.getMethodExpression().getQualifierExpression();
if(!(qualifier instanceof PsiMethodCallExpression)) return;
currentCall = (PsiMethodCallExpression)qualifier;
}
else if (extractIterableForEach(call) != null) {
register(call, nameElement, "Replace 'forEach' call with loop");
}
}
private void register(PsiMethodCallExpression call, PsiElement nameElement, String message) {
TextRange range;
if (isOnTheFly && InspectionProjectProfileManager.isInformationLevel(getShortName(), call)) {
range = new TextRange(0, call.getTextLength());
}
else {
range = nameElement.getTextRange().shiftRight(-call.getTextOffset());
}
holder.registerProblem(call, range, message, new ReplaceStreamWithLoopFix(message));
}
};
}
@@ -169,11 +169,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
Operation op = Operation.createIntermediate(name, args, outVar, elementType, supportUnknownSources);
if (op != null) return op;
PsiElement parent = call.getParent();
boolean isVoid = parent instanceof PsiExpressionStatement ||
(parent instanceof PsiLambdaExpression &&
PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)parent)));
op = TerminalOperation.createTerminal(name, args, elementType, callType, isVoid);
op = TerminalOperation.createTerminal(name, args, elementType, callType, isVoidContext(call.getParent()));
if (op != null) return op;
}
return null;
@@ -181,6 +177,39 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
return SourceOperation.createSource(call, supportUnknownSources);
}
private static boolean isVoidContext(PsiElement element) {
return element instanceof PsiExpressionStatement ||
(element instanceof PsiLambdaExpression &&
PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)element)));
}
@Nullable
static List<OperationRecord> extractIterableForEach(PsiMethodCallExpression terminalCall) {
if (MethodCallUtils.isCallToMethod(terminalCall, CommonClassNames.JAVA_LANG_ITERABLE, PsiType.VOID, "forEach", new PsiType[1])
&& isVoidContext(terminalCall.getParent())) {
PsiExpression qualifier = terminalCall.getMethodExpression().getQualifierExpression();
if (qualifier == null) return null;
// Do not visit this path if some class implements both Iterable and Stream
PsiType type = qualifier.getType();
if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) return null;
PsiExpression[] args = terminalCall.getArgumentList().getExpressions();
if (args.length != 1) return null;
FunctionHelper fn = FunctionHelper.create(args[0], 1);
if (fn == null) return null;
PsiType elementType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false);
if(elementType == null) return null;
TerminalOperation terminal = new TerminalOperation.ForEachTerminalOperation(fn);
SourceOperation source = new SourceOperation.ForEachSource(qualifier);
OperationRecord terminalRecord = new OperationRecord();
OperationRecord sourceRecord = new OperationRecord();
terminalRecord.myOperation = terminal;
sourceRecord.myOperation = source;
sourceRecord.myOutVar = terminalRecord.myInVar = new StreamVariable(elementType.getCanonicalText());
sourceRecord.myInVar = terminalRecord.myOutVar = StreamVariable.STUB;
return Arrays.asList(sourceRecord, terminalRecord);
}
return null;
}
@Nullable
static List<OperationRecord> extractOperations(StreamVariable outVar,
@@ -234,6 +263,19 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
static class ReplaceStreamWithLoopFix implements LocalQuickFix {
private String myMessage;
public ReplaceStreamWithLoopFix(String message) {
myMessage = message;
}
@Nls
@NotNull
@Override
public String getName() {
return myMessage;
}
@Nls
@NotNull
@Override
@@ -253,6 +295,9 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
PsiType resultType = terminalCall.getType();
if (resultType == null) return;
List<OperationRecord> operations = extractOperations(StreamVariable.STUB, terminalCall, true);
if (operations == null) {
operations = extractIterableForEach(terminalCall);
}
TerminalOperation terminal = getTerminal(operations);
if (terminal == null) return;
allOperations(operations).forEach(or -> or.myOperation.suggestNames(or.myInVar, or.myOutVar));
@@ -0,0 +1,22 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
public class Main {
public void testCast(Object obj, List<Object> list) {
for (Number n : ((Iterable<Number>) obj)) {
list.add(n);
}
}
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b");
for (String s : list) {
System.out.println(s);
}
}
}
@@ -0,0 +1,18 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
public class Main {
public void testCast(Object obj, List<Object> list) {
((Iterable<Number>)obj).forE<caret>ach(n -> list.add(n));
}
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b");
list.forEach(System.out::println);
}
}