SimplifyStreamApiCallChainsInspection: convert IntStream.range(0, Math.min(limit, arr.length)).map(idx -> arr[idx]) to Arrays.stream(arr).limit(limit) and so on

This commit is contained in:
Tagir Valeev
2017-02-15 11:46:18 +07:00
parent f7c2d3b4b0
commit fc3f0bb09d
4 changed files with 105 additions and 11 deletions
@@ -890,6 +890,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
.parameterCount(1);
private static final CallMatcher INT_STREAM_RANGE =
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "range").parameterTypes("int", "int");
private static final CallMatcher MIN_INT =
CallMatcher.anyOf(
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min").parameterTypes("int", "int"),
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min").parameterTypes("int", "int"));
private final String myName;
@@ -913,8 +917,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
public PsiElement simplify(PsiMethodCallExpression mapToObjCall) {
Project project = mapToObjCall.getProject();
PsiExpression mapper = ArrayUtil.getFirstElement(mapToObjCall.getArgumentList().getExpressions());
IndexedContainer container = extractContainer(getQualifierMethodCall(mapToObjCall), mapper);
if (container == null) return null;
LimitedContainer limitedContainer = extractContainer(getQualifierMethodCall(mapToObjCall), mapper);
if (limitedContainer == null) return null;
IndexedContainer container = limitedContainer.myContainer;
PsiExpression limit = limitedContainer.myLimit;
PsiExpression containerQualifier = container.getQualifier();
PsiType type = containerQualifier.getType();
PsiType elementType = container.getElementType();
@@ -929,6 +935,9 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
CommentTracker ct = new CommentTracker();
if (limit != null) {
replacement += ".limit(" + ct.text(limit) + ")";
}
if (mapper instanceof PsiMethodReferenceExpression) {
mapper = LambdaRefactoringUtil.convertMethodReferenceToLambda((PsiMethodReferenceExpression)mapper, false, true);
}
@@ -975,22 +984,39 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
static CallHandler<CallChainSimplification> handler() {
return CallHandler.of(INT_STREAM_MAP, call -> {
PsiExpression mapper = call.getArgumentList().getExpressions()[0];
IndexedContainer container = extractContainer(getQualifierMethodCall(call), mapper);
if (container == null) return null;
return new ReplaceWithElementIterationFix(container, call.getMethodExpression().getReferenceName());
LimitedContainer limitedContainer = extractContainer(getQualifierMethodCall(call), mapper);
if (limitedContainer == null) return null;
return new ReplaceWithElementIterationFix(limitedContainer.myContainer, call.getMethodExpression().getReferenceName());
});
}
@Contract("null, _ -> null")
private static IndexedContainer extractContainer(PsiMethodCallExpression qualifierCall, PsiExpression mapper) {
private static LimitedContainer extractContainer(PsiMethodCallExpression qualifierCall, PsiExpression mapper) {
if (!INT_STREAM_RANGE.test(qualifierCall)) return null;
PsiExpression[] rangeArgs = qualifierCall.getArgumentList().getExpressions();
if (!ExpressionUtils.isZero(rangeArgs[0])) return null;
PsiExpression bound = rangeArgs[1];
PsiExpression bound = ExpressionUtils.resolveExpression(rangeArgs[1]);
IndexedContainer container = IndexedContainer.fromLengthExpression(bound);
if (container == null || !StreamApiUtil.isSupportedStreamElement(container.getElementType())) return null;
PsiExpression limit = null;
if (container == null) {
if(bound instanceof PsiMethodCallExpression && MIN_INT.test((PsiMethodCallExpression)bound)) {
PsiExpression[] args = ((PsiMethodCallExpression)bound).getArgumentList().getExpressions();
container = IndexedContainer.fromLengthExpression(args[0]);
if(container != null) {
limit = args[1];
} else {
container = IndexedContainer.fromLengthExpression(args[1]);
if(container != null) {
limit = args[0];
}
}
}
if(container == null) return null;
}
if (!StreamApiUtil.isSupportedStreamElement(container.getElementType())) return null;
LimitedContainer limitedContainer = new LimitedContainer(container, limit);
if (mapper instanceof PsiMethodReferenceExpression && container.isGetMethodReference((PsiMethodReferenceExpression)mapper)) {
return container;
return limitedContainer;
}
if (mapper instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)mapper;
@@ -1001,12 +1027,23 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
if (body == null) return null;
Collection<PsiReference> refs = ReferencesSearch.search(indexParameter, new LocalSearchScope(body)).findAll();
if (!refs.isEmpty() &&
refs.stream().allMatch(ref -> container.extractGetExpressionFromIndex(tryCast(ref, PsiExpression.class)) != null)) {
return container;
refs.stream()
.allMatch(ref -> limitedContainer.myContainer.extractGetExpressionFromIndex(tryCast(ref, PsiExpression.class)) != null)) {
return limitedContainer;
}
}
return null;
}
static class LimitedContainer {
@NotNull final IndexedContainer myContainer;
@Nullable final PsiExpression myLimit;
LimitedContainer(@NotNull IndexedContainer container, @Nullable PsiExpression limit) {
myContainer = container;
myLimit = limit;
}
}
}
private static class RemoveBooleanIdentityFix implements CallChainSimplification {
@@ -0,0 +1,14 @@
// "Replace with collect" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public List<String> test(String[] data) {
int top = Math.min(10, data.length);
List<String> result = Arrays.stream(data).limit(10).map(String::trim).filter(item -> !item.isEmpty()).collect(Collectors.toList());
return result;
}
}
@@ -0,0 +1,18 @@
// "Replace with collect" "true"
import java.util.ArrayList;
import java.util.List;
public class Test {
public List<String> test(String[] data) {
int top = Math.min(10, data.length);
List<String> result = new ArrayList<>();
for(int<caret> i=0; i<top; i++) {
String item = data[i].trim();
if(!item.isEmpty()) {
result.add(item);
}
}
return result;
}
}
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.psi.util.InheritanceUtil;
@@ -936,4 +937,28 @@ public class ExpressionUtils {
public static void bindCallTo(@NotNull PsiMethodCallExpression call, @NotNull String newName) {
bindReferenceTo(call.getMethodExpression(), newName);
}
/**
* Returns the expression itself (probably with stripped parentheses) or the corresponding value if the expression is a local variable
* reference which is initialized and not used anywhere else
*
* @param expression
* @return a resolved expression or expression itself
*/
@Contract("null -> null")
@Nullable
public static PsiExpression resolveExpression(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression)expression;
PsiLocalVariable variable = ObjectUtils.tryCast(reference.resolve(), PsiLocalVariable.class);
if (variable != null) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null && ReferencesSearch.search(variable).forEach(ref -> ref == reference)) {
return initializer;
}
}
}
return expression;
}
}