compound filter from filters chain (IDEA-146147)

This commit is contained in:
Anna Kozlova
2015-11-04 15:52:15 +01:00
parent ae4b083aec
commit 94e3eb2c04
15 changed files with 281 additions and 14 deletions
@@ -0,0 +1,163 @@
/*
* Copyright 2000-2015 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.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public class MergeFilterChainAction extends PsiElementBaseIntentionAction {
private static final Logger LOG = Logger.getInstance(MergeFilterChainAction.class.getName());
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull final PsiElement element) {
if (!(element instanceof PsiIdentifier)) return false;
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiReferenceExpression)) return false;
final PsiElement gParent = parent.getParent();
if (!(gParent instanceof PsiMethodCallExpression)) return false;
if (!isFilterCall((PsiMethodCallExpression)gParent)) return false;
return getFilterToMerge((PsiMethodCallExpression)gParent) != null;
}
private static PsiMethodCallExpression getFilterToMerge(PsiMethodCallExpression methodCallExpression) {
final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiMethodCallExpression && isFilterCall((PsiMethodCallExpression)qualifierExpression)) {
return (PsiMethodCallExpression)qualifierExpression;
}
final PsiElement parent = methodCallExpression.getParent();
if (parent instanceof PsiReferenceExpression) {
final PsiElement gParent = parent.getParent();
if (gParent instanceof PsiMethodCallExpression && isFilterCall((PsiMethodCallExpression)gParent)) {
return (PsiMethodCallExpression)gParent;
}
}
return null;
}
public static boolean isFilterCall(PsiMethodCallExpression methodCallExpression) {
if (!"filter".equals(methodCallExpression.getMethodExpression().getReferenceName())) return false;
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] expressions = argumentList.getExpressions();
if (expressions.length != 1) return false;
if (!(expressions[0] instanceof PsiLambdaExpression)) return false;
final PsiElement lambdaBody = ((PsiLambdaExpression)expressions[0]).getBody();
if (!(lambdaBody instanceof PsiExpression)) return false;
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) return false;
final PsiClass containingClass = method.getContainingClass();
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 1 &&
InheritanceUtil.isInheritor(containingClass, false, CommonClassNames.JAVA_UTIL_STREAM_STREAM) &&
InheritanceUtil.isInheritor(parameters[0].getType(), CommonClassNames.JAVA_UTIL_FUNCTION_PREDICATE)) {
return true;
}
return false;
}
@NotNull
@Override
public String getText() {
return CodeInsightBundle.message("intention.merge.filter.text");
}
@Override
@NotNull
public String getFamilyName() {
return CodeInsightBundle.message("intention.merge.filter.family");
}
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
try {
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
final PsiMethodCallExpression filterCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
LOG.assertTrue(filterCall != null);
final PsiMethodCallExpression filterToMerge = getFilterToMerge(filterCall);
LOG.assertTrue(filterToMerge != null);
final PsiMethodCallExpression callToStay = filterCall.getTextLength() < filterToMerge.getTextLength() ? filterCall : filterToMerge;
final PsiMethodCallExpression callToEliminate = callToStay == filterCall ? filterToMerge : filterCall;
final PsiLambdaExpression targetLambda = (PsiLambdaExpression)callToStay.getArgumentList().getExpressions()[0];
final PsiParameter[] parameters = targetLambda.getParameterList().getParameters();
final String name = parameters.length > 0 ? parameters[0].getName() : null;
final PsiLambdaExpression sourceLambda = (PsiLambdaExpression)callToEliminate.getArgumentList().getExpressions()[0];
if (name != null) {
final PsiParameter[] sourceLambdaParams = sourceLambda.getParameterList().getParameters();
if (sourceLambdaParams.length > 0 && !name.equals(sourceLambdaParams[0].getName())) {
for (PsiReference reference : ReferencesSearch.search(sourceLambdaParams[0]).findAll()) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement instanceof PsiReferenceExpression) {
((PsiReferenceExpression)referenceElement).handleElementRename(name);
}
}
}
}
PsiElement targetBody = targetLambda.getBody();
LOG.assertTrue(targetBody instanceof PsiExpression);
final PsiElement sourceLambdaBody = sourceLambda.getBody();
LOG.assertTrue(sourceLambdaBody instanceof PsiExpression);
final PsiExpression compoundExpression = JavaPsiFacade.getElementFactory(project)
.createExpressionFromText(targetBody.getText() + " && " + sourceLambdaBody.getText(), sourceLambda);
targetBody = targetBody.replace(compoundExpression);
CodeStyleManager.getInstance(project).reformat(targetBody);
final PsiExpression qualifierExpression = callToEliminate.getMethodExpression().getQualifierExpression();
LOG.assertTrue(qualifierExpression != null, callToEliminate);
final Collection<PsiComment> comments = PsiTreeUtil.findChildrenOfType(callToEliminate, PsiComment.class);
for (PsiComment comment : comments) {
final TextRange commentRange = comment.getTextRange();
if (!sourceLambdaBody.getTextRange().contains(commentRange) &&
!qualifierExpression.getTextRange().contains(commentRange)) {
targetBody.add(comment);
}
}
callToEliminate.replace(qualifierExpression);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
@@ -22,7 +22,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
@@ -50,18 +49,7 @@ public class SplitFilterAction extends PsiElementBaseIntentionAction {
final PsiElement gParent = parent.getParent();
if (!(gParent instanceof PsiMethodCallExpression)) return false;
final PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)gParent).getMethodExpression();
if (!"filter".equals(methodExpression.getReferenceName())) return false;
final PsiExpressionList argumentList = ((PsiMethodCallExpression)gParent).getArgumentList();
if (argumentList.getExpressions().length != 1) return false;
final PsiMethod method = ((PsiMethodCallExpression)gParent).resolveMethod();
if (method == null) return false;
final PsiClass containingClass = method.getContainingClass();
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 1 &&
InheritanceUtil.isInheritor(containingClass, false, CommonClassNames.JAVA_UTIL_STREAM_STREAM) &&
InheritanceUtil.isInheritor(parameters[0].getType(), CommonClassNames.JAVA_UTIL_FUNCTION_PREDICATE)) {
if (MergeFilterChainAction.isFilterCall((PsiMethodCallExpression)gParent)) {
return true;
}
@@ -0,0 +1,11 @@
// "Merge filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A") && name.//comment2
length() > 1//comment
/*comment1*/
).findAny();
}
}
@@ -0,0 +1,8 @@
// "Merge filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A") && name.length() > 1).findAny();
}
}
@@ -0,0 +1,10 @@
// "Merge filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filt<caret>er(name -> name.startsWith("A"))//comment
.filter(a -> a.//comment2
length() > 1 /*comment1*/).findAny();
}
}
@@ -0,0 +1,8 @@
// "Split into filter's chain" "false"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.fi<caret>lter(name -> name.startsWith("A")).filter().findAny();
}
}
@@ -0,0 +1,8 @@
// "Merge filter's chain" "false"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.fil<caret>ter(name -> name.startsWith("A")).map(a -> a).filter(b -> true);
}
}
@@ -0,0 +1,8 @@
// "Merge filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filt<caret>er(name -> name.startsWith("A")).filter(a -> a.length() > 1).findAny();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2015 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.intellij.codeInsight.intention;
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
public class MergeFilterChainActionTest extends LightIntentionActionTestCase {
public void test() throws Exception { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/mergeFilter";
}
@Override
protected Sdk getProjectJDK() {
return IdeaTestUtil.getMockJdk18();
}
}
@@ -175,6 +175,8 @@ intention.split.if.family=Split If
intention.split.if.text=Split into 2 if's
intention.split.filter.text=Split into filter's chain
intention.split.filter.family=Split filter
intention.merge.filter.text=Merge filter's chain
intention.merge.filter.family=Merge filters
intention.introduce.variable.text=Introduce local variable
intention.encapsulate.field.text=Encapsulate field
intention.implement.abstract.method.family=Implement Abstract Method
@@ -0,0 +1,9 @@
import java.util.Optional;
import java.util.stream.Stream;
public class X {
Optional<String> foo(Stream<String> stream) {
return stream.filter(<spot>name -> name.startsWith("A") && name.length() > 1</spot>).findFirst();
}
}
@@ -0,0 +1,8 @@
import java.util.Optional;
import java.util.stream.Stream;
public class X {
Optional<String> foo(Stream<String> stream) {
return stream<spot>.filter(name -> name.startsWith("A")).filter(name -> name.length() > 1)</spot>.findFirst();
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts chain of Stream.filter calls into one filter call with conjunction condition.
</body>
</html>
@@ -1,5 +1,5 @@
<html>
<body>
This intention converts stream.filter(a && b) expression into 2 chained filter calls.
This intention converts stream.filter(s -> a && b) expression into 2 chained filter calls.
</body>
</html>
+4
View File
@@ -769,6 +769,10 @@
<className>com.intellij.codeInsight.intention.impl.SplitFilterAction</className>
<category>Java/Streams</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.MergeFilterChainAction</className>
<category>Java/Streams</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.InvertIfConditionAction</className>
<category>Java/Control Flow</category>