IDEA-160364, IDEA-160301 (SimplifyStreamApiCallChainsInspection.java)

Inspection to replace Collections.emptyList/Set().stream() with Stream.empty() and Collections.singleton[List](xyz).stream() with Stream.of(xyz)
Arrays.<String[]>asList(stringArray).stream() is incorrectly converted to Arrays.<String[]>stream(stringArray)
This commit is contained in:
Tagir Valeev
2016-08-26 18:38:43 +03:00
parent c63601e064
commit 7b22b0d763
13 changed files with 248 additions and 30 deletions
@@ -16,11 +16,11 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
@@ -31,13 +31,16 @@ import org.jetbrains.annotations.Nullable;
* @author Pavel.Dolgov
*/
public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalInspectionTool {
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";
@Override
public boolean isEnabledByDefault() {
@@ -54,33 +57,55 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression methodCall) {
if (isCallOf(methodCall, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
final PsiMethod method = methodCall.resolveMethod();
if (isCallOf(method, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall);
if (isCallOf(qualifierCall, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) {
final PsiExpression[] argumentExpressions = qualifierCall.getArgumentList().getExpressions();
if (argumentExpressions.length == 1 && argumentExpressions[0].getType() instanceof PsiArrayType) {
holder.registerProblem(methodCall, null, "Arrays.asList().stream() can be replaced with Arrays.stream()",
new ArraysAsListSingleArrayFix());
if (qualifierCall == null) return;
final PsiMethod qualifier = qualifierCall.resolveMethod();
ReplaceCollectionStreamFix fix = null;
if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) {
if (hasSingleArrayArgument(qualifierCall)) {
fix = new ArraysAsListSingleArrayFix();
}
else {
holder.registerProblem(methodCall, null, "Arrays.asList().stream() can be replaced with Stream.of()",
new ArraysAsListVarargFix());
fix = new ReplaceWithStreamOfFix("Arrays.asList()");
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_LIST_METHOD, 1)) {
if(!hasSingleArrayArgument(qualifierCall)) {
fix = new ReplaceSingletonWithStreamOfFix("Collections.singletonList()");
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_METHOD, 1)) {
if(!hasSingleArrayArgument(qualifierCall)) {
fix = new ReplaceSingletonWithStreamOfFix("Collections.singleton()");
}
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_LIST_METHOD, 0)) {
fix = new ReplaceWithStreamEmptyFix(EMPTY_LIST_METHOD);
}
else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_SET_METHOD, 0)) {
fix = new ReplaceWithStreamEmptyFix(EMPTY_SET_METHOD);
}
if (fix != null) {
holder.registerProblem(methodCall, null, fix.getMessage(), fix);
}
}
else {
final String name;
if (isCallOf(methodCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_METHOD, 1)) {
if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_METHOD, 1)) {
name = FOR_EACH_METHOD;
}
else if (isCallOf(methodCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_ORDERED_METHOD, 1)) {
else if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_ORDERED_METHOD, 1)) {
name = FOR_EACH_ORDERED_METHOD;
}
else {
return;
}
final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall);
if (isCallOf(qualifierCall, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
if (qualifierCall == null) return;
final PsiMethod qualifier = qualifierCall.resolveMethod();
if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
String message = "Collection.stream()." + name + "() can be replaced with Collection.forEach()";
final LocalQuickFix fix;
if (FOR_EACH_METHOD.equals(name)) {
@@ -97,6 +122,25 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
};
}
static boolean hasSingleArrayArgument(PsiMethodCallExpression qualifierCall) {
final PsiExpression[] argumentExpressions = qualifierCall.getArgumentList().getExpressions();
if (argumentExpressions.length == 1) {
PsiType type = argumentExpressions[0].getType();
if(type instanceof PsiArrayType) {
PsiType methodType = qualifierCall.getType();
// Rule out cases like Arrays.<String[]>asList(stringArr)
if(methodType instanceof PsiClassType) {
PsiType[] parameters = ((PsiClassType)methodType).getParameters();
if(parameters.length == 1 && parameters[0].equals(type))
return false;
}
return true;
}
}
return false;
}
@Nullable
private static PsiMethodCallExpression getQualifierMethodCall(PsiMethodCallExpression methodCall) {
final PsiExpression qualifierExpression = methodCall.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiMethodCallExpression) {
@@ -116,13 +160,12 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
@Contract("null, _, _, _ -> false")
protected static boolean isCallOf(@Nullable PsiMethodCallExpression expression,
protected static boolean isCallOf(@Nullable PsiMethod method,
@NotNull String className,
@NotNull String methodName,
int parametersCount) {
if (expression == null) return false;
final PsiMethod method = expression.resolveMethod();
if (method != null && methodName.equals(method.getName()) && method.getParameterList().getParametersCount() == parametersCount) {
if (method == null) return false;
if (methodName.equals(method.getName()) && method.getParameterList().getParametersCount() == parametersCount) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && className.equals(containingClass.getQualifiedName())) {
return true;
@@ -159,15 +202,34 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
@Nullable PsiExpression qualifierExpression);
}
private static abstract class ArraysAsListFix extends CallChainFixBase {
private static abstract class ReplaceCollectionStreamFix extends CallChainFixBase {
private final String myClassName;
private final String myMethodName;
private final String myQualifierCall;
private ArraysAsListFix(String className, String methodName) {
private ReplaceCollectionStreamFix(String qualifierCall, String className, String methodName) {
myQualifierCall = qualifierCall;
myClassName = className;
myMethodName = methodName;
}
String getMessage() {
return myQualifierCall + ".stream() can be replaced with " + ClassUtil.extractClassName(myClassName) + "." + myMethodName + "()";
}
@Nls
@NotNull
@Override
public String getName() {
return "Replace " + myQualifierCall + ".stream() with " + ClassUtil.extractClassName(myClassName) + "." + myMethodName + "()";
}
@Nullable
protected String getTypeParameter(@NotNull PsiMethodCallExpression qualifierCall) {
PsiType[] parameters = qualifierCall.getMethodExpression().getTypeParameters();
return parameters.length == 1 ? parameters[0].getCanonicalText() : null;
}
@Override
protected void replaceMethodCall(@NotNull PsiMethodCallExpression methodCall,
@NotNull PsiMethodCallExpression qualifierCall,
@@ -175,11 +237,12 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
methodCall.getArgumentList().replace(qualifierCall.getArgumentList());
final Project project = methodCall.getProject();
PsiType[] parameters = qualifierCall.getMethodExpression().getTypeParameters();
String typeParameter = getTypeParameter(qualifierCall);
String replacement;
if(parameters.length == 1) {
replacement = myClassName + ".<" + parameters[0].getCanonicalText() + ">" + myMethodName;
} else {
if (typeParameter != null) {
replacement = myClassName + ".<" + typeParameter + ">" + myMethodName;
}
else {
replacement = myClassName + "." + myMethodName;
}
final PsiExpression newMethodExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(replacement, methodCall);
@@ -187,22 +250,44 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private static class ArraysAsListVarargFix extends ArraysAsListFix {
private ArraysAsListVarargFix() {
super(CommonClassNames.JAVA_UTIL_STREAM_STREAM, OF_METHOD);
private static class ReplaceWithStreamOfFix extends ReplaceCollectionStreamFix {
private ReplaceWithStreamOfFix(String qualifierCall) {
super(qualifierCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, OF_METHOD);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Replace Arrays.asList().stream() with Stream.of()";
return "Replace with Stream.of()";
}
}
private static class ArraysAsListSingleArrayFix extends ArraysAsListFix {
private static class ReplaceSingletonWithStreamOfFix extends ReplaceWithStreamOfFix {
private ReplaceSingletonWithStreamOfFix(String qualifierCall) {
super(qualifierCall);
}
@Nullable
@Override
protected String getTypeParameter(@NotNull PsiMethodCallExpression qualifierCall) {
String typeParameter = super.getTypeParameter(qualifierCall);
if(typeParameter != null)
return typeParameter;
PsiType[] argTypes = qualifierCall.getArgumentList().getExpressionTypes();
if(argTypes.length == 1) {
PsiType argType = argTypes[0];
if(argType instanceof PsiArrayType) {
return argType.getCanonicalText();
}
}
return null;
}
}
private static class ArraysAsListSingleArrayFix extends ReplaceCollectionStreamFix {
private ArraysAsListSingleArrayFix() {
super(CommonClassNames.JAVA_UTIL_ARRAYS, STREAM_METHOD);
super("Arrays.asList()", CommonClassNames.JAVA_UTIL_ARRAYS, STREAM_METHOD);
}
@Nls
@@ -213,6 +298,19 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
}
}
private static class ReplaceWithStreamEmptyFix extends ReplaceCollectionStreamFix {
private ReplaceWithStreamEmptyFix(String qualifierMethodName) {
super("Collections." + qualifierMethodName + "()", CommonClassNames.JAVA_UTIL_STREAM_STREAM, EMPTY_METHOD);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Replace with Stream.empty()";
}
}
private static class CollectionForEachFix extends CallChainFixBase {
@Nls
@NotNull
@@ -0,0 +1,10 @@
// "Replace Arrays.asList().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
public class ArraysStreamSingleElementArray {
Stream<String[]> stream(String[] args) {
return Stream.<String[]>of(args);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.emptyList().stream() with Stream.empty()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionEmptyListStream {
Stream<String> stream(String[] args) {
return args.length == 1 ? Stream.<String>empty() : Arrays.stream(args);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.emptySet().stream() with Stream.empty()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionEmptySetStream {
Stream<String> stream(String[] args) {
return args.length == 1 ? Stream.<String>empty() : Arrays.stream(args);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singleton().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonArrayStream {
Stream<String[]> stream(String[] args) {
return Stream.<String[]>of(args);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singletonList().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonListStream {
Stream<String> stream(String[] args) {
return Stream.of("xyz");
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singleton().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonStream {
Stream<String> stream(String[] args) {
return Stream.<String>of("xyz");
}
}
@@ -0,0 +1,10 @@
// "Replace Arrays.asList().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
public class ArraysStreamSingleElementArray {
Stream<String[]> stream(String[] args) {
return Arrays.<Strin<caret>g[]>asList(args).stream();
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.emptyList().stream() with Stream.empty()" "true"
import java.util.*;
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);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.emptySet().stream() with Stream.empty()" "true"
import java.util.*;
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);
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singleton().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonArrayStream {
Stream<String[]> stream(String[] args) {
return Col<caret>lections.singleton(args).stream();
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singletonList().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonListStream {
Stream<String> stream(String[] args) {
return Collections.singletonList("xyz").strea<caret>m();
}
}
@@ -0,0 +1,10 @@
// "Replace Collections.singleton().stream() with Stream.of()" "true"
import java.util.*;
import java.util.stream.Stream;
class CollectionSingletonStream {
Stream<String> stream(String[] args) {
return Col<caret>lections.<String>singleton("xyz").stream();
}
}