split compound filter into filters chain (IDEA-146147)

This commit is contained in:
Anna Kozlova
2015-11-04 15:52:15 +01:00
parent 9b658eb409
commit ae4b083aec
14 changed files with 304 additions and 42 deletions
@@ -0,0 +1,72 @@
/*
* 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.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
public class SplitConditionUtil {
public static PsiPolyadicExpression findCondition(PsiElement element) {
return findCondition(element, true, true);
}
public static PsiPolyadicExpression findCondition(PsiElement element, boolean acceptAnd, boolean acceptOr) {
if (!(element instanceof PsiJavaToken)) {
return null;
}
PsiJavaToken token = (PsiJavaToken)element;
if (!(token.getParent() instanceof PsiPolyadicExpression)) return null;
PsiPolyadicExpression expression = (PsiPolyadicExpression)token.getParent();
boolean isAndExpression = acceptAnd && expression.getOperationTokenType() == JavaTokenType.ANDAND;
boolean isOrExpression = acceptOr && expression.getOperationTokenType() == JavaTokenType.OROR;
if (!isAndExpression && !isOrExpression) return null;
while (expression.getParent() instanceof PsiPolyadicExpression) {
expression = (PsiPolyadicExpression)expression.getParent();
if (isAndExpression && expression.getOperationTokenType() != JavaTokenType.ANDAND) return null;
if (isOrExpression && expression.getOperationTokenType() != JavaTokenType.OROR) return null;
}
return expression;
}
public static PsiExpression getROperands(PsiPolyadicExpression expression, PsiJavaToken separator) throws IncorrectOperationException {
PsiElement next = PsiTreeUtil.skipSiblingsForward(separator, PsiWhiteSpace.class, PsiComment.class);
final int offsetInParent;
if (next == null) {
offsetInParent = separator.getStartOffsetInParent() + separator.getTextLength();
} else {
offsetInParent = next.getStartOffsetInParent();
}
PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
String rOperands = expression.getText().substring(offsetInParent);
return factory.createExpressionFromText(rOperands, expression.getParent());
}
public static PsiExpression getLOperands(PsiPolyadicExpression expression, PsiJavaToken separator) throws IncorrectOperationException {
PsiElement prev = separator;
if (prev.getPrevSibling() instanceof PsiWhiteSpace) prev = prev.getPrevSibling();
if (prev == null) {
throw new IncorrectOperationException("Unable to split '"+expression.getText()+"' left to '"+separator+"' (offset "+separator.getStartOffsetInParent()+")");
}
PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
String rOperands = expression.getText().substring(0, prev.getStartOffsetInParent());
return factory.createExpressionFromText(rOperands, expression.getParent());
}
}
@@ -0,0 +1,130 @@
/*
* 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.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import static com.intellij.codeInsight.intention.impl.SplitConditionUtil.getLOperands;
import static com.intellij.codeInsight.intention.impl.SplitConditionUtil.getROperands;
public class SplitFilterAction extends PsiElementBaseIntentionAction {
private static final Logger LOG = Logger.getInstance(SplitFilterAction.class.getName());
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
final PsiPolyadicExpression expression = SplitConditionUtil.findCondition(element, true, false);
if (expression == null || expression.getOperands().length < 2) return false;
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (!(parent instanceof PsiLambdaExpression)) return false;
if (((PsiLambdaExpression)parent).getParameterList().getParametersCount() != 1) return false;
parent = parent.getParent();
if (!(parent instanceof PsiExpressionList)) return false;
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)) {
return true;
}
return false;
}
@NotNull
@Override
public String getText() {
return CodeInsightBundle.message("intention.split.filter.text");
}
@Override
@NotNull
public String getFamilyName() {
return CodeInsightBundle.message("intention.split.filter.family");
}
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
try {
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
final PsiJavaToken token = (PsiJavaToken)element;
final PsiPolyadicExpression expression = SplitConditionUtil.findCondition(element, true, false);
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(expression, PsiLambdaExpression.class);
LOG.assertTrue(lambdaExpression != null);
final String lambdaParameterName = lambdaExpression.getParameterList().getParameters()[0].getName();
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class);
LOG.assertTrue(methodCallExpression != null, expression);
PsiExpression lOperand = getLOperands(expression, token);
PsiExpression rOperand = getROperands(expression, token);
final Collection<PsiComment> comments = PsiTreeUtil.findChildrenOfType(expression, PsiComment.class);
final PsiMethodCallExpression chainedCall =
(PsiMethodCallExpression)JavaPsiFacade.getElementFactory(project).createExpressionFromText("a.filter(" + lambdaParameterName + " -> x)", expression);
final PsiExpression argExpression = chainedCall.getArgumentList().getExpressions()[0];
final PsiElement rReplaced = ((PsiLambdaExpression)argExpression).getBody().replace(rOperand);
final PsiExpression compoundArg = methodCallExpression.getArgumentList().getExpressions()[0];
final int separatorOffset = token.getTextOffset();
for (PsiComment comment : comments) {
if (comment.getTextOffset() < separatorOffset) {
compoundArg.getParent().add(comment);
}
else {
rReplaced.getParent().add(comment);
}
}
((PsiLambdaExpression)compoundArg).getBody().replace(lOperand);
chainedCall.getMethodExpression().getQualifierExpression().replace(methodCallExpression);
methodCallExpression.replace(chainedCall);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
@@ -29,6 +29,9 @@ import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.codeInsight.intention.impl.SplitConditionUtil.getLOperands;
import static com.intellij.codeInsight.intention.impl.SplitConditionUtil.getROperands;
/**
* @author mike
*/
@@ -37,22 +40,8 @@ public class SplitIfAction extends PsiElementBaseIntentionAction {
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
if (!(element instanceof PsiJavaToken)) {
return false;
}
PsiJavaToken token = (PsiJavaToken)element;
if (!(token.getParent() instanceof PsiPolyadicExpression)) return false;
PsiPolyadicExpression expression = (PsiPolyadicExpression)token.getParent();
boolean isAndExpression = expression.getOperationTokenType() == JavaTokenType.ANDAND;
boolean isOrExpression = expression.getOperationTokenType() == JavaTokenType.OROR;
if (!isAndExpression && !isOrExpression) return false;
while (expression.getParent() instanceof PsiPolyadicExpression) {
expression = (PsiPolyadicExpression)expression.getParent();
if (isAndExpression && expression.getOperationTokenType() != JavaTokenType.ANDAND) return false;
if (isOrExpression && expression.getOperationTokenType() != JavaTokenType.OROR) return false;
}
final PsiPolyadicExpression expression = SplitConditionUtil.findCondition(element);
if (expression == null) return false;
if (!(expression.getParent() instanceof PsiIfStatement)) return false;
PsiIfStatement ifStatement = (PsiIfStatement)expression.getParent();
@@ -124,32 +113,6 @@ public class SplitIfAction extends PsiElementBaseIntentionAction {
editor.getSelectionModel().removeSelection();
}
private static PsiExpression getROperands(PsiPolyadicExpression expression, PsiJavaToken separator) throws IncorrectOperationException {
PsiElement next = PsiTreeUtil.skipSiblingsForward(separator, PsiWhiteSpace.class, PsiComment.class);
final int offsetInParent;
if (next == null) {
offsetInParent = separator.getStartOffsetInParent() + separator.getTextLength();
} else {
offsetInParent = next.getStartOffsetInParent();
}
PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
String rOperands = expression.getText().substring(offsetInParent);
return factory.createExpressionFromText(rOperands, expression.getParent());
}
private static PsiExpression getLOperands(PsiPolyadicExpression expression, PsiJavaToken separator) throws IncorrectOperationException {
PsiElement prev = separator;
if (prev.getPrevSibling() instanceof PsiWhiteSpace) prev = prev.getPrevSibling();
if (prev == null) {
throw new IncorrectOperationException("Unable to split '"+expression.getText()+"' left to '"+separator+"' (offset "+separator.getStartOffsetInParent()+")");
}
PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
String rOperands = expression.getText().substring(0, prev.getStartOffsetInParent());
return factory.createExpressionFromText(rOperands, expression.getParent());
}
private static void doOrSplit(PsiIfStatement ifStatement, PsiPolyadicExpression expression, PsiJavaToken token, Editor editor) throws IncorrectOperationException {
PsiExpression lOperand = getLOperands(expression, token);
PsiExpression rOperand = getROperands(expression, token);
@@ -0,0 +1,9 @@
// "Split into filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A")//starts with A
).filter(name -> name.length() > 1/*comment*/).findAny();
}
}
@@ -0,0 +1,8 @@
// "Split into filter's chain" "false"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A") &<caret>&).findAny();
}
}
@@ -0,0 +1,8 @@
// "Split into filter's chain" "false"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A") |<caret>| name.length() > 1).findAny();
}
}
@@ -0,0 +1,9 @@
// "Split into filter's chain" "true"
import java.util.stream.Stream;
class Test {
void foo(Stream<String> stringStream ) {
stringStream.filter(name -> name.startsWith("A") //starts with A
&<caret>& /*comment*/name.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 SplitFilterActionTest extends LightIntentionActionTestCase {
public void test() throws Exception { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/splitFilter";
}
@Override
protected Sdk getProjectJDK() {
return IdeaTestUtil.getMockJdk18();
}
}
@@ -103,6 +103,7 @@ public interface CommonClassNames {
@NonNls String JAVA_UTIL_STREAM_BASE_STREAM = "java.util.stream.BaseStream";
@NonNls String JAVA_UTIL_STREAM_STREAM = "java.util.stream.Stream";
@NonNls String JAVA_UTIL_STREAM_COLLECTORS = "java.util.stream.Collectors";
@NonNls String JAVA_UTIL_FUNCTION_PREDICATE = "java.util.function.Predicate";
@NonNls String JAVA_LANG_INVOKE_MH_POLYMORPHIC = "java.lang.invoke.MethodHandle.PolymorphicSignature";
@@ -173,6 +173,8 @@ intention.make.type.generic.family=Make Type Generic
intention.make.type.generic.text=Change type of {0} to {1}
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.introduce.variable.text=Introduce local variable
intention.encapsulate.field.text=Encapsulate field
intention.implement.abstract.method.family=Implement Abstract Method
@@ -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,8 @@
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,5 @@
<html>
<body>
This intention converts stream.filter(a && b) expression into 2 chained filter calls.
</body>
</html>
+4
View File
@@ -765,6 +765,10 @@
<className>com.intellij.codeInsight.intention.impl.SplitIfAction</className>
<category>Java/Control Flow</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.SplitFilterAction</className>
<category>Java/Streams</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.InvertIfConditionAction</className>
<category>Java/Control Flow</category>