IDEA-187214 Stream to loop inspection: support Java 10 toUnmodifiableList/Set/Map

This commit is contained in:
Tagir Valeev
2018-02-27 10:54:26 +07:00
parent 2bc3c8b81b
commit 0da4fd17f3
4 changed files with 128 additions and 4 deletions

View File

@@ -50,7 +50,7 @@ public class StreamToLoopInspection extends AbstractBaseJavaLocalInspectionTool
// To quickly filter out most of the non-interesting method calls
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");
"average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet", "toImmutableList", "toImmutableSet");
@SuppressWarnings("PublicField")
public boolean SUPPORT_UNKNOWN_SOURCES = false;

View File

@@ -100,6 +100,12 @@ abstract class TerminalOperation extends Operation {
if(name.equals("toSet") && args.length == 0) {
return ToCollectionTerminalOperation.toSet(resultType);
}
if(name.equals("toImmutableList") && args.length == 0) {
return new WrappedCollectionTerminalOperation(ToCollectionTerminalOperation.toList(resultType), "unmodifiableList");
}
if(name.equals("toImmutableSet") && args.length == 0) {
return new WrappedCollectionTerminalOperation(ToCollectionTerminalOperation.toSet(resultType), "unmodifiableSet");
}
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);
@@ -174,13 +180,20 @@ abstract class TerminalOperation extends Operation {
case "toList":
if (collectorArgs.length != 0) return null;
return ToCollectionTerminalOperation.toList(resultType);
case "toUnmodifiableList":
if (collectorArgs.length != 0) return null;
return new WrappedCollectionTerminalOperation(ToCollectionTerminalOperation.toList(resultType), "unmodifiableList");
case "toSet":
if (collectorArgs.length != 0) return null;
return ToCollectionTerminalOperation.toSet(resultType);
case "toUnmodifiableSet":
if (collectorArgs.length != 0) return null;
return new WrappedCollectionTerminalOperation(ToCollectionTerminalOperation.toSet(resultType), "unmodifiableSet");
case "toCollection":
if (collectorArgs.length != 1) return null;
fn = FunctionHelper.create(collectorArgs[0], 0);
return fn == null ? null : new ToCollectionTerminalOperation(resultType, fn, null);
case "toUnmodifiableMap":
case "toMap": {
if (collectorArgs.length < 2 || collectorArgs.length > 4) return null;
FunctionHelper key = FunctionHelper.create(collectorArgs[0], 1);
@@ -191,7 +204,10 @@ abstract class TerminalOperation extends Operation {
? FunctionHelper.create(collectorArgs[3], 0)
: FunctionHelper.newObjectSupplier(resultType, CommonClassNames.JAVA_UTIL_HASH_MAP);
if(supplier == null) return null;
return new ToMapTerminalOperation(key, value, merger, supplier, resultType);
CollectorBasedTerminalOperation operation = new ToMapTerminalOperation(key, value, merger, supplier, resultType);
return collectorName.equals("toUnmodifiableMap")
? new WrappedCollectionTerminalOperation(operation, "unmodifiableMap")
: operation;
}
case "reducing":
switch (collectorArgs.length) {
@@ -605,10 +621,14 @@ abstract class TerminalOperation extends Operation {
@Override
String initAccumulator(StreamVariable inVar, StreamToLoopReplacementContext context) {
return initAccumulator(inVar, context, true);
}
String initAccumulator(StreamVariable inVar, StreamToLoopReplacementContext context, boolean canBeFinal) {
transform(context, inVar.getName());
PsiType resultType = correctReturnType(myType);
return context
.declareResult(myAccNameSupplier.apply(context), resultType, myMostAbstractAllowedType, getSupplier(), ResultKind.FINAL);
return context.declareResult(myAccNameSupplier.apply(context), resultType, myMostAbstractAllowedType, getSupplier(),
canBeFinal ? ResultKind.FINAL : ResultKind.UNKNOWN);
}
@Override
@@ -711,6 +731,33 @@ abstract class TerminalOperation extends Operation {
}
}
static class WrappedCollectionTerminalOperation extends TerminalOperation {
private final CollectorBasedTerminalOperation myDelegate;
private final String myWrapper;
public WrappedCollectionTerminalOperation(CollectorBasedTerminalOperation delegate, String wrapper) {
myDelegate = delegate;
myWrapper = wrapper;
}
@Override
public void registerReusedElements(Consumer<PsiElement> consumer) {
myDelegate.registerReusedElements(consumer);
}
@Override
public void preprocessVariables(StreamToLoopReplacementContext context, StreamVariable inVar, StreamVariable outVar) {
myDelegate.preprocessVariables(context, inVar, outVar);
}
@Override
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
String acc = myDelegate.initAccumulator(inVar, context, false);
context.setFinisher(CommonClassNames.JAVA_UTIL_COLLECTIONS + "." + myWrapper + "(" + acc + ")");
return myDelegate.getAccumulatorUpdater(inVar, acc);
}
}
static class ToCollectionTerminalOperation extends CollectorBasedTerminalOperation {
private final boolean myList;

View File

@@ -0,0 +1,47 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
package java.util.stream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
class Collectors {
public static native <T> Collector<T, ?, List<T>> toUnmodifiableList();
public static native <T> Collector<T, ?, Set<T>> toUnmodifiableSet();
public static native <T, K, U> Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
}
class MyFile {
public static List<String> testList(String[] args) {
List<String> result = new ArrayList<>();
for (String arg : args) {
if (arg != null) {
result.add(arg);
}
}
List<String> list = Collections.unmodifiableList(result);
return list;
}
public static Set<String> testSet(String[] args) {
Set<String> set = new HashSet<>();
for (String arg : args) {
if (arg != null) {
set.add(arg);
}
}
return Collections.unmodifiableSet(set);
}
public static Map<String, String> testMap(String[] args) {
Map<String, String> map = new HashMap<>();
for (String arg : args) {
if (arg != null) {
if (map.put(arg.trim(), arg) != null) {
throw new IllegalStateException("Duplicate key");
}
}
}
return Collections.unmodifiableMap(map);
}
}

View File

@@ -0,0 +1,30 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
package java.util.stream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
class Collectors {
public static native <T> Collector<T, ?, List<T>> toUnmodifiableList();
public static native <T> Collector<T, ?, Set<T>> toUnmodifiableSet();
public static native <T, K, U> Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
}
class MyFile {
public static List<String> testList(String[] args) {
List<String> list = Arrays.stream(args).filter(Objects::nonNull)
.collect(Collectors.toUnmodifiab<caret>leList());
return list;
}
public static Set<String> testSet(String[] args) {
return Arrays.stream(args).filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableSet());
}
public static Map<String, String> testMap(String[] args) {
return Arrays.stream(args).filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableMap(String::trim, Function.identity()));
}
}