diff --git a/java/java-impl/src/com/intellij/codeInspection/streamToLoop/StreamToLoopInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamToLoop/StreamToLoopInspection.java index 95ad4ccab841..39a4417e1c8f 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamToLoop/StreamToLoopInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamToLoop/StreamToLoopInspection.java @@ -475,9 +475,17 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool { } public String declareResult(String desiredName, PsiType type, String initializer, @NotNull ResultKind kind) { + return declareResult(desiredName, type, null, initializer, kind); + } + + public String declareResult(String desiredName, + PsiType type, + String mostAbstractAllowedType, + String initializer, + @NotNull ResultKind kind) { if (kind != ResultKind.UNKNOWN && myStreamExpression.getParent() instanceof PsiVariable) { PsiVariable var = (PsiVariable)myStreamExpression.getParent(); - if (EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(var.getType(), type) && + if (isCompatibleType(var, type, mostAbstractAllowedType) && var.getParent() instanceof PsiDeclarationStatement && (kind == ResultKind.FINAL || canUseAsNonFinal(var))) { PsiDeclarationStatement declaration = (PsiDeclarationStatement)var.getParent(); if(declaration.getDeclaredElements().length == 1) { @@ -506,6 +514,14 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool { return name; } + private static boolean isCompatibleType(@NotNull PsiVariable var, @NotNull PsiType type, @Nullable String mostAbstractAllowedType) { + if (EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(var.getType(), type)) return true; + if (mostAbstractAllowedType == null) return false; + PsiType[] superTypes = type.getSuperTypes(); + return Arrays.stream(superTypes).anyMatch(superType -> InheritanceUtil.isInheritor(superType, mostAbstractAllowedType) && + isCompatibleType(var, superType, mostAbstractAllowedType)); + } + @Contract("null -> false") private static boolean canUseAsNonFinal(PsiVariable var) { if (!(var instanceof PsiLocalVariable)) return false; diff --git a/java/java-impl/src/com/intellij/codeInspection/streamToLoop/TerminalOperation.java b/java/java-impl/src/com/intellij/codeInspection/streamToLoop/TerminalOperation.java index cef3c1e986a1..ac56d74fc773 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamToLoop/TerminalOperation.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamToLoop/TerminalOperation.java @@ -591,10 +591,14 @@ abstract class TerminalOperation extends Operation { final PsiType myType; final Function myAccNameSupplier; final FunctionHelper mySupplier; + final String myMostAbstractAllowedType; - CollectorBasedTerminalOperation(PsiType type, Function accNameSupplier, + CollectorBasedTerminalOperation(PsiType type, + String mostAbstractAllowedType, + Function accNameSupplier, FunctionHelper accSupplier) { myType = type; + myMostAbstractAllowedType = mostAbstractAllowedType; myAccNameSupplier = accNameSupplier; mySupplier = accSupplier; } @@ -603,7 +607,8 @@ abstract class TerminalOperation extends Operation { String initAccumulator(StreamVariable inVar, StreamToLoopReplacementContext context) { transform(context, inVar.getName()); PsiType resultType = correctReturnType(myType); - return context.declareResult(myAccNameSupplier.apply(context), resultType, getSupplier(), ResultKind.FINAL); + return context + .declareResult(myAccNameSupplier.apply(context), resultType, myMostAbstractAllowedType, getSupplier(), ResultKind.FINAL); } @Override @@ -710,7 +715,8 @@ abstract class TerminalOperation extends Operation { private final boolean myList; public ToCollectionTerminalOperation(PsiType resultType, FunctionHelper fn, String desiredName) { - super(resultType, context -> fn.suggestFinalOutputNames(context, desiredName, "collection").get(0), fn); + super(resultType, CommonClassNames.JAVA_UTIL_COLLECTION, + context -> fn.suggestFinalOutputNames(context, desiredName, "collection").get(0), fn); myList = InheritanceUtil.isInheritor(resultType, CommonClassNames.JAVA_UTIL_LIST); } @@ -809,7 +815,7 @@ abstract class TerminalOperation extends Operation { PsiExpression merger, FunctionHelper supplier, PsiType resultType) { - super(resultType, context -> "map", supplier); + super(resultType, CommonClassNames.JAVA_UTIL_MAP, context -> "map", supplier); myKeyExtractor = keyExtractor; myValueExtractor = valueExtractor; myMerger = merger; @@ -878,7 +884,7 @@ abstract class TerminalOperation extends Operation { private String myKeyVar; public GroupByTerminalOperation(FunctionHelper keyExtractor, FunctionHelper supplier, PsiType resultType, CollectorOperation collector) { - super(resultType, context -> "map", supplier); + super(resultType, CommonClassNames.JAVA_UTIL_MAP, context -> "map", supplier); myKeyExtractor = keyExtractor; myCollector = collector; } @@ -947,7 +953,7 @@ abstract class TerminalOperation extends Operation { PsiType resultType = context.createType(myResultType); resultType = correctTypeParameters(resultType, CommonClassNames.JAVA_UTIL_MAP, Collections.singletonMap("V", myCollector::correctReturnType)); - String map = context.declareResult("map", resultType, "new java.util.HashMap<>()", ResultKind.FINAL); + String map = context.declareResult("map", resultType, CommonClassNames.JAVA_UTIL_MAP, "new java.util.HashMap<>()", ResultKind.FINAL); myPredicate.transform(context, inVar.getName()); myCollector.transform(context, inVar.getName()); context.addBeforeStep(map + ".put(false, " + myCollector.getSupplier() + ");"); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/afterInExactVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/afterInExactVariable.java new file mode 100644 index 000000000000..d10812002af1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/afterInExactVariable.java @@ -0,0 +1,111 @@ +// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true" + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class InExactVariable { + public void testMap() { + HashMap map = new HashMap<>(); + for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) { + String of = String.valueOf(integer2); + map.putIfAbsent(of.trim(), of); + } + Object map1 = map; + HashMap map2 = new HashMap<>(); + for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) { + String valueOf = String.valueOf(integer1); + map2.putIfAbsent(valueOf.trim(), valueOf); + } + Map map3 = new HashMap<>(); + for (Integer integer : Arrays.asList(1, 2, 3, 4)) { + String s = String.valueOf(integer); + map3.putIfAbsent(s.trim(), s); + } + } + + public void testList() { + List result1 = new ArrayList<>(); + for (Integer integer5 : Arrays.asList(1, 2, 3, 4)) { + String valueOf1 = String.valueOf(integer5); + result1.add(valueOf1); + } + Object list1 = result1; + List result = new ArrayList<>(); + for (Integer integer4 : Arrays.asList(1, 2, 3, 4)) { + String s1 = String.valueOf(integer4); + result.add(s1); + } + Iterable list2 = result; + Collection list3 = new ArrayList<>(); + for (Integer integer3 : Arrays.asList(1, 2, 3, 4)) { + String value = String.valueOf(integer3); + list3.add(value); + } + List list4 = new ArrayList<>(); + for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) { + String of = String.valueOf(integer2); + list4.add(of); + } + Collection list5 = new ArrayList<>(); + for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) { + String valueOf = String.valueOf(integer1); + list5.add(valueOf); + } + List list = new ArrayList<>(); + for (Integer integer : Arrays.asList(1, 2, 3, 4)) { + String s = String.valueOf(integer); + list.add(s); + } + Collection list6 = list; + } + + public void testPartition() { + Map> map = new HashMap<>(); + map.put(false, new ArrayList<>()); + map.put(true, new ArrayList<>()); + for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) { + String s = String.valueOf(integer1); + map.get(s.length() > 1).add(s); + } + Object map1 = map; + Map> map2 = new HashMap<>(); + map2.put(false, new ArrayList<>()); + map2.put(true, new ArrayList<>()); + for (Integer integer : Arrays.asList(1, 2, 3, 4)) { + String x = String.valueOf(integer); + map2.get(x.length() > 1).add(x); + } + } + + public void testGroupingBy() { + TreeMap> result = new TreeMap<>(); + for (Integer integer4 : Arrays.asList(1, 2, 3, 4)) { + String s1 = String.valueOf(integer4); + result.computeIfAbsent(s1.length(), k2 -> new HashSet<>()).add(s1); + } + Object map1 = result; + TreeMap> map2 = new TreeMap<>(); + for (Integer integer3 : Arrays.asList(1, 2, 3, 4)) { + String value = String.valueOf(integer3); + map2.computeIfAbsent(value.length(), key1 -> new HashSet<>()).add(value); + } + NavigableMap> map3 = new TreeMap<>(); + for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) { + String of = String.valueOf(integer2); + map3.computeIfAbsent(of.length(), k1 -> new HashSet<>()).add(of); + } + SortedMap> map4 = new TreeMap<>(); + for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) { + String valueOf = String.valueOf(integer1); + map4.computeIfAbsent(valueOf.length(), key -> new HashSet<>()).add(valueOf); + } + TreeMap> map = new TreeMap<>(); + for (Integer integer : Arrays.asList(1, 2, 3, 4)) { + String s = String.valueOf(integer); + map.computeIfAbsent(s.length(), k -> new HashSet<>()).add(s); + } + Cloneable map5 = map; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/beforeInExactVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/beforeInExactVariable.java new file mode 100644 index 000000000000..97e0a96d6f19 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamToLoop/beforeInExactVariable.java @@ -0,0 +1,46 @@ +// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true" + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class InExactVariable { + public void testMap() { + Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf) + .collect(Collectors.toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new)); + HashMap map2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors + .toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new)); + Map map3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors + .toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new)); + } + + public void testList() { + Object list1 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + Iterable list2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + Collection list3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + List list4 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + Collection list5 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + Collection list6 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList()); + } + + public void testPartition() { + Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf) + .collect(Collectors.partitioningBy(x -> x.length() > 1)); + Map> map2 = Stream.of(1, 2, 3, 4).map(String::valueOf) + .collect(Collectors.partitioningBy((String x) -> x.length() > 1)); + } + + public void testGroupingBy() { + Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf) + .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toSet())); + TreeMap> map2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors + .groupingBy(String::length, TreeMap::new, Collectors.toSet())); + NavigableMap> map3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors + .groupingBy(String::length, TreeMap::new, Collectors.toSet())); + SortedMap> map4 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors + .groupingBy(String::length, TreeMap::new, Collectors.toSet())); + Cloneable map5 = Stream.of(1, 2, 3, 4).map(String::valueOf) + .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toSet())); + } +}