IDEA-168203 StreamApiMigration: accept more standard collection constructors

This commit is contained in:
Tagir Valeev
2017-02-15 11:46:18 +07:00
parent fc3f0bb09d
commit 024c0831ac
4 changed files with 221 additions and 12 deletions
@@ -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();
}
}
@@ -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<String> input) {
List<String> result = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new ArrayList<>(10)));
System.out.println(result);
ArrayList<String> result2 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new ArrayList<>(20)));
System.out.println(result2);
// Non-empty
ArrayList<String> result3 = new ArrayList<>(input);
input.stream().filter(s -> !s.isEmpty()).forEach(result3::add);
System.out.println(result3);
}
void testSet(List<String> input) {
Set<String> result = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new HashSet<>(10)));
System.out.println(result);
Collection<String> result2 = input.stream().filter(s -> !s.isEmpty()).collect(Collectors.toCollection(() -> new LinkedHashSet<>(20, 0.8f)));
System.out.println(result2);
// Non-empty
AbstractSet<String> result3 = new HashSet<>(input);
input.stream().filter(s -> !s.isEmpty()).forEach(result3::add);
System.out.println(result3);
Collection<TimeUnit> result4 = input.stream().filter(s -> !s.isEmpty()).map(TimeUnit::valueOf).collect(Collectors.toCollection(() -> EnumSet.noneOf(TimeUnit.class)));
System.out.println(result4);
}
void testMap(List<String> input) {
Map<Integer, String> 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<Integer, String> 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<TimeUnit, String> 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<TimeUnit, String> result4 = new EnumMap<>(result3);
input.stream().filter(s -> !s.isEmpty()).forEach(s -> result4.put(TimeUnit.valueOf(s), s));
System.out.println(result4);
}
}
@@ -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<String> input) {
List<String> result = new ArrayList<>(10);
for (String s : in<caret>put) {
if (!s.isEmpty()) {
result.add(s);
}
}
System.out.println(result);
ArrayList<String> result2 = new ArrayList<>(20);
for (String s : input) {
if (!s.isEmpty()) {
result2.add(s);
}
}
System.out.println(result2);
// Non-empty
ArrayList<String> result3 = new ArrayList<>(input);
for (String s : input) {
if (!s.isEmpty()) {
result3.add(s);
}
}
System.out.println(result3);
}
void testSet(List<String> input) {
Set<String> result = new HashSet<>(10);
for (String s : input) {
if (!s.isEmpty()) {
result.add(s);
}
}
System.out.println(result);
Collection<String> result2 = new LinkedHashSet<>(20, 0.8f);
for (String s : input) {
if (!s.isEmpty()) {
result2.add(s);
}
}
System.out.println(result2);
// Non-empty
AbstractSet<String> result3 = new HashSet<>(input);
for (String s : input) {
if (!s.isEmpty()) {
result3.add(s);
}
}
System.out.println(result3);
Collection<TimeUnit> result4 = EnumSet.noneOf(TimeUnit.class);
for (String s : input) {
if (!s.isEmpty()) {
result4.add(TimeUnit.valueOf(s));
}
}
System.out.println(result4);
}
void testMap(List<String> input) {
Map<Integer, String> result = new HashMap<>(10);
for (String s : input) {
if (!s.isEmpty()) {
result.put(s.length(), s);
}
}
System.out.println(result);
Map<Integer, String> result2 = new HashMap<>(10, 0.8f);
for (String s : input) {
if (!s.isEmpty()) {
result2.put(s.length(), s);
}
}
System.out.println(result2);
EnumMap<TimeUnit, String> 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<TimeUnit, String> result4 = new EnumMap<>(result3);
for (String s : input) {
if (!s.isEmpty()) {
result4.put(TimeUnit.valueOf(s), s);
}
}
System.out.println(result4);
}
}
@@ -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<String> 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<PsiType> 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;
}