mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-166896 Simplify stream chain in case of *match used with Boolean::booleanValue
This commit is contained in:
+105
@@ -32,6 +32,7 @@ import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.refactoring.util.LambdaRefactoringUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
@@ -124,6 +125,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
"!" + psiClass.getName() + (argNegated ? ".anyMatch(x -> !(...))" : ".anyMatch(...)"),
|
||||
argNegated ? ALL_MATCH_METHOD : NONE_MATCH_METHOD));
|
||||
}
|
||||
handleBooleanIdentity(methodCall);
|
||||
}
|
||||
else if (isStreamCall(method, NONE_MATCH_METHOD, true)) {
|
||||
if(isParentNegated(methodCall)) {
|
||||
@@ -132,6 +134,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
if(isArgumentLambdaNegated(methodCall)) {
|
||||
registerMatchFix(methodCall, new SimplifyMatchNegationFix(psiClass.getName()+".noneMatch(x -> !(...))", ALL_MATCH_METHOD));
|
||||
}
|
||||
handleBooleanIdentity(methodCall);
|
||||
}
|
||||
else if (isStreamCall(method, ALL_MATCH_METHOD, true)) {
|
||||
if(isArgumentLambdaNegated(methodCall)) {
|
||||
@@ -140,6 +143,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
new SimplifyMatchNegationFix((parentNegated ? "!" : "") + psiClass.getName() + ".allMatch(x -> !(...))",
|
||||
parentNegated ? ANY_MATCH_METHOD : NONE_MATCH_METHOD));
|
||||
}
|
||||
handleBooleanIdentity(methodCall);
|
||||
}
|
||||
else {
|
||||
handleMapToObj(methodCall);
|
||||
@@ -148,6 +152,27 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBooleanIdentity(PsiMethodCallExpression call) {
|
||||
PsiElement nameElement = call.getMethodExpression().getReferenceNameElement();
|
||||
if (nameElement == null) return;
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
if (args.length != 1 || !isBooleanIdentity(args[0])) return;
|
||||
PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression());
|
||||
if (!(qualifier instanceof PsiMethodCallExpression)) return;
|
||||
PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)qualifier;
|
||||
if (MethodCallUtils.isCallToMethod(qualifierCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, null,
|
||||
"map", new PsiType[]{null})) {
|
||||
PsiExpression[] qualifierArgs = qualifierCall.getArgumentList().getExpressions();
|
||||
if(qualifierArgs.length != 1) return;
|
||||
PsiExpression qualifierArg = qualifierArgs[0];
|
||||
|
||||
if(canBePredicate(qualifierArg) != ThreeState.NO) {
|
||||
holder.registerProblem(nameElement, "Can be merged with previous 'map' call",
|
||||
new SimplifyCallChainFix(new RemoveBooleanIdentityFix()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleToArray(PsiMethodCallExpression methodCall) {
|
||||
if(isCollectionStream(getQualifierMethodCall(methodCall))) {
|
||||
PsiArrayType type = getArrayType(methodCall);
|
||||
@@ -346,6 +371,54 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns yes if expression can be used as j.u.f.Predicate, no if cannot,
|
||||
* unsure if can be used as Predicate after wrapping with (expression)::apply.
|
||||
*
|
||||
* @param expression expression to test
|
||||
* @return yes, no or unsure
|
||||
*/
|
||||
@NotNull
|
||||
private static ThreeState canBePredicate(PsiExpression expression) {
|
||||
expression = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
if(expression instanceof PsiFunctionalExpression) return ThreeState.YES;
|
||||
if(expression == null) return ThreeState.NO;
|
||||
PsiType type = expression.getType();
|
||||
PsiType inType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_FUNCTION_FUNCTION, 0, false);
|
||||
if(inType == null) return ThreeState.NO;
|
||||
Project project = expression.getProject();
|
||||
PsiClass predicateClass =
|
||||
JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_UTIL_FUNCTION_PREDICATE, expression.getResolveScope());
|
||||
if(predicateClass == null) return ThreeState.NO;
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiType wantedType = factory.createType(predicateClass, inType);
|
||||
PsiExpression copy = factory.createExpressionFromText(expression.getText(), expression);
|
||||
PsiType copyType = copy.getType();
|
||||
if(copyType != null && wantedType.isAssignableFrom(copyType)) return ThreeState.YES;
|
||||
PsiMethodReferenceExpression methodRef =
|
||||
(PsiMethodReferenceExpression)factory.createExpressionFromText("(" + expression.getText() + ")::apply", expression);
|
||||
PsiType methodRefType = methodRef.getType();
|
||||
if(methodRefType != null && wantedType.isAssignableFrom(methodRefType)) return ThreeState.UNSURE;
|
||||
return ThreeState.NO;
|
||||
}
|
||||
|
||||
private static boolean isBooleanIdentity(PsiExpression arg) {
|
||||
arg = PsiUtil.skipParenthesizedExprDown(arg);
|
||||
if (FunctionalExpressionUtils.isFunctionalReferenceTo(arg, CommonClassNames.JAVA_LANG_BOOLEAN, PsiType.BOOLEAN,
|
||||
"booleanValue", PsiType.EMPTY_ARRAY) ||
|
||||
FunctionalExpressionUtils.isFunctionalReferenceTo(arg, CommonClassNames.JAVA_LANG_BOOLEAN, null,
|
||||
"valueOf", PsiType.BOOLEAN)) {
|
||||
return true;
|
||||
}
|
||||
if (arg instanceof PsiLambdaExpression) {
|
||||
PsiLambdaExpression lambda = (PsiLambdaExpression)arg;
|
||||
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
PsiParameterList parameters = lambda.getParameterList();
|
||||
return parameters.getParametersCount() == 1 && ExpressionUtils.isReferenceTo(body, parameters.getParameters()[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ReplaceWithElementIterationFix findIndexedIterationFix(PsiMethodCallExpression methodCall) {
|
||||
PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
|
||||
@@ -1133,4 +1206,36 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
return CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveBooleanIdentityFix implements CallChainFix {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Merge with previous 'map' call";
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
if (qualifier == null) return;
|
||||
String name = call.getMethodExpression().getReferenceName();
|
||||
if (name == null) return;
|
||||
PsiExpression[] args = qualifier.getArgumentList().getExpressions();
|
||||
if (args.length == 1) {
|
||||
PsiExpression arg = args[0];
|
||||
PsiType argType = arg.getType();
|
||||
PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(argType);
|
||||
if (canBePredicate(arg) == ThreeState.UNSURE && method != null) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
String text = ParenthesesUtils.getText(arg, ParenthesesUtils.POSTFIX_PRECEDENCE) + "::" + method.getName();
|
||||
arg.replace(factory.createExpressionFromText(text, arg));
|
||||
}
|
||||
}
|
||||
qualifier.getMethodExpression().handleElementRename(name);
|
||||
CommentTracker ct = new CommentTracker();
|
||||
ct.replaceAndRestoreComments(call, ct.markUnchanged(qualifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn) {
|
||||
return list.stream().allMatch(fn::apply);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, Function<String, Boolean> fn2, boolean b, boolean b2) {
|
||||
return list.stream().allMatch((b ? fn : b2 ? fn2 : fn)::apply);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, Function<String, Boolean> fn2, boolean b) {
|
||||
return list.stream().allMatch((b ? fn : fn2)::apply);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, boolean b, boolean b2) {
|
||||
return list.stream().allMatch(b ? String::isEmpty : b2 ? "foo"::equals : "bar"::equals);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
return list.stream().allMatch(String::isEmpty);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
/* ditto boolean!*/
|
||||
return list.stream().anyMatch(String::isEmpty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
return list.stream().noneMatch(String::isEmpty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn) {
|
||||
return list.stream().map(fn).all<caret>Match(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// "Merge with previous 'map' call" "false"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
interface MyFunction extends Function<Object, Boolean> {};
|
||||
|
||||
MyFunction fn3 = "xyz"::equals;
|
||||
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, Function<String, Boolean> fn2, boolean b, boolean b2) {
|
||||
// neither "b ? fn : b2 ? fn2 : fn3" nor "(b ? fn : b2 ? fn2 : fn3)::apply" can serve as predicate
|
||||
// all possible replacements are longer
|
||||
return list.stream().map(b ? fn : b2 ? fn2 : fn3).all<caret>Match(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, Function<String, Boolean> fn2, boolean b, boolean b2) {
|
||||
return list.stream().map(b ? fn : b2 ? fn2 : fn).allM<caret>atch(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, Function<String, Boolean> fn2, boolean b) {
|
||||
return list.stream().map(b ? fn : fn2).all<caret>Match(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Merge with previous 'map' call" "false"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, Function<String, T> fn, boolean b) {
|
||||
// neither "b ? String::isEmpty : fn" nor "(b ? String::isEmpty : fn)::apply" can serve as predicate
|
||||
// all possible replacements are longer
|
||||
return list.stream().map(b ? String::isEmpty : fn).all<caret>Match(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Test {
|
||||
public <T extends Boolean> boolean test(List<String> list, boolean b, boolean b2) {
|
||||
return list.stream().map(b ? String::isEmpty : b2 ? "foo"::equals : "bar"::equals).allM<caret>atch(Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
return list.stream().map(String::isEmpty).allMa<caret>tch(b -> {
|
||||
return Boolean.valueOf(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
return list.stream().map(String::isEmpty).a<caret>nyMatch(/* ditto boolean!*/Boolean::booleanValue);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Merge with previous 'map' call" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
public boolean test(List<String> list) {
|
||||
return list.stream().map(String::isEmpty).none<caret>Match(b -> b);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,9 @@ It allows to avoid creating redundant temporary objects when traversing a collec
|
||||
<li><code>stream.noneMatch(x -> !(...))</code> → <code>stream.allMatch()</code></li>
|
||||
<li><code>stream.allMatch(x -> !(...))</code> → <code>stream.noneMatch()</code></li>
|
||||
<li><code>!stream.allMatch(x -> !(...))</code> → <code>stream.anyMatch()</code></li>
|
||||
<li><code>stream.map().anyMatch(Boolean::booleanValue)</code> -> <code>stream.anyMatch()</code></li>
|
||||
<li><code>stream.map().allMatch(Boolean::booleanValue)</code> -> <code>stream.allMatch()</code></li>
|
||||
<li><code>stream.map().noneMatch(Boolean::booleanValue)</code> -> <code>stream.noneMatch()</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
Note that the replacements semantic may have minor difference in some cases.
|
||||
|
||||
Reference in New Issue
Block a user