IDEA-167574 Provide inspection for find redundant StreamSupport.stream

Refactor SimplifyStreamApiCallChainsInspection
This commit is contained in:
Tagir Valeev
2017-02-03 10:40:11 +03:00
parent f3c188db81
commit 0bc8981efa
14 changed files with 272 additions and 202 deletions
@@ -31,7 +31,6 @@ import com.intellij.psi.util.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
@@ -44,23 +43,27 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Pavel.Dolgov
* @author Tagir Valeev
*/
public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalInspectionTool {
private static final List<Function<PsiMethodCallExpression, CallChainSimplification>> SIMPLIFIERS = Arrays.asList(
ReplaceCollectionStreamFix::findCollectionStreamFix,
ReplaceWithElementIterationFix::findIndexedIterationFix,
ReplaceStreamSupportWithCollectionStreamFix::findStreamSupportFix,
ReplaceWithBoxedFix::findBoxedFix,
ReplaceWithToArrayFix::findToArrayFix
);
private static final Logger LOG = Logger.getInstance("#" + SimplifyStreamApiCallChainsInspection.class.getName());
private static final String FOR_EACH_METHOD = "forEach";
private static final String FOR_EACH_ORDERED_METHOD = "forEachOrdered";
private static final String STREAM_METHOD = "stream";
private static final String EMPTY_METHOD = "empty";
private static final String AS_LIST_METHOD = "asList";
private static final String OF_METHOD = "of";
private static final String EMPTY_LIST_METHOD = "emptyList";
private static final String EMPTY_SET_METHOD = "emptySet";
private static final String SINGLETON_LIST_METHOD = "singletonList";
private static final String SINGLETON_METHOD = "singleton";
private static final String COLLECT_METHOD = "collect";
private static final String IS_PRESENT_METHOD = "isPresent";
private static final String FIND_ANY_METHOD = "findAny";
@@ -69,7 +72,6 @@ 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";
@@ -98,33 +100,31 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if (nameElement == null) return;
PsiMethod method = methodCall.resolveMethod();
if(method == null) return;
PsiClass psiClass = method.getContainingClass();
if(psiClass == null) return;
if (isCallOf(method, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
handleCollectionStream(methodCall);
}
else if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, COLLECT_METHOD, 1)) {
StreamEx.of(SIMPLIFIERS).map(simplifier -> simplifier.apply(methodCall)).nonNull().findFirst()
.ifPresent(ccs -> holder.registerProblem(nameElement, ccs.getMessage(), new SimplifyCallChainFix(ccs)));
if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, COLLECT_METHOD, 1)) {
handleStreamCollect(methodCall);
}
else if (isCallOf(method, CommonClassNames.JAVA_UTIL_OPTIONAL, IS_PRESENT_METHOD, 0)) {
handleOptionalIsPresent(methodCall);
}
else if (isStreamCall(method, TO_ARRAY_METHOD, false)) {
handleToArray(methodCall);
}
else if (isStreamCall(method, ANY_MATCH_METHOD, true)) {
else if (isStreamCall(method, ANY_MATCH_METHOD)) {
if(isParentNegated(methodCall)) {
boolean argNegated = isArgumentLambdaNegated(methodCall);
registerMatchFix(methodCall,
new SimplifyMatchNegationFix(
"!" + psiClass.getName() + (argNegated ? ".anyMatch(x -> !(...))" : ".anyMatch(...)"),
argNegated ? ALL_MATCH_METHOD : NONE_MATCH_METHOD));
argNegated ? ALL_MATCH_METHOD : NONE_MATCH_METHOD));
}
handleBooleanIdentity(methodCall);
}
else if (isStreamCall(method, NONE_MATCH_METHOD, true)) {
else if (isStreamCall(method, NONE_MATCH_METHOD)) {
if(isParentNegated(methodCall)) {
registerMatchFix(methodCall, new SimplifyMatchNegationFix("!"+psiClass.getName()+".noneMatch(...)", ANY_MATCH_METHOD));
}
@@ -133,7 +133,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
handleBooleanIdentity(methodCall);
}
else if (isStreamCall(method, ALL_MATCH_METHOD, true)) {
else if (isStreamCall(method, ALL_MATCH_METHOD)) {
if(isArgumentLambdaNegated(methodCall)) {
boolean parentNegated = isParentNegated(methodCall);
registerMatchFix(methodCall,
@@ -143,8 +143,6 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
handleBooleanIdentity(methodCall);
}
else {
handleMapToObj(methodCall);
handleIndexedIteration(methodCall);
handleStreamForEach(methodCall, method);
}
}
@@ -170,20 +168,6 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private void handleToArray(PsiMethodCallExpression methodCall) {
if(isCollectionStream(getQualifierMethodCall(methodCall))) {
PsiArrayType type = getArrayType(methodCall);
if(type != null) {
PsiElement nameElement = methodCall.getMethodExpression().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) {
@@ -191,79 +175,6 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private void handleIndexedIteration(PsiMethodCallExpression methodCall) {
ReplaceWithElementIterationFix fix = findIndexedIterationFix(methodCall);
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if (fix != null && nameElement != null) {
holder.registerProblem(nameElement, "Can be replaced with element iteration", new SimplifyCallChainFix(fix));
}
}
private void handleMapToObj(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if(nameElement == null || !"mapToObj".equals(nameElement.getText())) return;
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length != 1) return;
PsiType type = StreamApiUtil.getStreamElementType(methodCall.getType());
if(!(type instanceof PsiClassType)) return;
PsiClass targetClass = ((PsiClassType)type).resolve();
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
if (qualifier == null || !TypeConversionUtil
.boxingConversionApplicable(StreamApiUtil.getStreamElementType(qualifier.getType()), type)) {
return;
}
if(isBoxingFunction(args[0], targetClass)) {
ReplaceWithBoxedFix fix = new ReplaceWithBoxedFix();
holder.registerProblem(nameElement,
"Can be replaced with 'boxed'", new SimplifyCallChainFix(fix));
}
}
@Contract("null, _ -> false")
private boolean isBoxingFunction(PsiExpression arg, PsiClass targetClass) {
if(arg instanceof PsiMethodReferenceExpression) {
PsiElement target = ((PsiMethodReferenceExpression)arg).resolve();
if(target instanceof PsiMethod) {
PsiMethod method = (PsiMethod)target;
// Integer::new or Integer::valueOf
if(targetClass == method.getContainingClass() &&
(method.isConstructor() || method.getName().equals("valueOf")) && method.getParameterList().getParametersCount() == 1) {
return true;
}
}
}
if(arg instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)arg;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if(parameters.length != 1) return false;
PsiParameter parameter = parameters[0];
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()));
// x -> x
if(ExpressionUtils.isReferenceTo(expression, parameter)) {
return true;
}
if(expression instanceof PsiCallExpression) {
PsiExpressionList list = ((PsiCallExpression)expression).getArgumentList();
if(list == null) return false;
PsiExpression[] args = list.getExpressions();
if(args.length != 1 || !ExpressionUtils.isReferenceTo(args[0], parameter)) {
return false;
}
// x -> new Integer(x)
if(expression instanceof PsiNewExpression) {
PsiJavaCodeReferenceElement ref = ((PsiNewExpression)expression).getClassReference();
if(ref != null && ref.isReferenceTo(targetClass)) return true;
}
// x -> Integer.valueOf(x)
if(expression instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
if(method != null && method.getContainingClass() == targetClass && method.getName().equals("valueOf")) return true;
}
}
}
return false;
}
private void handleOptionalIsPresent(PsiMethodCallExpression methodCall) {
PsiExpression optionalQualifier = methodCall.getMethodExpression().getQualifierExpression();
if(optionalQualifier instanceof PsiMethodCallExpression) {
@@ -358,13 +269,6 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
}
private void handleCollectionStream(PsiMethodCallExpression methodCall) {
ReplaceCollectionStreamFix fix = findCollectionStreamFix(methodCall);
if (fix != null) {
holder.registerProblem(methodCall, null, fix.getMessage(), new SimplifyCallChainFix(fix));
}
}
};
}
@@ -394,7 +298,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return copy.getText();
}
String adapted = ParenthesesUtils.getText(expression, ParenthesesUtils.POSTFIX_PRECEDENCE) + "::apply";
PsiClassType type = ObjectUtils.tryCast(expression.getType(), PsiClassType.class);
PsiClassType type = tryCast(expression.getType(), PsiClassType.class);
if (type == null) return null;
if (type.rawType().equalsToText(CommonClassNames.JAVA_UTIL_FUNCTION_FUNCTION)) return adapted;
PsiClass typeClass = type.resolve();
@@ -421,44 +325,49 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return arg instanceof PsiLambdaExpression && LambdaUtil.isIdentityLambda((PsiLambdaExpression)arg);
}
@Nullable
private static ReplaceWithElementIterationFix findIndexedIterationFix(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if (nameElement == null || !nameElement.getText().startsWith("map")) return null;
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if (args.length != 1) return null;
PsiExpression mapper = args[0];
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
IndexedContainer container = extractContainer(qualifier, mapper);
if (container == null) return null;
return new ReplaceWithElementIterationFix(container, nameElement.getText());
}
@Nullable
private static ReplaceCollectionStreamFix findCollectionStreamFix(PsiMethodCallExpression methodCall) {
PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall);
if (qualifierCall == null) return null;
PsiMethod qualifier = qualifierCall.resolveMethod();
if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) {
return hasSingleArrayArgument(qualifierCall) ? new ArraysAsListSingleArrayFix() : new ReplaceWithStreamOfFix("Arrays.asList()");
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_LIST_METHOD, 1)) {
if (!hasSingleArrayArgument(qualifierCall)) {
return new ReplaceSingletonWithStreamOfFix("Collections.singletonList()");
@Contract("null, _ -> false")
private static boolean isBoxingFunction(PsiExpression arg, PsiClass targetClass) {
if (arg instanceof PsiMethodReferenceExpression) {
PsiElement target = ((PsiMethodReferenceExpression)arg).resolve();
if (target instanceof PsiMethod) {
PsiMethod method = (PsiMethod)target;
// Integer::new or Integer::valueOf
if (targetClass == method.getContainingClass() &&
(method.isConstructor() || method.getName().equals("valueOf")) && method.getParameterList().getParametersCount() == 1) {
return true;
}
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_METHOD, 1)) {
if (!hasSingleArrayArgument(qualifierCall)) {
return new ReplaceSingletonWithStreamOfFix("Collections.singleton()");
if (arg instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)arg;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length != 1) return false;
PsiParameter parameter = parameters[0];
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()));
// x -> x
if (ExpressionUtils.isReferenceTo(expression, parameter)) {
return true;
}
if (expression instanceof PsiCallExpression) {
PsiExpressionList list = ((PsiCallExpression)expression).getArgumentList();
if (list == null) return false;
PsiExpression[] args = list.getExpressions();
if (args.length != 1 || !ExpressionUtils.isReferenceTo(args[0], parameter)) {
return false;
}
// x -> new Integer(x)
if (expression instanceof PsiNewExpression) {
PsiJavaCodeReferenceElement ref = ((PsiNewExpression)expression).getClassReference();
if (ref != null && ref.isReferenceTo(targetClass)) return true;
}
// x -> Integer.valueOf(x)
if (expression instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
if (method != null && method.getContainingClass() == targetClass && method.getName().equals("valueOf")) return true;
}
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_LIST_METHOD, 0)) {
return new ReplaceWithStreamEmptyFix(EMPTY_LIST_METHOD);
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_SET_METHOD, 0)) {
return new ReplaceWithStreamEmptyFix(EMPTY_SET_METHOD);
}
return null;
return false;
}
@Contract("null -> false")
@@ -470,16 +379,12 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
public static PsiElement simplifyStreamExpressions(PsiElement element) {
boolean replaced = true;
List<Function<PsiMethodCallExpression, CallChainSimplification>> simplifiers = Arrays.asList(
call -> isCollectionStream(call) ? findCollectionStreamFix(call) : null,
SimplifyStreamApiCallChainsInspection::findIndexedIterationFix
);
while(replaced) {
replaced = false;
Map<PsiMethodCallExpression, CallChainSimplification> callToSimplification =
StreamEx.ofTree(element, e -> StreamEx.of(e.getChildren()))
.select(PsiMethodCallExpression.class)
.cross(call -> StreamEx.of(simplifiers).map(simplifier -> simplifier.apply(call)))
.cross(call -> StreamEx.of(SIMPLIFIERS).map(simplifier -> simplifier.apply(call)))
.nonNullValues()
.toMap((a, b) -> a);
for (Map.Entry<PsiMethodCallExpression, CallChainSimplification> entry : callToSimplification.entrySet()) {
@@ -605,7 +510,7 @@ 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(ObjectUtils.tryCast(ref, PsiExpression.class)) != null)) {
refs.stream().allMatch(ref -> container.extractGetExpressionFromIndex(tryCast(ref, PsiExpression.class)) != null)) {
return container;
}
}
@@ -647,11 +552,8 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
@Nullable
static PsiMethodCallExpression getQualifierMethodCall(PsiMethodCallExpression methodCall) {
final PsiExpression qualifierExpression = methodCall.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiMethodCallExpression) {
return (PsiMethodCallExpression)qualifierExpression;
}
return null;
return
tryCast(PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()), PsiMethodCallExpression.class);
}
@NotNull
@@ -679,12 +581,9 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return false;
}
@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;
}
@Contract("null, _ -> false")
static boolean isStreamCall(@Nullable PsiMethod method, @NotNull String methodName) {
if (method == null || !methodName.equals(method.getName()) || method.getParameterList().getParametersCount() != 1) return false;
final PsiClass containingClass = method.getContainingClass();
return containingClass != null && InheritanceUtil.isInheritor(containingClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM);
}
@@ -695,6 +594,8 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
interface CallChainSimplification extends CallChainFix {
String getMessage();
default void applyFix(@NotNull Project project, PsiElement element) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class, false);
if (call != null) {
@@ -733,6 +634,11 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
private static abstract class ReplaceCollectionStreamFix implements CallChainSimplification {
private static final String EMPTY_LIST_METHOD = "emptyList";
private static final String EMPTY_SET_METHOD = "emptySet";
private static final String SINGLETON_LIST_METHOD = "singletonList";
private static final String SINGLETON_METHOD = "singleton";
private static final String AS_LIST_METHOD = "asList";
private final String myClassName;
private final String myMethodName;
private final String myQualifierCall;
@@ -779,6 +685,34 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
PsiExpression newMethodExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(replacement, streamCall);
return JavaCodeStyleManager.getInstance(project).shortenClassReferences(streamCall.getMethodExpression().replace(newMethodExpression));
}
@Nullable
static ReplaceCollectionStreamFix findCollectionStreamFix(PsiMethodCallExpression methodCall) {
if (!isCollectionStream(methodCall)) return null;
PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall);
if (qualifierCall == null) return null;
PsiMethod qualifier = qualifierCall.resolveMethod();
if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) {
return hasSingleArrayArgument(qualifierCall) ? new ArraysAsListSingleArrayFix() : new ReplaceWithStreamOfFix("Arrays.asList()");
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_LIST_METHOD, 1)) {
if (!hasSingleArrayArgument(qualifierCall)) {
return new ReplaceSingletonWithStreamOfFix("Collections.singletonList()");
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_METHOD, 1)) {
if (!hasSingleArrayArgument(qualifierCall)) {
return new ReplaceSingletonWithStreamOfFix("Collections.singleton()");
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_LIST_METHOD, 0)) {
return new ReplaceWithStreamEmptyFix(EMPTY_LIST_METHOD);
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_SET_METHOD, 0)) {
return new ReplaceWithStreamEmptyFix(EMPTY_SET_METHOD);
}
return null;
}
}
private static class ReplaceWithStreamOfFix extends ReplaceCollectionStreamFix {
@@ -1081,29 +1015,47 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private static class ReplaceWithBoxedFix implements CallChainFix {
private static class ReplaceWithBoxedFix implements CallChainSimplification {
@Override
public String getName() {
return "Replace with 'boxed'";
}
@Override
public void applyFix(@NotNull Project project, PsiElement element) {
if(!(element instanceof PsiIdentifier)) return;
PsiElement parent = element.getParent();
if(!(parent instanceof PsiReferenceExpression)) return;
PsiElement grandParent = parent.getParent();
if(!(grandParent instanceof PsiMethodCallExpression)) return;
PsiExpression[] args = ((PsiMethodCallExpression)grandParent).getArgumentList().getExpressions();
if(args.length != 1) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
element.replace(factory.createIdentifier("boxed"));
public String getMessage() {
return "Can be replaced with 'boxed'";
}
@Override
public PsiElement simplify(PsiMethodCallExpression call) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1) return null;
call.getMethodExpression().handleElementRename("boxed");
args[0].delete();
((PsiMethodCallExpression)grandParent).getTypeArgumentList().delete();
call.getTypeArgumentList().delete();
return call;
}
static ReplaceWithBoxedFix findBoxedFix(PsiMethodCallExpression methodCall) {
if (!"mapToObj".equals(methodCall.getMethodExpression().getReferenceName())) return null;
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if (args.length != 1) return null;
PsiType type = StreamApiUtil.getStreamElementType(methodCall.getType());
if (!(type instanceof PsiClassType)) return null;
PsiClass targetClass = ((PsiClassType)type).resolve();
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
if (qualifier == null ||
!TypeConversionUtil
.boxingConversionApplicable(StreamApiUtil.getStreamElementType(qualifier.getType()), type) ||
!isBoxingFunction(args[0], targetClass)) {
return null;
}
return new ReplaceWithBoxedFix();
}
}
private static class ReplaceWithToArrayFix implements CallChainFix {
private static class ReplaceWithToArrayFix implements CallChainSimplification {
private static final String TO_ARRAY_METHOD = "toArray";
private final String myReplacement;
private ReplaceWithToArrayFix(String replacement) {
@@ -1116,17 +1068,33 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
@Override
public void applyFix(@NotNull Project project, PsiElement element) {
PsiMethodCallExpression toArrayCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (toArrayCall == null) return;
PsiExpression qualifier = toArrayCall.getMethodExpression().getQualifierExpression();
if(!(qualifier instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression streamCall = (PsiMethodCallExpression)qualifier;
public String getMessage() {
return "Can be replaced with collection.toArray()";
}
@Override
public PsiElement simplify(PsiMethodCallExpression toArrayCall) {
PsiMethodCallExpression streamCall = getQualifierMethodCall(toArrayCall);
if(streamCall == null) return null;
PsiExpression collectionExpression = streamCall.getMethodExpression().getQualifierExpression();
if(collectionExpression == null) return;
if(collectionExpression == null) return null;
CommentTracker ct = new CommentTracker();
PsiElement result = ct.replaceAndRestoreComments(toArrayCall, ct.text(collectionExpression) + ".toArray(" + myReplacement + ")");
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
return ct.replaceAndRestoreComments(toArrayCall, ct.text(collectionExpression) + ".toArray(" + myReplacement + ")");
}
@Nullable
static ReplaceWithToArrayFix findToArrayFix(PsiMethodCallExpression methodCall) {
if (!TO_ARRAY_METHOD.equals(methodCall.getMethodExpression().getReferenceName())) return null;
PsiMethod method = methodCall.resolveMethod();
if (method == null) return null;
PsiClass aClass = method.getContainingClass();
if (aClass == null || !CommonClassNames.JAVA_UTIL_STREAM_STREAM.equals(aClass.getQualifiedName())) return null;
if (!isCollectionStream(getQualifierMethodCall(methodCall))) return null;
PsiArrayType type = getArrayType(methodCall);
if (type == null) return null;
String replacement = type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT+"[]") ? "" :
"new "+type.getCanonicalText().replaceFirst("\\[]", "[0]");
return new ReplaceWithToArrayFix(replacement);
}
}
@@ -1144,6 +1112,11 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return myName;
}
@Override
public String getMessage() {
return "Can be replaced with element iteration";
}
@Override
public PsiElement simplify(PsiMethodCallExpression mapToObjCall) {
Project project = mapToObjCall.getProject();
@@ -1190,7 +1163,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
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));
PsiExpression getExpression = container.extractGetExpressionFromIndex(tryCast(ref, PsiExpression.class));
if (getExpression != null) {
PsiElement result = ct.replace(getExpression, factory.createIdentifier(name));
if (getExpression == body) {
@@ -1207,6 +1180,19 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
return CodeStyleManager.getInstance(project).reformat(result);
}
@Nullable
static ReplaceWithElementIterationFix findIndexedIterationFix(PsiMethodCallExpression methodCall) {
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
if (nameElement == null || !nameElement.getText().startsWith("map")) return null;
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if (args.length != 1) return null;
PsiExpression mapper = args[0];
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
IndexedContainer container = extractContainer(qualifier, mapper);
if (container == null) return null;
return new ReplaceWithElementIterationFix(container, nameElement.getText());
}
}
private static class RemoveBooleanIdentityFix implements CallChainFix {
@@ -1219,8 +1205,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
public void applyFix(@NotNull Project project, PsiElement element) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (call == null) return;
PsiMethodCallExpression qualifier = ObjectUtils
.tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiMethodCallExpression.class);
PsiMethodCallExpression qualifier = getQualifierMethodCall(call);
if (qualifier == null) return;
String name = call.getMethodExpression().getReferenceName();
if (name == null) return;
@@ -1237,4 +1222,59 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
ct.replaceAndRestoreComments(call, ct.markUnchanged(qualifier));
}
}
private static class ReplaceStreamSupportWithCollectionStreamFix implements CallChainSimplification {
private boolean myParallel;
public ReplaceStreamSupportWithCollectionStreamFix(boolean parallel) {
myParallel = parallel;
}
@Override
public String getName() {
return "Replace with 'collection." + getMethodName() + "' call";
}
@Override
public String getMessage() {
return "Can be replaced with 'collection." + (getMethodName()) + "' call";
}
@NotNull
private String getMethodName() {
return myParallel ? "parallelStream" : "stream";
}
@Override
public PsiElement simplify(PsiMethodCallExpression call) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 2) return null;
PsiMethodCallExpression spliteratorCall = tryCast(PsiUtil.skipParenthesizedExprDown(args[0]), PsiMethodCallExpression.class);
if (spliteratorCall == null) return null;
spliteratorCall.getMethodExpression().handleElementRename(getMethodName());
CommentTracker ct = new CommentTracker();
return ct.replace(call, spliteratorCall);
}
@Nullable
static ReplaceStreamSupportWithCollectionStreamFix findStreamSupportFix(PsiMethodCallExpression call) {
if (!MethodCallUtils.isCallToMethod(call, "java.util.stream.StreamSupport", null, "stream",
null, PsiType.BOOLEAN)) {
return null;
}
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 2) return null;
PsiExpression parallel = args[1];
if (!ExpressionUtils.isLiteral(parallel, Boolean.TRUE) && !ExpressionUtils.isLiteral(parallel, Boolean.FALSE)) return null;
PsiMethodCallExpression spliterator = tryCast(PsiUtil.skipParenthesizedExprDown(args[0]), PsiMethodCallExpression.class);
if (spliterator != null &&
MethodCallUtils.isCallToMethod(spliterator, CommonClassNames.JAVA_UTIL_COLLECTION, null, "spliterator", PsiType.EMPTY_ARRAY)) {
PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression());
if (qualifier != null && !(qualifier instanceof PsiThisExpression)) {
return new ReplaceStreamSupportWithCollectionStreamFix(ExpressionUtils.isLiteral(parallel, Boolean.TRUE));
}
}
return null;
}
}
}
@@ -5,7 +5,7 @@ import java.util.List;
public class Test {
public void test(List<List<String>> list) {
List<?>[] arr = list.stream().toArray(List[]::new);
List<?>[] arr = list.toArray(new List[0]);
System.out.println(Arrays.toString(arr));
}
}
@@ -0,0 +1,15 @@
// "Fix all 'Simplify stream API call chains' problems in file" "true"
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.StreamSupport;
public class Test {
public static void test(List<String> list, Collection<Number> collection, Iterable<Integer> iterable) {
list.stream().filter(Objects::nonNull).forEach(System.out::println);
collection.parallelStream().forEach(System.out::println);
StreamSupport.stream(iterable.spliterator(), true).forEach(System.out::println);
StreamSupport.stream(list.spliterator(), collection.isEmpty()).forEach(System.out::println);
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
public class ArraysStreamSingleElementArray {
Stream<String[]> stream(String[] args) {
return Arrays.<Strin<caret>g[]>asList(args).stream();
return Arrays.<String[]>asList(args).st<caret>ream();
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
public class ArraysStreamSingleObjectElementArray {
Stream<Object[]> stream(String[] args) {
return Arrays.<Objec<caret>t[]>asList(args).stream();
return Arrays.<Object[]>asList(args).stre<caret>am();
}
}
@@ -4,6 +4,6 @@ import java.util.Arrays;
class AsListArrayStream {
String max(String[] args) {
return Arrays.asL<caret>ist(args).stream().max(String::compareTo);
return Arrays.asList(args).st<caret>ream().max(String::compareTo);
}
}
@@ -4,6 +4,6 @@ import static java.util.Arrays.asList;
class AsListArrayStreamStaticImport {
String max(String[] args) {
return asL<caret>ist(args).stream().max(String::compareTo);
return asList(args).st<caret>ream().max(String::compareTo);
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
class AsListIncompleteArgsStream {
Stream<String> abc() {
return Ar<caret>rays.asList("a", , ).stream();
return Arrays.asList("a", , ).stre<caret>am();
}
}
@@ -6,7 +6,7 @@ import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<List<Object>> list = Arrays.<List<Object>>asL<caret>ist(Arrays.asList(1,2,3), Arrays.asList(1.0, 2.0, 3.0)).stream()
List<List<Object>> list = Arrays.<List<Object>>asList(Arrays.asList(1,2,3), Arrays.asList(1.0, 2.0, 3.0)).str<caret>eam()
.collect(Collectors.toList());
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
class CollectionEmptyListStream {
Stream<String> stream(String[] args) {
return args.length == 1 ? Col<caret>lections.<String>emptyList().stream() : Arrays.stream(args);
return args.length == 1 ? Collections.<String>emptyList().st<caret>ream() : Arrays.stream(args);
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
class CollectionEmptySetStream {
Stream<String> stream(String[] args) {
return args.length == 1 ? Col<caret>lections.<String>emptySet().stream() : Arrays.stream(args);
return args.length == 1 ? Collections.<String>emptySet().st<caret>ream() : Arrays.stream(args);
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
class CollectionSingletonArrayStream {
Stream<String[]> stream(String[] args) {
return Col<caret>lections.singleton(args).stream();
return Collections.singleton(args).str<caret>eam();
}
}
@@ -5,6 +5,6 @@ import java.util.stream.Stream;
class CollectionSingletonStream {
Stream<String> stream(String[] args) {
return Col<caret>lections.<String>singleton("xyz").stream();
return Collections.<String>singleton("xyz").st<caret>ream();
}
}
@@ -0,0 +1,15 @@
// "Fix all 'Simplify stream API call chains' problems in file" "true"
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.StreamSupport;
public class Test {
public static void test(List<String> list, Collection<Number> collection, Iterable<Integer> iterable) {
StreamSupport.str<caret>eam(list.spliterator(), false).filter(Objects::nonNull).forEach(System.out::println);
StreamSupport.stream(collection.spliterator(), true).forEach(System.out::println);
StreamSupport.stream(iterable.spliterator(), true).forEach(System.out::println);
StreamSupport.stream(list.spliterator(), collection.isEmpty()).forEach(System.out::println);
}
}