diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java index 0b137baf7981..e50668d03df5 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java @@ -106,7 +106,8 @@ class CollectMigration extends BaseStreamApiMigration { return initializerClass != null && varClass != null && CommonClassNames.JAVA_UTIL_HASH_MAP.equals(initializerClass.getQualifiedName()) && - CommonClassNames.JAVA_UTIL_MAP.equals(varClass.getQualifiedName()); + CommonClassNames.JAVA_UTIL_MAP.equals(varClass.getQualifiedName()) && + !ConstructionUtils.isCustomizedEmptyCollectionInitializer(initializer); } @Nullable @@ -315,13 +316,14 @@ class CollectMigration extends BaseStreamApiMigration { PsiClassType rawVarType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : null; if (rawType != null && rawVarType != null && rawType.equalsToText(CommonClassNames.JAVA_UTIL_ARRAY_LIST) && - (rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_LIST) || rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION))) { + (rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_LIST) || rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION)) && + !ConstructionUtils.isCustomizedEmptyCollectionInitializer(initializer)) { collector = "toList()"; } else if (rawType != null && rawVarType != null && rawType.equalsToText(CommonClassNames.JAVA_UTIL_HASH_SET) && - (rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_SET) || - rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION))) { + (rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_SET) || rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION)) && + !ConstructionUtils.isCustomizedEmptyCollectionInitializer(initializer)) { collector = "toSet()"; } else { @@ -331,7 +333,7 @@ class CollectMigration extends BaseStreamApiMigration { PsiExpressionList argumentList = ((PsiNewExpression)copy).getArgumentList(); if (argumentList != null) { PsiExpression arg = ArrayUtil.getFirstElement(argumentList.getExpressions()); - if (arg != null) { + if (arg != null && !(arg.getType() instanceof PsiPrimitiveType)) { arg.delete(); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCollectCustomConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCollectCustomConstructor.java new file mode 100644 index 000000000000..ac0b5bcf3f96 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCollectCustomConstructor.java @@ -0,0 +1,52 @@ +// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true" + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class Test { + void testList(List input) { + List result = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new ArrayList<>(10))); + System.out.println(result); + + ArrayList result2 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new ArrayList<>(20))); + System.out.println(result2); + + // Non-empty + ArrayList result3 = new ArrayList<>(input); + input.stream().filter(s -> !s.isEmpty()).forEach(result3::add); + System.out.println(result3); + } + + void testSet(List input) { + Set result = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new HashSet<>(10))); + System.out.println(result); + + Collection result2 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new LinkedHashSet<>(20, 0.8f))); + System.out.println(result2); + + // Non-empty + AbstractSet result3 = new HashSet<>(input); + input.stream().filter(s -> !s.isEmpty()).forEach(result3::add); + System.out.println(result3); + + Collection result4 = input.stream().filter(s -> !s.isEmpty()).map(TimeUnit::valueOf).collect(Collectors.toCollection(() -> EnumSet.noneOf(TimeUnit.class))); + System.out.println(result4); + } + + void testMap(List input) { + Map result = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toMap(String::length, s -> s, (a, b) -> b, () -> new HashMap<>(10))); + System.out.println(result); + + Map result2 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toMap(String::length, s -> s, (a, b) -> b, () -> new HashMap<>(10, 0.8f))); + System.out.println(result2); + + EnumMap result3 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toMap(TimeUnit::valueOf, s -> s, (a, b) -> b, () -> new EnumMap<>(TimeUnit.class))); + System.out.println(result3); + + // Non-empty + EnumMap result4 = new EnumMap<>(result3); + input.stream().filter(s -> !s.isEmpty()).forEach(s -> result4.put(TimeUnit.valueOf(s), s)); + System.out.println(result4); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCollectCustomConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCollectCustomConstructor.java new file mode 100644 index 000000000000..503747e9d353 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCollectCustomConstructor.java @@ -0,0 +1,103 @@ +// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true" + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class Test { + void testList(List input) { + List result = new ArrayList<>(10); + for (String s : input) { + if (!s.isEmpty()) { + result.add(s); + } + } + System.out.println(result); + + ArrayList result2 = new ArrayList<>(20); + for (String s : input) { + if (!s.isEmpty()) { + result2.add(s); + } + } + System.out.println(result2); + + // Non-empty + ArrayList result3 = new ArrayList<>(input); + for (String s : input) { + if (!s.isEmpty()) { + result3.add(s); + } + } + System.out.println(result3); + } + + void testSet(List input) { + Set result = new HashSet<>(10); + for (String s : input) { + if (!s.isEmpty()) { + result.add(s); + } + } + System.out.println(result); + + Collection result2 = new LinkedHashSet<>(20, 0.8f); + for (String s : input) { + if (!s.isEmpty()) { + result2.add(s); + } + } + System.out.println(result2); + + // Non-empty + AbstractSet result3 = new HashSet<>(input); + for (String s : input) { + if (!s.isEmpty()) { + result3.add(s); + } + } + System.out.println(result3); + + Collection result4 = EnumSet.noneOf(TimeUnit.class); + for (String s : input) { + if (!s.isEmpty()) { + result4.add(TimeUnit.valueOf(s)); + } + } + System.out.println(result4); + } + + void testMap(List input) { + Map result = new HashMap<>(10); + for (String s : input) { + if (!s.isEmpty()) { + result.put(s.length(), s); + } + } + System.out.println(result); + + Map result2 = new HashMap<>(10, 0.8f); + for (String s : input) { + if (!s.isEmpty()) { + result2.put(s.length(), s); + } + } + System.out.println(result2); + + EnumMap result3 = new EnumMap<>(TimeUnit.class); + for (String s : input) { + if (!s.isEmpty()) { + result3.put(TimeUnit.valueOf(s), s); + } + } + System.out.println(result3); + + // Non-empty + EnumMap result4 = new EnumMap<>(result3); + for (String s : input) { + if (!s.isEmpty()) { + result4.put(TimeUnit.valueOf(s), s); + } + } + System.out.println(result4); + } +} diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ConstructionUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ConstructionUtils.java index 1c6d3d20034f..a1e6ac5a74d6 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ConstructionUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ConstructionUtils.java @@ -17,13 +17,24 @@ package com.siyeh.ig.psiutils; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.callMatcher.CallMatcher; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + /** * @author Tagir Valeev */ public class ConstructionUtils { + private static final Set GUAVA_UTILITY_CLASSES = + ContainerUtil.set("com.google.common.collect.Maps", "com.google.common.collect.Lists", "com.google.common.collect.Sets"); + private static final CallMatcher ENUM_SET_NONE_OF = + CallMatcher.staticCall("java.util.EnumSet", "noneOf").parameterCount(1); + /** * Checks that given expression initializes empty StringBuilder or StringBuffer (either with explicit default capacity or not) * @@ -79,10 +90,11 @@ public class ConstructionUtils { expression = PsiUtil.skipParenthesizedExprDown(expression); if (expression instanceof PsiNewExpression) { PsiExpressionList argumentList = ((PsiNewExpression)expression).getArgumentList(); - if (argumentList == null || argumentList.getExpressions().length != 0) return false; - PsiType type = expression.getType(); - return com.intellij.psi.util.InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION) || - com.intellij.psi.util.InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP); + if (argumentList != null && argumentList.getExpressions().length == 0) { + PsiType type = expression.getType(); + return com.intellij.psi.util.InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION) || + com.intellij.psi.util.InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP); + } } if (expression instanceof PsiMethodCallExpression) { PsiMethodCallExpression call = (PsiMethodCallExpression)expression; @@ -94,15 +106,55 @@ public class ConstructionUtils { PsiClass aClass = method.getContainingClass(); if(aClass != null) { String qualifiedName = aClass.getQualifiedName(); - if("com.google.common.collect.Maps".equals(qualifiedName) || - "com.google.common.collect.Lists".equals(qualifiedName) || - "com.google.common.collect.Sets".equals(qualifiedName)) { + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { return true; } } } } } + return isCustomizedEmptyCollectionInitializer(expression); + } + + /** + * Checks that given expression initializes empty Collection or Map with custom initial capacity or load factor + * + * @param expression expression to check + * @return true if the expression is the empty Collection or Map initializer with custom initial capacity or load factor + */ + @Contract("null -> false") + public static boolean isCustomizedEmptyCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression) { + PsiExpressionList argumentList = ((PsiNewExpression)expression).getArgumentList(); + if (argumentList == null || argumentList.getExpressions().length == 0) return false; + PsiMethod constructor = ((PsiNewExpression)expression).resolveConstructor(); + if (constructor == null) return false; + PsiClass aClass = constructor.getContainingClass(); + if (aClass == null || aClass.getQualifiedName() == null || !aClass.getQualifiedName().startsWith("java.util.")) return false; + Predicate allowedParameterType = t -> t instanceof PsiPrimitiveType || + (t instanceof PsiClassType && + ((PsiClassType)t).rawType().equalsToText(CommonClassNames.JAVA_LANG_CLASS)); + return Stream.of(constructor.getParameterList().getParameters()).map(PsiParameter::getType).allMatch(allowedParameterType); + } + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + if (ENUM_SET_NONE_OF.test(call)) return true; + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && argumentList.getExpressions().length > 0) { + PsiMethod method = call.resolveMethod(); + if (method != null && method.getParameterList().getParametersCount() > 0) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + String qualifiedName = aClass.getQualifiedName(); + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return Stream.of(method.getParameterList().getParameters()).allMatch(p -> p.getType() instanceof PsiPrimitiveType); + } + } + } + } + } return false; }