IDEA-166577 Convert patterns like IntStream.range(0, array.length).mapToObj(idx -> array[idx]) to Arrays.stream(array)

This commit is contained in:
Tagir Valeev
2017-01-13 17:46:27 +07:00
parent 895843ef37
commit ed5189e166
19 changed files with 583 additions and 99 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,15 +19,20 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
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 com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +40,7 @@ import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Stream;
@@ -134,6 +140,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
else {
handleMapToObj(methodCall);
handleIndexedIteration(methodCall);
handleStreamForEach(methodCall, method);
}
}
@@ -159,6 +166,20 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private void handleIndexedIteration(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if (nameElement == null || !nameElement.getText().startsWith("map")) return;
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if (args.length != 1) return;
PsiExpression mapper = args[0];
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
IndexedContainer container = extractContainer(qualifier, mapper);
if (container != null) {
holder.registerProblem(nameElement, "Can be replaced with element iteration",
new SimplifyCallChainFix(new ReplaceWithElementIterationFix(container, nameElement.getText())));
}
}
private void handleMapToObj(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if(nameElement == null || !"mapToObj".equals(nameElement.getText())) return;
@@ -504,6 +525,34 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return null;
}
@Contract("null, _ -> null")
static IndexedContainer extractContainer(PsiExpression qualifier, PsiExpression mapper) {
if (!(qualifier instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)qualifier;
if (!MethodCallUtils.isCallToStaticMethod(qualifierCall, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "range", 2)) {
return null;
}
PsiExpression[] rangeArgs = qualifierCall.getArgumentList().getExpressions();
if (rangeArgs.length != 2 || !ExpressionUtils.isZero(rangeArgs[0])) return null;
PsiExpression bound = rangeArgs[1];
IndexedContainer container = IndexedContainer.fromLengthExpression(bound);
if (container == null || !StreamApiUtil.isSupportedStreamElement(container.getElementType())) return null;
if (mapper instanceof PsiMethodReferenceExpression && container.isGetMethodReference((PsiMethodReferenceExpression)mapper)) {
return container;
}
if (mapper instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)mapper;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length != 1) return null;
PsiParameter indexParameter = parameters[0];
PsiElement body = lambda.getBody();
if (body != null && ReferencesSearch.search(indexParameter, new LocalSearchScope(body)).forEach(
indexReference -> container.extractGetExpressionFromIndex(ObjectUtils.tryCast(indexReference, PsiExpression.class)) != null)) {
return container;
}
}
return null;
}
static boolean isParentNegated(PsiMethodCallExpression methodCall) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent());
return parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent);
@@ -1016,4 +1065,82 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
}
private static class ReplaceWithElementIterationFix implements CallChainFix {
private final String myName;
public ReplaceWithElementIterationFix(IndexedContainer container, String name) {
PsiType type = container.getQualifier().getType();
String replacement = type instanceof PsiArrayType ? "Arrays.stream()" : "collection.stream()";
myName = "Replace IntStream.range()." + name + "() with " + replacement;
}
@Override
public String getName() {
return myName;
}
@Override
public void applyFix(@NotNull Project project, PsiElement element) {
PsiMethodCallExpression mapToObjCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (mapToObjCall == null) return;
PsiExpression mapper = ArrayUtil.getFirstElement(mapToObjCall.getArgumentList().getExpressions());
PsiExpression qualifier = mapToObjCall.getMethodExpression().getQualifierExpression();
IndexedContainer container = extractContainer(qualifier, mapper);
if (container == null) return;
PsiExpression containerQualifier = container.getQualifier();
PsiType type = containerQualifier.getType();
PsiType elementType = container.getElementType();
PsiType outElementType = StreamApiUtil.getStreamElementType(mapToObjCall.getType());
if (type == null || elementType == null) return;
String replacement;
if (type instanceof PsiArrayType) {
replacement = CommonClassNames.JAVA_UTIL_ARRAYS + ".stream(" + containerQualifier.getText() + ")";
}
else {
replacement = ParenthesesUtils.getText(containerQualifier, ParenthesesUtils.POSTFIX_PRECEDENCE) + ".stream()";
}
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
CommentTracker ct = new CommentTracker();
if (mapper instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)mapper;
PsiParameter indexParameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters());
PsiElement body = lambda.getBody();
if (body == null || indexParameter == null) return;
String nameCandidate = null;
if (containerQualifier instanceof PsiReferenceExpression) {
String name = ((PsiReferenceExpression)containerQualifier).getReferenceName();
if (name != null) {
nameCandidate = StringUtil.unpluralize(name);
if (name.equals(nameCandidate)) {
nameCandidate = null;
}
}
}
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
SuggestedNameInfo info =
javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, nameCandidate, null, elementType, true);
nameCandidate = ArrayUtil.getFirstElement(info.names);
String name = javaCodeStyleManager.suggestUniqueVariableName(nameCandidate == null ? "item" : nameCandidate, mapToObjCall, true);
Collection<PsiReference> refs = ReferencesSearch.search(indexParameter, new LocalSearchScope(body)).findAll();
for (PsiReference ref : refs) {
PsiExpression getExpression = container.extractGetExpressionFromIndex(ObjectUtils.tryCast(ref, PsiExpression.class));
if (getExpression != null) {
PsiElement result = ct.replace(getExpression, factory.createIdentifier(name));
if (getExpression == body) {
body = result;
}
}
}
PsiLambdaExpression newLambda = (PsiLambdaExpression)factory
.createExpressionFromText("(" + elementType.getCanonicalText() + " " + name + ")->" + ct.text(body), mapToObjCall);
PsiParameter newParameter = ArrayUtil.getFirstElement(newLambda.getParameterList().getParameters());
replacement += StreamApiUtil.generateMapOperation(newParameter, outElementType, newLambda.getBody());
}
PsiElement result = ct.replaceAndRestoreComments(mapToObjCall, replacement);
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
CodeStyleManager.getInstance(project).reformat(result);
}
}
}
@@ -15,7 +15,6 @@
*/
package com.intellij.codeInspection.bulkOperation;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.IteratorDeclaration;
@@ -26,10 +25,8 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
@@ -60,12 +57,6 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
.flatMap(BulkMethodInfoProvider::consumers).findFirst(info -> info.isMyMethod(ref)).orElse(null);
}
private static PsiExpression getQualifierOrThis(@NotNull PsiReferenceExpression ref) {
PsiExpression qualifier = ref.getQualifierExpression();
if (qualifier != null) return qualifier;
return JavaPsiFacade.getElementFactory(ref.getProject()).createExpressionFromText("this", ref);
}
@Nullable
private static PsiExpression findIterable(PsiMethodCallExpression expression) {
PsiExpression[] args = expression.getArgumentList().getExpressions();
@@ -147,24 +138,9 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
@Nullable
private static PsiExpression findIterableForIndexedLoop(PsiForStatement loop, PsiExpression getElementExpression) {
PsiExpression indexExpression = null;
PsiExpression iterable = null;
// Check that getElementExpression is either list.get(idx) or arr[idx] extracting idx and list/arr
if (getElementExpression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)getElementExpression;
indexExpression = arrayAccess.getIndexExpression();
iterable = arrayAccess.getArrayExpression();
}
else if (getElementExpression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)getElementExpression;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1 || !MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT)) {
return null;
}
indexExpression = args[0];
iterable = getQualifierOrThis(call.getMethodExpression());
}
if (iterable == null) return null;
IndexedContainer container = IndexedContainer.fromGetExpression(getElementExpression);
if(container == null) return null;
PsiExpression indexExpression = container.extractIndexFromGetExpression(getElementExpression);
// Check that loop initialization is like `int idx = 0` and loop update is like `idx++`
PsiStatement initialization = loop.getInitialization();
@@ -179,22 +155,14 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
}
// Check that loop condition is like `idx < arr.length` or `idx < list.size()`
PsiExpression condition = loop.getCondition();
if (!(condition instanceof PsiBinaryExpression)) return null;
PsiBinaryExpression binOp = (PsiBinaryExpression)condition;
if (!binOp.getOperationTokenType().equals(JavaTokenType.LT)) return null;
if (!ExpressionUtils.isReferenceTo(binOp.getLOperand(), indexVariable)) return null;
PsiExpression bound = binOp.getROperand();
if (bound instanceof PsiMethodCallExpression) {
PsiMethodCallExpression boundCall = (PsiMethodCallExpression)bound;
if (!MethodCallUtils.isCallToMethod(boundCall, CommonClassNames.JAVA_UTIL_LIST, PsiType.INT, "size")) return null;
PsiExpression sizeQualifier = getQualifierOrThis(boundCall.getMethodExpression());
if (PsiEquivalenceUtil.areElementsEquivalent(sizeQualifier, iterable)) return sizeQualifier;
} else {
PsiExpression arrayExpression = ExpressionUtils.getArrayFromLengthExpression(bound);
if (arrayExpression != null && PsiEquivalenceUtil.areElementsEquivalent(arrayExpression, iterable)) return arrayExpression;
PsiBinaryExpression condition = ObjectUtils.tryCast(loop.getCondition(), PsiBinaryExpression.class);
if (condition == null ||
!condition.getOperationTokenType().equals(JavaTokenType.LT) ||
!ExpressionUtils.isReferenceTo(condition.getLOperand(), indexVariable)) {
return null;
}
return null;
PsiExpression bound = condition.getROperand();
return container.extractQualifierFromLengthExpression(bound);
}
@Nullable
@@ -209,13 +177,13 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
PsiMethodCallExpression parentCall = (PsiMethodCallExpression)parent;
PsiExpression parentQualifier = PsiUtil.skipParenthesizedExprDown(parentCall.getMethodExpression().getQualifierExpression());
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_LANG_ITERABLE, null, "forEach", new PsiType[]{null})) {
return getQualifierOrThis(parentCall.getMethodExpression());
return ExpressionUtils.getQualifierOrThis(parentCall.getMethodExpression());
}
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, null, FOR_EACH_METHOD, new PsiType[]{null}) &&
parentQualifier instanceof PsiMethodCallExpression) {
PsiMethodCallExpression grandParentCall = (PsiMethodCallExpression)parentQualifier;
if (MethodCallUtils.isCallToMethod(grandParentCall, CommonClassNames.JAVA_UTIL_COLLECTION, null, "stream", PsiType.EMPTY_ARRAY)) {
return getQualifierOrThis(grandParentCall.getMethodExpression());
return ExpressionUtils.getQualifierOrThis(grandParentCall.getMethodExpression());
}
PsiExpression[] grandParentArgs = grandParentCall.getArgumentList().getExpressions();
if (grandParentArgs.length == 1) {
@@ -263,7 +231,7 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
private void register(@NotNull PsiExpression iterable,
@NotNull BulkMethodInfo info,
@NotNull PsiReferenceExpression methodExpression) {
PsiExpression qualifier = getQualifierOrThis(methodExpression);
PsiExpression qualifier = ExpressionUtils.getQualifierOrThis(methodExpression);
if (qualifier instanceof PsiThisExpression) {
PsiMethod method = PsiTreeUtil.getParentOfType(iterable, PsiMethod.class);
// Likely we are inside of the bulk method implementation
@@ -313,8 +281,7 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getStartElement();
if (!(element instanceof PsiReferenceExpression)) return;
PsiExpression qualifier = getQualifierOrThis((PsiReferenceExpression)element);
if (qualifier == null) return;
PsiExpression qualifier = ExpressionUtils.getQualifierOrThis((PsiReferenceExpression)element);
PsiExpression iterable;
if (element instanceof PsiMethodReferenceExpression) {
iterable = findIterableForFunction((PsiFunctionalExpression)element);
@@ -23,7 +23,6 @@ import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
@@ -38,7 +37,6 @@ import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
@@ -301,13 +299,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return method == null;
}
static boolean isSupported(PsiType type) {
if(type instanceof PsiPrimitiveType) {
return type.equals(PsiType.INT) || type.equals(PsiType.LONG) || type.equals(PsiType.DOUBLE);
}
return true;
}
@Nullable
private static PsiClassType createDefaultConsumerType(Project project, PsiVariable variable) {
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
@@ -711,7 +702,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class);
if(initializer == null) return null;
PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class);
if(arrayType == null || !isSupported(arrayType.getComponentType())) return null;
if(arrayType == null || !StreamApiUtil.isSupportedStreamElement(arrayType.getComponentType())) return null;
PsiExpression dimension = ArrayUtil.getFirstElement(initializer.getArrayDimensions());
if(dimension == null) return null;
PsiExpression bound = loop.myBound;
@@ -819,37 +810,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
@Override
public String createReplacement() {
if (ExpressionUtils.isReferenceTo(myExpression, myVariable)) {
if (!(myType instanceof PsiPrimitiveType)) {
return myVariable.getType() instanceof PsiPrimitiveType ? ".boxed()" : "";
}
if(myType.equals(myVariable.getType())) {
return "";
}
if (PsiType.LONG.equals(myType) && PsiType.INT.equals(myVariable.getType())) {
return ".asLongStream()";
}
if (PsiType.DOUBLE.equals(myType) && (PsiType.LONG.equals(myVariable.getType()) || PsiType.INT.equals(myVariable.getType()))) {
return ".asDoubleStream()";
}
}
String operationName = "map";
if(myType instanceof PsiPrimitiveType) {
if(!myType.equals(myVariable.getType())) {
if(PsiType.INT.equals(myType)) {
operationName = "mapToInt";
} else if(PsiType.LONG.equals(myType)) {
operationName = "mapToLong";
} else if(PsiType.DOUBLE.equals(myType)) {
operationName = "mapToDouble";
}
}
} else if(myVariable.getType() instanceof PsiPrimitiveType) {
operationName = "mapToObj";
}
PsiExpression expression = myType == null ? myExpression : RefactoringUtil.convertInitializerToNormalExpression(myExpression, myType);
return "." + OptionalUtil.getMapTypeArgument(expression, myType) + operationName +
"(" + LambdaUtil.createLambda(myVariable, expression) + ")";
return StreamApiUtil.generateMapOperation(myVariable, myType, myExpression);
}
@Override
@@ -1067,7 +1028,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
PsiArrayType iteratedValueType = tryCast(iteratedValue.getType(), PsiArrayType.class);
PsiParameter parameter = statement.getIterationParameter();
if (iteratedValueType != null && isSupported(iteratedValueType.getComponentType()) &&
if (iteratedValueType != null && StreamApiUtil.isSupportedStreamElement(iteratedValueType.getComponentType()) &&
(!(parameter.getType() instanceof PsiPrimitiveType) || parameter.getType().equals(iteratedValueType.getComponentType()))) {
return new ArrayStream(statement, parameter, iteratedValue);
}
@@ -1105,7 +1066,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
if (collectionClass == null ||
!InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true) ||
isRawSubstitution(iteratedValueType, collectionClass) ||
!isSupported(statement.getIterationParameter().getType())) {
!StreamApiUtil.isSupportedStreamElement(statement.getIterationParameter().getType())) {
return null;
}
return new CollectionStream(statement, statement.getIterationParameter(), iteratedValue);
@@ -192,7 +192,7 @@ class TerminalBlock {
PsiElement[] elements = decl.getDeclaredElements();
if(elements.length == 1) {
PsiLocalVariable declaredVar = tryCast(elements[0], PsiLocalVariable.class);
if (declaredVar != null && isSupported(declaredVar.getType())) {
if (declaredVar != null && StreamApiUtil.isSupportedStreamElement(declaredVar.getType())) {
PsiExpression initializer = declaredVar.getInitializer();
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
if (initializer != null && ReferencesSearch.search(myVariable, new LocalSearchScope(leftOver)).findFirst() == null) {
@@ -0,0 +1,13 @@
// "Replace IntStream.range().mapToLong() with Arrays.stream()" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
int[] intArr = {1,2,3,4,5};
long[] longs = Arrays.stream(intArr).asLongStream().toArray();
}
}
@@ -0,0 +1,13 @@
// "Replace IntStream.range().mapToDouble() with Arrays.stream()" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
double[] numbers = {1,2,3,4,5};
double[] doubled = Arrays.stream(numbers).map(number -> number * 2).toArray();
}
}
@@ -0,0 +1,11 @@
// "Replace IntStream.range().mapToObj() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
List<?>[] arr = list.stream().toArray(List[]::new);
}
}
@@ -0,0 +1,11 @@
// "Replace IntStream.range().map() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
int[] arr1 = list.stream().mapToInt(List::size).toArray();
}
}
@@ -0,0 +1,16 @@
// "Replace IntStream.range().mapToObj() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
/*comment1*/
/*comment4*/
// comment 0
String[] arr2 = list.get(0).stream().map(s -> s + /*comment2*/"!!!" + // comment3
s)
.toArray(String[]::new);
}
}
@@ -0,0 +1,12 @@
// "Replace IntStream.range().mapToLong() with Arrays.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
int[] intArr = {1,2,3,4,5};
long[] longs = IntStream.range(0, intArr.length).map<caret>ToLong(idx -> intArr[idx]).toArray();
}
}
@@ -0,0 +1,12 @@
// "Replace IntStream.range().mapToDouble() with Arrays.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
double[] numbers = {1,2,3,4,5};
double[] doubled = IntStream.range(0, numbers.length).ma<caret>pToDouble(idx -> numbers[idx]*2).toArray();
}
}
@@ -0,0 +1,11 @@
// "Replace IntStream.range().mapToObj() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
List<?>[] arr = IntStream.range(0, list.size()).map<caret>ToObj(index -> list.get(index)).toArray(List[]::new);
}
}
@@ -0,0 +1,11 @@
// "Replace IntStream.range().map() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
int[] arr1 = IntStream.range(0, list.size()).m<caret>ap(index -> list.get(index).size()).toArray();
}
}
@@ -0,0 +1,14 @@
// "Replace IntStream.range().mapToObj() with collection.stream()" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
String[] arr2 = IntStream.range(0, list.get(0).size()) // comment 0
.m<caret>apToObj(index -> list./*comment1*/get(0).get(index) + /*comment2*/"!!!" + // comment3
list.get(0).get(/*comment4*/index))
.toArray(String[]::new);
}
}
@@ -0,0 +1,12 @@
// "Replace IntStream.range().map() with collection.stream()" "false"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Test extends ArrayList<String> {
public void test(List<List<String>> list) {
int[] arrWrong = IntStream.range(0, list.size())
.m<caret>ap(index -> list.get(index).size() + list.get(0).get(index).length()).toArray();
}
}
@@ -0,0 +1,239 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.Nullable;
/**
* Represents an indexed container (java.util.List or array)
*
* @author Tagir Valeev
*/
public abstract class IndexedContainer {
private final PsiExpression myQualifier;
protected IndexedContainer(PsiExpression qualifier) {
myQualifier = PsiUtil.skipParenthesizedExprDown(qualifier);
}
/**
* Returns true if the supplied method reference maps index to the collection element
*
* @param methodReference method reference to check
* @return true if the supplied method reference is element retrieval method reference
*/
public abstract boolean isGetMethodReference(PsiMethodReferenceExpression methodReference);
/**
* Extracts the qualifier if the supplied expression obtains the container length (either array.length or list.size())
*
* @param expression expression to extract the qualifier from
* @return the extracted qualifier or null if the supplied expression is not a length expression. The extracted qualifier might be
* non-physical if it was implicit in the original code (e.g. "this" could be returned if original call was simply "size()")
*/
public abstract PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression);
/**
* Returns an ancestor element retrieval expression if the supplied expression is the index used in it
* (e.g. index in arr[index] or in list.get(index))
*
* @param indexExpression index expression
* @return a surrounding element retrieval expression or null if no element retrieval expression found
*/
public abstract PsiExpression extractGetExpressionFromIndex(@Nullable PsiExpression indexExpression);
/**
* Extracts the element index if the supplied expression obtains the container element by index (either array[idx] or list.get(idx))
*
* @param expression expression to extract the index from
* @return the extracted index or null if the supplied expression is not an element retrieval expression
*/
public abstract PsiExpression extractIndexFromGetExpression(@Nullable PsiExpression expression);
/**
* @return the qualifier of the expression which was used to create this {@code IndexedContainer}. The extracted qualifier might be
* non-physical if it was implicit in the original code (e.g. "this" could be returned if original call was simply "size()")
*/
public PsiExpression getQualifier() {
return myQualifier;
}
/**
* @return type of the elements in the container or null if cannot be determined
*/
public abstract PsiType getElementType();
/**
* Creates an IndexedContainer from length retrieval expression (like array.length or list.size())
*
* @param expression expression to create an IndexedContainer from
* @return newly created IndexedContainer or null if the supplied expression is not length retrieval expression
*/
@Nullable
public static IndexedContainer fromLengthExpression(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
PsiExpression arrayExpression = ExpressionUtils.getArrayFromLengthExpression(expression);
if (arrayExpression != null) {
return new ArrayIndexedContainer(arrayExpression);
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
if (ListIndexedContainer.isSizeCall(call)) {
return new ListIndexedContainer(ExpressionUtils.getQualifierOrThis(call.getMethodExpression()));
}
}
return null;
}
/**
* Creates an IndexedContainer from element retrieval expression (like array[idx] or list.get(idx))
*
* @param expression expression to create an IndexedContainer from
* @return newly created IndexedContainer or null if the supplied expression is not element retrieval expression
*/
@Nullable
public static IndexedContainer fromGetExpression(PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)expression;
return new ArrayIndexedContainer(arrayAccess.getArrayExpression());
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1 || !ListIndexedContainer.isGetCall(call)) return null;
return new ListIndexedContainer(ExpressionUtils.getQualifierOrThis(call.getMethodExpression()));
}
return null;
}
static class ArrayIndexedContainer extends IndexedContainer {
ArrayIndexedContainer(PsiExpression qualifier) {
super(qualifier);
}
@Override
public boolean isGetMethodReference(PsiMethodReferenceExpression methodReference) {
return false;
}
@Override
public PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
PsiExpression lengthQualifier = ExpressionUtils.getArrayFromLengthExpression(expression);
return lengthQualifier != null && PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), lengthQualifier) ? lengthQualifier : null;
}
@Override
public PsiExpression extractGetExpressionFromIndex(@Nullable PsiExpression indexExpression) {
if (indexExpression != null) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(indexExpression.getParent());
if (parent instanceof PsiExpression &&
PsiTreeUtil.isAncestor(extractIndexFromGetExpression((PsiExpression)parent), indexExpression, false)) {
return (PsiExpression)parent;
}
}
return null;
}
@Override
public PsiExpression extractIndexFromGetExpression(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)expression;
if (PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), arrayAccess.getArrayExpression())) {
return arrayAccess.getIndexExpression();
}
}
return null;
}
@Override
public PsiType getElementType() {
PsiType type = getQualifier().getType();
return type instanceof PsiArrayType ? ((PsiArrayType)type).getComponentType() : null;
}
}
static class ListIndexedContainer extends IndexedContainer {
ListIndexedContainer(PsiExpression qualifier) {
super(qualifier);
}
@Override
public boolean isGetMethodReference(PsiMethodReferenceExpression methodReference) {
if (!"get".equals(methodReference.getReferenceName())) return false;
PsiExpression qualifier = methodReference.getQualifierExpression();
if (qualifier == null || !PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), qualifier)) return false;
PsiMethod method = ObjectUtils.tryCast(methodReference.resolve(), PsiMethod.class);
return method != null && MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT);
}
@Override
public PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression) {
PsiMethodCallExpression call = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiMethodCallExpression.class);
if (call == null || !isSizeCall(call)) return null;
PsiExpression lengthQualifier = ExpressionUtils.getQualifierOrThis(call.getMethodExpression());
return PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), lengthQualifier) ? lengthQualifier : null;
}
@Override
public PsiExpression extractGetExpressionFromIndex(@Nullable PsiExpression indexExpression) {
if (indexExpression != null) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(indexExpression.getParent());
if (parent instanceof PsiExpressionList) {
PsiElement gParent = PsiUtil.skipParenthesizedExprUp(parent.getParent());
if (gParent instanceof PsiMethodCallExpression &&
PsiTreeUtil.isAncestor(extractIndexFromGetExpression((PsiExpression)gParent), indexExpression, false)) {
return (PsiExpression)gParent;
}
}
}
return null;
}
@Override
public PsiExpression extractIndexFromGetExpression(@Nullable PsiExpression expression) {
PsiMethodCallExpression call = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiMethodCallExpression.class);
if (call == null) return null;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length == 1 && isGetCall(call) &&
PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), ExpressionUtils.getQualifierOrThis(call.getMethodExpression()))) {
return args[0];
}
return null;
}
@Override
public PsiType getElementType() {
PsiType type = PsiUtil.substituteTypeParameter(getQualifier().getType(), CommonClassNames.JAVA_UTIL_LIST, 0, true);
return GenericsUtil.getVariableTypeByExpressionType(type);
}
static boolean isGetCall(PsiMethodCallExpression call) {
return MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT);
}
static boolean isSizeCall(PsiMethodCallExpression call) {
return MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_UTIL_LIST, PsiType.INT, "size");
}
}
}
@@ -15,9 +15,13 @@
*/
package com.siyeh.ig.psiutils;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Tagir Valeev
@@ -63,4 +67,52 @@ public class StreamApiUtil {
String qualifiedName = aClass.getQualifiedName();
return qualifiedName != null && qualifiedName.startsWith("java.util.stream.");
}
@Contract("null -> false")
public static boolean isSupportedStreamElement(PsiType type) {
if(type == null) return false;
if(type instanceof PsiPrimitiveType) {
return type.equals(PsiType.INT) || type.equals(PsiType.LONG) || type.equals(PsiType.DOUBLE);
}
return true;
}
@NotNull
public static String generateMapOperation(PsiVariable variable, @Nullable PsiType outType, PsiElement mapper) {
PsiType inType = variable.getType();
if (mapper instanceof PsiExpression && ExpressionUtils.isReferenceTo((PsiExpression)mapper, variable)) {
if (!(outType instanceof PsiPrimitiveType)) {
return inType instanceof PsiPrimitiveType ? ".boxed()" : "";
}
if(outType.equals(inType)) {
return "";
}
if (PsiType.LONG.equals(outType) && PsiType.INT.equals(inType)) {
return ".asLongStream()";
}
if (PsiType.DOUBLE.equals(outType) && (PsiType.LONG.equals(inType) || PsiType.INT.equals(inType))) {
return ".asDoubleStream()";
}
}
String operationName = "map";
if(outType instanceof PsiPrimitiveType) {
if(!outType.equals(inType)) {
if(PsiType.INT.equals(outType)) {
operationName = "mapToInt";
} else if(PsiType.LONG.equals(outType)) {
operationName = "mapToLong";
} else if(PsiType.DOUBLE.equals(outType)) {
operationName = "mapToDouble";
}
}
} else if(inType instanceof PsiPrimitiveType) {
operationName = "mapToObj";
}
if(outType != null && mapper instanceof PsiArrayInitializerExpression) {
mapper = RefactoringUtil.convertInitializerToNormalExpression((PsiExpression)mapper, outType);
}
String typeArgument = mapper instanceof PsiExpression ? OptionalUtil.getMapTypeArgument((PsiExpression)mapper, outType) : "";
return "." + typeArgument + operationName +
"(" + variable.getName() + "->" + mapper.getText() + ")";
}
}
@@ -13,6 +13,8 @@ It allows to avoid creating redundant temporary objects when traversing a collec
<li><code>collection.stream().collect(Collectors.toCollection(CollectionType::new))</code> &rarr; <code>new CollectionType&lt;&gt;(collection)</code></li>
<li><code>collection.stream().toArray()</code> &rarr; <code>collection.toArray()</code></li>
<li><code>Arrays.asList().stream()</code> &rarr; <code>Arrays.stream()</code> or <code>Stream.of()</code></li>
<li><code>IntStream.range(0, array.length).mapToObj(idx -> array[idx])</code> &rarr; <code>Arrays.stream(array)</code></li>
<li><code>IntStream.range(0, list.size()).mapToObj(idx -> list.get(idx))</code> &rarr; <code>list.stream()</code></li>
<li><code>Collections.singleton().stream()</code> &rarr; <code>Stream.of()</code></li>
<li><code>Collections.singletonList().stream()</code> &rarr; <code>Stream.of()</code></li>
<li><code>Collections.emptyList().stream()</code> &rarr; <code>Stream.empty()</code></li>