StreamToLoopInspection: allow custom sources when option is set

This commit is contained in:
Tagir Valeev
2017-01-24 15:15:44 +07:00
parent 28f8e7760b
commit 1820f2db99
10 changed files with 181 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -57,7 +57,7 @@ abstract class Operation {
@Nullable
static Operation createIntermediate(@NotNull String name, @NotNull PsiExpression[] args,
@NotNull StreamVariable outVar, @NotNull PsiType inType) {
@NotNull StreamVariable outVar, @NotNull PsiType inType, boolean supportUnknownSources) {
if(name.equals("distinct") && args.length == 0) {
return new DistinctOperation();
}
@@ -83,7 +83,7 @@ abstract class Operation {
}
if ((name.equals("flatMap") || name.equals("flatMapToInt") || name.equals("flatMapToLong") || name.equals("flatMapToDouble")) &&
args.length == 1) {
return FlatMapOperation.from(outVar, args[0], inType);
return FlatMapOperation.from(outVar, args[0], inType, supportUnknownSources);
}
if ((name.equals("map") ||
name.equals("mapToInt") ||
@@ -162,7 +162,7 @@ abstract class Operation {
@Override
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
return outVar.getDeclaration() + " = " + myFn.getText() + ";\n" + code;
return outVar.getDeclaration(myFn.getText()) + code;
}
@Override
@@ -177,7 +177,7 @@ abstract class Operation {
StreamVariable outVar,
String code,
StreamToLoopReplacementContext context) {
return outVar.getDeclaration() + " = " + inVar + ";\n" + code;
return outVar.getDeclaration(inVar.getName()) + code;
}
@Override
@@ -256,7 +256,7 @@ abstract class Operation {
}
@Nullable
public static FlatMapOperation from(StreamVariable outVar, PsiExpression arg, PsiType inType) {
public static FlatMapOperation from(StreamVariable outVar, PsiExpression arg, PsiType inType, boolean supportUnknownSources) {
FunctionHelper fn = FunctionHelper.create(arg, 1);
if(fn == null) return null;
String varName = fn.tryLightTransform(inType);
@@ -280,7 +280,8 @@ abstract class Operation {
}
if(!(body instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression terminalCall = (PsiMethodCallExpression)body;
List<StreamToLoopInspection.OperationRecord> records = StreamToLoopInspection.extractOperations(outVar, terminalCall);
List<StreamToLoopInspection.OperationRecord> records = StreamToLoopInspection.extractOperations(outVar, terminalCall,
supportUnknownSources);
if(records == null || StreamToLoopInspection.getTerminal(records) != null) return null;
return new FlatMapOperation(varName, fn, records, condition, inverted);
}

View File

@@ -53,7 +53,7 @@ abstract class SourceOperation extends Operation {
abstract String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context);
@Nullable
static SourceOperation createSource(PsiMethodCallExpression call) {
static SourceOperation createSource(PsiMethodCallExpression call, boolean supportUnknownSources) {
PsiExpression[] args = call.getArgumentList().getExpressions();
PsiType callType = call.getType();
if(callType == null) return null;
@@ -100,6 +100,9 @@ abstract class SourceOperation extends Operation {
CommonClassNames.JAVA_UTIL_ARRAYS.equals(className)) {
return new ForEachSource(args[0]);
}
if (supportUnknownSources) {
return new StreamIteratorSource(call);
}
return null;
}
@@ -224,7 +227,7 @@ abstract class SourceOperation extends Operation {
}
return context.getLoopLabel() +
loop+"{\n" +
outVar.getDeclaration() + "=" + myFn.getText() + ";\n" + code +
outVar.getDeclaration(myFn.getText()) + code +
"}\n";
}
}
@@ -300,4 +303,56 @@ abstract class SourceOperation extends Operation {
code + "}\n";
}
}
private static class StreamIteratorSource extends SourceOperation {
private PsiMethodCallExpression myCall;
public StreamIteratorSource(PsiMethodCallExpression call) {
myCall = call;
}
@Override
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
myCall = (PsiMethodCallExpression)replaceVarReference(myCall, oldName, newName, context);
}
@Override
public void registerReusedElements(Consumer<PsiElement> consumer) {
consumer.accept(myCall);
}
@Override
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
String name = myCall.getMethodExpression().getReferenceName();
if (name != null) {
String unpluralized = StringUtil.unpluralize(name);
if (unpluralized != null && !unpluralized.equals(name)) {
outVar.addOtherNameCandidate(unpluralized);
}
}
}
static String getIteratorType(String type) {
switch(type) {
case "int":
return "java.util.PrimitiveIterator.OfInt";
case "long":
return "java.util.PrimitiveIterator.OfLong";
case "double":
return "java.util.PrimitiveIterator.OfDouble";
default:
return CommonClassNames.JAVA_UTIL_ITERATOR+"<"+type+">";
}
}
@Override
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
String iterator = context.registerVarName(Arrays.asList("it", "iter", "iterator"));
String declaration = getIteratorType(outVar.getType()) + " " + iterator + "=" + myCall.getText() + ".iterator()";
String condition = iterator + ".hasNext()";
return "for(" + declaration + ";" + condition + ";) {\n" +
outVar.getDeclaration(iterator + ".next()") +
code + "}\n";
}
}
}

View File

@@ -19,6 +19,7 @@ import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.lang.java.lexer.JavaLexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -43,6 +44,7 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import static com.intellij.codeInspection.streamToLoop.Operation.FlatMapOperation;
@@ -58,6 +60,14 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
"findFirst", "findAny", "anyMatch", "allMatch", "noneMatch",
"toArray", "average", "forEach", "forEachOrdered", "min", "max").toSet();
public boolean SUPPORT_UNKNOWN_SOURCES = false;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel("Iterate unknown Stream sources via Stream.iterator()", this, "SUPPORT_UNKNOWN_SOURCES");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
@@ -77,7 +87,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
if(!InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) return;
PsiMethodCallExpression currentCall = call;
while(true) {
Operation op = createOperationFromCall(StreamVariable.STUB, currentCall);
Operation op = createOperationFromCall(StreamVariable.STUB, currentCall, SUPPORT_UNKNOWN_SOURCES);
if(op == null) return;
if(op instanceof SourceOperation) {
TextRange range;
@@ -137,7 +147,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
@Nullable
static Operation createOperationFromCall(StreamVariable outVar, PsiMethodCallExpression call) {
static Operation createOperationFromCall(StreamVariable outVar, PsiMethodCallExpression call, boolean supportUnknownSources) {
PsiMethod method = call.resolveMethod();
if(method == null) return null;
PsiClass aClass = method.getContainingClass();
@@ -157,7 +167,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
// Raw type in any stream step is not supported
return null;
}
Operation op = Operation.createIntermediate(name, args, outVar, elementType);
Operation op = Operation.createIntermediate(name, args, outVar, elementType, supportUnknownSources);
if (op != null) return op;
PsiElement parent = call.getParent();
boolean isVoid = parent instanceof PsiExpressionStatement ||
@@ -166,19 +176,22 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
op = TerminalOperation.createTerminal(name, args, elementType, callType, isVoid);
if (op != null) return op;
}
return null;
}
return SourceOperation.createSource(call);
return SourceOperation.createSource(call, supportUnknownSources);
}
@Nullable
static List<OperationRecord> extractOperations(StreamVariable outVar, PsiMethodCallExpression terminalCall) {
static List<OperationRecord> extractOperations(StreamVariable outVar,
PsiMethodCallExpression terminalCall,
boolean supportUnknownSources) {
List<OperationRecord> operations = new ArrayList<>();
PsiMethodCallExpression currentCall = terminalCall;
StreamVariable lastVar = outVar;
Operation next = null;
while(true) {
Operation op = createOperationFromCall(lastVar, currentCall);
Operation op = createOperationFromCall(lastVar, currentCall, supportUnknownSources);
if(op == null) return null;
if(next != null) {
Operation combined = op.combineWithNext(next);
@@ -239,7 +252,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
if (terminalCall == null) return;
PsiType resultType = terminalCall.getType();
if (resultType == null) return;
List<OperationRecord> operations = extractOperations(StreamVariable.STUB, terminalCall);
List<OperationRecord> operations = extractOperations(StreamVariable.STUB, terminalCall, true);
TerminalOperation terminal = getTerminal(operations);
if (terminal == null) return;
allOperations(operations).forEach(or -> or.myOperation.suggestNames(or.myInVar, or.myOutVar));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -116,6 +116,10 @@ class StreamVariable {
return getType() + " " + getName();
}
String getDeclaration(String initializer) {
return getType() + " " + getName() + "=" + initializer + ";\n";
}
@Override
public String toString() {
if (myName == null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -963,7 +963,7 @@ abstract class TerminalOperation extends Operation {
@Override
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
createVariable(context, inVar.getName());
return myVariable.getDeclaration() + "=" + myMapper.getText() + ";\n" + myDownstream.generate(myVariable, context);
return myVariable.getDeclaration(myMapper.getText()) + myDownstream.generate(myVariable, context);
}
private void createVariable(StreamToLoopReplacementContext context, String item) {
@@ -982,8 +982,7 @@ abstract class TerminalOperation extends Operation {
@Override
public String getAccumulatorUpdater(StreamVariable inVar, String acc) {
return myVariable.getDeclaration() + "=" + myMapper.getText() + ";\n" +
myDownstreamCollector.getAccumulatorUpdater(myVariable, acc);
return myVariable.getDeclaration(myMapper.getText()) + myDownstreamCollector.getAccumulatorUpdater(myVariable, acc);
}
}

View File

@@ -0,0 +1,24 @@
// "Replace Stream API chain with loop" "true"
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
Stream<String> names() {
return Stream.of("foo", "bar");
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (Iterator<String> it = new Test().names().iterator(); it.hasNext(); ) {
String name = it.next();
String n = name.trim();
if (!n.isEmpty()) {
list.add(n);
}
}
}
}

View File

@@ -0,0 +1,25 @@
// "Replace Stream API chain with loop" "true"
import java.util.PrimitiveIterator;
import java.util.Random;
import java.util.stream.IntStream;
public class Test {
static void test() {
for (int n = 1; n < 100; n++) {
if (n > 20) {
Integer integer = n;
for (PrimitiveIterator.OfDouble it = new Random(integer).doubles(integer).iterator(); it.hasNext(); ) {
double v = it.next();
if (v < 0.01) {
System.out.println(v);
}
}
}
}
}
public static void main(String[] args) {
test();
}
}

View File

@@ -0,0 +1,16 @@
// "Replace Stream API chain with loop" "true"
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
Stream<String> names() {
return Stream.of("foo", "bar");
}
public static void main(String[] args) {
List<String> list = new Test().names().map(String::trim).filter(n -> !n.isEmpty())
.<caret>collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,19 @@
// "Replace Stream API chain with loop" "true"
import java.util.Random;
import java.util.stream.IntStream;
public class Test {
static void test() {
IntStream.range(1, 100)
.filter(n -> n > 20)
.boxed()
.flatMapToDouble(n -> new Random(n).doubles(n))
.filter(n -> n < 0.01)
.fo<caret>rEach(System.out::println);
}
public static void main(String[] args) {
test();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -24,9 +24,9 @@ public class StreamToLoopInspectionTest extends LightQuickFixParameterizedTestCa
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new StreamToLoopInspection()
};
StreamToLoopInspection inspection = new StreamToLoopInspection();
inspection.SUPPORT_UNKNOWN_SOURCES = true;
return new LocalInspectionTool[]{inspection};
}
public void test() throws Exception { doAllTests(); }