StreamToLoopInspection: support some simple StreamEx scenarios; support Java 9 takeWhile/dropWhile

This commit is contained in:
Tagir Valeev
2017-02-01 13:32:15 +03:00
parent a380a9dab1
commit 80256c9bfb
7 changed files with 152 additions and 20 deletions

View File

@@ -552,7 +552,7 @@ abstract class FunctionHelper {
}
}
private static class InlinedFunctionHelper extends FunctionHelper {
static class InlinedFunctionHelper extends FunctionHelper {
private final int myArgCount;
private final String myTemplate;
private PsiExpression myExpression;

View File

@@ -71,6 +71,17 @@ abstract class Operation {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
return fn == null ? null : new FilterOperation(fn);
}
if(name.equals("takeWhile") && args.length == 1) {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
return fn == null ? null : new TakeWhileOperation(fn);
}
if(name.equals("dropWhile") && args.length == 1) {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
return fn == null ? null : new DropWhileOperation(fn);
}
if (name.equals("nonNull") && args.length == 0) { // StreamEx
return new FilterOperation(new FunctionHelper.InlinedFunctionHelper(PsiType.BOOLEAN, 1, "{0} != null"));
}
if(name.equals("sorted") && !(inType instanceof PsiPrimitiveType)) {
return new SortedOperation(args.length == 1 ? args[0] : null);
}
@@ -138,6 +149,33 @@ abstract class Operation {
}
}
static class TakeWhileOperation extends LambdaIntermediateOperation {
public TakeWhileOperation(FunctionHelper fn) {
super(fn);
}
@Override
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
return "if(" + BoolUtils.getNegatedExpressionText(myFn.getExpression()) + ") {\n" +
context.getBreakStatement() + "}\n" + code;
}
}
static class DropWhileOperation extends LambdaIntermediateOperation {
public DropWhileOperation(FunctionHelper fn) {
super(fn);
}
@Override
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
String dropping = context.declare("dropping", "boolean", "true");
return "if(" + dropping + ") {\n" +
"if(" + myFn.getText() + ") {\ncontinue;\n}\n" +
dropping + "=false;\n" +
"}\n" + code;
}
}
static class PeekOperation extends LambdaIntermediateOperation {
public PeekOperation(FunctionHelper fn) {
super(fn);

View File

@@ -19,6 +19,7 @@ import com.intellij.codeInspection.streamToLoop.StreamToLoopInspection.StreamToL
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.StreamApiUtil;
@@ -65,30 +66,36 @@ abstract class SourceOperation extends Operation {
String className = aClass.getQualifiedName();
if(className == null) return null;
if ((name.equals("range") || name.equals("rangeClosed")) && args.length == 2 && method.getModifierList().hasExplicitModifier(
PsiModifier.STATIC) && (className.equals(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM) ||
className.equals(CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM))) {
PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
return new RangeSource(args[0], args[1], name.equals("rangeClosed"));
}
if (name.equals("of") && method.getModifierList().hasExplicitModifier(
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
if(args.length == 1) {
PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
if (method.getParameterList().getParametersCount() != 1) return null;
if (args.length == 1) {
PsiType type = args[0].getType();
if(type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
if(StreamApiUtil.getStreamElementType(callType).isAssignableFrom(componentType)) {
return new ForEachSource(args[0]);
}
PsiType componentType = null;
if (type instanceof PsiArrayType) {
componentType = ((PsiArrayType)type).getComponentType();
}
else if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE)) {
componentType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false);
}
PsiType elementType = StreamApiUtil.getStreamElementType(callType);
if (componentType != null && elementType.isAssignableFrom(componentType)) {
return new ForEachSource(args[0]);
}
if (type == null || !elementType.isAssignableFrom(type)) return null;
}
return new ExplicitSource(call);
}
if (name.equals("generate") && args.length == 1 && method.getModifierList().hasExplicitModifier(
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
FunctionHelper fn = FunctionHelper.create(args[0], 0);
return fn == null ? null : new GenerateSource(fn, null);
}
if (name.equals("iterate") && args.length == 2 && method.getModifierList().hasExplicitModifier(
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
FunctionHelper fn = FunctionHelper.create(args[1], 1);
return fn == null ? null : new IterateSource(args[0], fn);
}
@@ -155,7 +162,7 @@ abstract class SourceOperation extends Operation {
@Override
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
myCall = (PsiMethodCallExpression)replaceVarReference(myCall, oldName, newName, context);
myCall = replaceVarReference(myCall, oldName, newName, context);
}
@Override
@@ -313,7 +320,7 @@ abstract class SourceOperation extends Operation {
@Override
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
myCall = (PsiMethodCallExpression)replaceVarReference(myCall, oldName, newName, context);
myCall = replaceVarReference(myCall, oldName, newName, context);
}
@Override

View File

@@ -36,6 +36,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.RedundantCastUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.IntStreamEx;
import one.util.streamex.StreamEx;
@@ -56,9 +57,9 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(StreamToLoopInspection.class);
// To quickly filter out most of the non-interesting method calls
private static final Set<String> SUPPORTED_TERMINALS = StreamEx.of("count", "sum", "summaryStatistics", "reduce", "collect",
"findFirst", "findAny", "anyMatch", "allMatch", "noneMatch",
"toArray", "average", "forEach", "forEachOrdered", "min", "max").toSet();
private static final Set<String> SUPPORTED_TERMINALS = ContainerUtil.set(
"count", "sum", "summaryStatistics", "reduce", "collect", "findFirst", "findAny", "anyMatch", "allMatch", "noneMatch", "toArray",
"average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet");
public boolean SUPPORT_UNKNOWN_SOURCES = false;

View File

@@ -94,6 +94,12 @@ abstract class TerminalOperation extends Operation {
PsiType optionalElementType = OptionalUtil.getOptionalElementType(resultType);
return optionalElementType == null ? null : new FindTerminalOperation(optionalElementType.getCanonicalText());
}
if(name.equals("toList") && args.length == 0) {
return ToCollectionTerminalOperation.toList(resultType);
}
if(name.equals("toSet") && args.length == 0) {
return ToCollectionTerminalOperation.toSet(resultType);
}
if((name.equals("anyMatch") || name.equals("allMatch") || name.equals("noneMatch")) && args.length == 1) {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
return fn == null ? null : new MatchTerminalOperation(fn, name);
@@ -170,8 +176,7 @@ abstract class TerminalOperation extends Operation {
return ToCollectionTerminalOperation.toList(resultType);
case "toSet":
if (collectorArgs.length != 0) return null;
return new ToCollectionTerminalOperation(resultType,
FunctionHelper.newObjectSupplier(resultType, CommonClassNames.JAVA_UTIL_HASH_SET), "set");
return ToCollectionTerminalOperation.toSet(resultType);
case "toCollection":
if (collectorArgs.length != 1) return null;
fn = FunctionHelper.create(collectorArgs[0], 0);
@@ -719,7 +724,14 @@ abstract class TerminalOperation extends Operation {
@NotNull
private static ToCollectionTerminalOperation toList(@NotNull PsiType resultType) {
return new ToCollectionTerminalOperation(resultType, FunctionHelper.newObjectSupplier(resultType, CommonClassNames.JAVA_UTIL_ARRAY_LIST), "list");
return new ToCollectionTerminalOperation(resultType,
FunctionHelper.newObjectSupplier(resultType, CommonClassNames.JAVA_UTIL_ARRAY_LIST), "list");
}
@NotNull
private static ToCollectionTerminalOperation toSet(@NotNull PsiType resultType) {
return new ToCollectionTerminalOperation(resultType,
FunctionHelper.newObjectSupplier(resultType, CommonClassNames.JAVA_UTIL_HASH_SET), "set");
}
}

View File

@@ -0,0 +1,45 @@
// "Replace Stream API chain with loop" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
// Mock Java 9 methods with inheritor
interface MyStream<T> extends Stream<T> {
<R> MyStream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
MyStream<T> takeWhile(Predicate<? super T> predicate);
MyStream<T> dropWhile(Predicate<? super T> predicate);
static <T> MyStream<T> of(List<T> list) {
return null;
}
}
public static void test(List<String> data) {
List<String> xyz = new ArrayList<>();
boolean dropping = true;
OUTER:
for (String s : data) {
for (String s1 : Arrays.asList(s, s + s)) {
if (s1.isEmpty()) {
break OUTER;
}
if (dropping) {
if (s1.length() < 3) {
continue;
}
dropping = false;
}
xyz.add(s1);
}
}
System.out.println(xyz);
}
}

View File

@@ -0,0 +1,29 @@
// "Replace Stream API chain with loop" "true"
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
// Mock Java 9 methods with inheritor
interface MyStream<T> extends Stream<T> {
<R> MyStream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
MyStream<T> takeWhile(Predicate<? super T> predicate);
MyStream<T> dropWhile(Predicate<? super T> predicate);
static <T> MyStream<T> of(List<T> list) {
return null;
}
}
public static void test(List<String> data) {
List<String> xyz = MyStream.of(data)
.flatMap(s -> Stream.of(s, s + s)).takeWhile(s -> !s.isEmpty())
.dropWhile(s -> s.length() < 3).col<caret>lect(Collectors.toList());
System.out.println(xyz);
}
}