IDEA-165395 Replace stream API with loop: l.stream().toArray(String[]::new)

This commit is contained in:
Tagir Valeev
2016-12-15 15:39:48 +07:00
parent 3b7f2879e2
commit 89e08570b4
9 changed files with 159 additions and 5 deletions
@@ -24,6 +24,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.psi.util.*;
import com.siyeh.ig.psiutils.BoolUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.StreamApiUtil;
import org.jetbrains.annotations.Contract;
@@ -59,6 +60,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
private static final String ANY_MATCH_METHOD = "anyMatch";
private static final String NONE_MATCH_METHOD = "noneMatch";
private static final String ALL_MATCH_METHOD = "allMatch";
private static final String TO_ARRAY_METHOD = "toArray";
private static final String COUNTING_COLLECTOR = "counting";
private static final String TO_LIST_COLLECTOR = "toList";
@@ -100,7 +102,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
else if (isCallOf(method, CommonClassNames.JAVA_UTIL_OPTIONAL, IS_PRESENT_METHOD, 0)) {
handleOptionalIsPresent(methodCall);
}
else if (isStreamCall(method, ANY_MATCH_METHOD)) {
else if (isStreamCall(method, TO_ARRAY_METHOD, false)) {
handleToArray(methodCall);
}
else if (isStreamCall(method, ANY_MATCH_METHOD, true)) {
if(isParentNegated(methodCall)) {
boolean argNegated = isArgumentLambdaNegated(methodCall);
registerMatchFix(methodCall,
@@ -109,7 +114,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
argNegated ? ALL_MATCH_METHOD : NONE_MATCH_METHOD));
}
}
else if (isStreamCall(method, NONE_MATCH_METHOD)) {
else if (isStreamCall(method, NONE_MATCH_METHOD, true)) {
if(isParentNegated(methodCall)) {
registerMatchFix(methodCall, new SimplifyMatchNegationFix("!"+psiClass.getName()+".noneMatch(...)", ANY_MATCH_METHOD));
}
@@ -117,7 +122,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
registerMatchFix(methodCall, new SimplifyMatchNegationFix(psiClass.getName()+".noneMatch(x -> !(...))", ALL_MATCH_METHOD));
}
}
else if (isStreamCall(method, ALL_MATCH_METHOD)) {
else if (isStreamCall(method, ALL_MATCH_METHOD, true)) {
if(isArgumentLambdaNegated(methodCall)) {
boolean parentNegated = isParentNegated(methodCall);
registerMatchFix(methodCall,
@@ -131,6 +136,22 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private void handleToArray(PsiMethodCallExpression methodCall) {
PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
PsiExpression qualifier = methodExpression.getQualifierExpression();
if(qualifier instanceof PsiMethodCallExpression && isCollectionStream((PsiMethodCallExpression)qualifier)) {
PsiArrayType type = getArrayType(methodCall);
if(type != null) {
PsiElement nameElement = methodExpression.getReferenceNameElement();
LOG.assertTrue(nameElement != null);
String replacement = type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT+"[]") ? "" :
"new "+type.getCanonicalText().replaceFirst("\\[]", "[0]");
holder.registerProblem(nameElement, "Can be replaced with collection.toArray()",
new SimplifyCallChainFix(new ReplaceWithToArrayFix(replacement)));
}
}
}
void registerMatchFix(PsiMethodCallExpression methodCall, SimplifyMatchNegationFix fix) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if(nameElement != null) {
@@ -341,6 +362,44 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
};
}
@Nullable
private static PsiArrayType getArrayType(PsiMethodCallExpression call) {
PsiType type = call.getType();
if(!(type instanceof PsiArrayType)) return null;
PsiArrayType candidate = (PsiArrayType)type;
PsiExpression[] args = call.getArgumentList().getExpressions();
if(args.length == 0) return candidate;
if(args.length != 1) return null;
PsiExpression supplier = args[0];
if(supplier instanceof PsiMethodReferenceExpression) {
// like toArray(String[]::new)
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)supplier;
PsiTypeElement qualifierType = methodRef.getQualifierType();
if (methodRef.isConstructor() && qualifierType != null && candidate.isAssignableFrom(qualifierType.getType())) {
return candidate;
}
} else if(supplier instanceof PsiLambdaExpression) {
// like toArray(size -> new String[size])
PsiLambdaExpression lambda = (PsiLambdaExpression)supplier;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if(parameters.length != 1) return null;
PsiParameter sizeParameter = parameters[0];
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
if(body instanceof PsiNewExpression) {
PsiNewExpression newExpression = (PsiNewExpression)body;
PsiExpression[] dimensions = newExpression.getArrayDimensions();
PsiType newExpressionType = newExpression.getType();
if (dimensions.length != 0 &&
ExpressionUtils.isReferenceTo(dimensions[0], sizeParameter) &&
newExpressionType != null &&
candidate.isAssignableFrom(newExpressionType)) {
return candidate;
}
}
}
return null;
}
private static boolean isCollectionConstructor(PsiMethod ctor) {
if(!ctor.getModifierList().hasExplicitModifier(PsiModifier.PUBLIC)) return false;
PsiParameterList list = ctor.getParameterList();
@@ -452,8 +511,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return false;
}
static boolean isStreamCall(@Nullable PsiMethod method, @NotNull String methodName) {
if (method == null || !methodName.equals(method.getName()) || method.getParameterList().getParametersCount() != 1) {
@Contract("null, _, _ -> false")
static boolean isStreamCall(@Nullable PsiMethod method, @NotNull String methodName, boolean checkSingleParameter) {
if (method == null || !methodName.equals(method.getName()) ||
(checkSingleParameter && method.getParameterList().getParametersCount() != 1)) {
return false;
}
final PsiClass containingClass = method.getContainingClass();
@@ -890,4 +951,31 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
((PsiMethodCallExpression)grandParent).getTypeArgumentList().delete();
}
}
private static class ReplaceWithToArrayFix implements CallChainFix {
private final String myReplacement;
private ReplaceWithToArrayFix(String replacement) {
myReplacement = replacement;
}
@Override
public String getName() {
return "Replace 'collection.stream().toArray()' with 'collection.toArray()'";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression toArrayCall = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (toArrayCall == null) return;
PsiExpression qualifier = toArrayCall.getMethodExpression().getQualifierExpression();
if(!(qualifier instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression streamCall = (PsiMethodCallExpression)qualifier;
PsiExpression collectionExpression = streamCall.getMethodExpression().getQualifierExpression();
if(collectionExpression == null) return;
CommentTracker ct = new CommentTracker();
PsiElement result = ct.replaceAndRestoreComments(toArrayCall, ct.text(collectionExpression) + ".toArray(" + myReplacement + ")");
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
}
}
@@ -0,0 +1,10 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String[]> data) {
/*generate array*/
String[][] array = data.subList(0, /*max number*/ 10).toArray(new String[0][]);
}
}
@@ -0,0 +1,9 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String> data) {
Object[] array = data.toArray(new String[0]);
}
}
@@ -0,0 +1,9 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String> data) {
Object[] array = data.toArray();
}
}
@@ -0,0 +1,10 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "false"
import java.util.*;
import java.util.function.*;
class Test {
public void testToArray(List<String[]> data, IntFunction<String[]> generator) {
Object[] array = data.stream().toA<caret>rray(generator);
}
}
@@ -0,0 +1,9 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String[]> data) {
String[][] array = data.subList(0, /*max number*/ 10).stream().to<caret>Array((size) -> /*generate array*/ new String[(size)][]);
}
}
@@ -0,0 +1,9 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String> data) {
Object[] array = data.stream().to<caret>Array(String[]::new);
}
}
@@ -0,0 +1,9 @@
// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true"
import java.util.*;
class Test {
public void testToArray(List<String> data) {
Object[] array = data.stream().to<caret>Array();
}
}