From ed788f420be27d254b7056841779346bcfc0749e Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 15 Jun 2012 18:20:25 +0200 Subject: [PATCH] new "Merge sequential method calls into call chain" intention --- .../IntentionPowerPak/src/META-INF/plugin.xml | 4 + .../siyeh/IntentionPowerPackBundle.properties | 2 + .../concatenation/CallSequencePredicate.java | 92 +++++++++++++++++++ .../MergeCallSequenceToChainIntention.java | 78 ++++++++++++++++ .../after.java.template | 6 ++ .../before.java.template | 7 ++ .../description.html | 5 + .../concatenation/merge_sequence/Append.java | 9 ++ .../merge_sequence/Append_after.java | 8 ++ ...MergeCallSequenceToChainIntentionTest.java | 23 +++++ 10 files changed, 234 insertions(+) create mode 100644 plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/CallSequencePredicate.java create mode 100644 plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntention.java create mode 100644 plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/after.java.template create mode 100644 plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/before.java.template create mode 100644 plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/description.html create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append.java create mode 100644 plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append_after.java create mode 100644 plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntentionTest.java diff --git a/plugins/IntentionPowerPak/src/META-INF/plugin.xml b/plugins/IntentionPowerPak/src/META-INF/plugin.xml index b022104c9475..878e3435132a 100644 --- a/plugins/IntentionPowerPak/src/META-INF/plugin.xml +++ b/plugins/IntentionPowerPak/src/META-INF/plugin.xml @@ -370,6 +370,10 @@ com.siyeh.ipp.concatenation.MakeCallChainIntoCallSequenceIntention intention.category.other + + com.siyeh.ipp.concatenation.MergeCallSequenceToChainIntention + intention.category.other + com.siyeh.ipp.exceptions.DetailExceptionsIntention intention.category.other diff --git a/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties b/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties index 86fe12496982..636391392211 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties +++ b/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties @@ -83,6 +83,8 @@ replace.equality.with.equals.intention.name=Replace '==' with '.equals()' replace.equality.with.equals.intention.family.name=Replace Equality with Equals make.call.chain.into.call.sequence.intention.name=Make method call chain into method call sequence make.call.chain.into.call.sequence.intention.family.name=Make Call Chain Into Call Sequence +merge.call.sequence.to.chain.intention.name=Merge sequential method calls into call chain +merge.call.sequence.to.chain.intention.family.name=Merge Sequential Method Calls into Call Chain detail.exceptions.intention.name=Detail exceptions detail.exceptions.intention.family.name=Detail Exceptions flip.conditional.intention.name=Flip '?:' diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/CallSequencePredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/CallSequencePredicate.java new file mode 100644 index 000000000000..b5a17409ae32 --- /dev/null +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/CallSequencePredicate.java @@ -0,0 +1,92 @@ +/* + * Copyright 2000-2012 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.siyeh.ipp.concatenation; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.ipp.base.PsiElementPredicate; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class CallSequencePredicate implements PsiElementPredicate { + + @Override + public boolean satisfiedBy(PsiElement element) { + if (!(element instanceof PsiExpressionStatement)) { + return false; + } + final PsiStatement statement = (PsiStatement)element; + final PsiStatement nextSibling = PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class); + if (nextSibling == null) { + return false; + } + final PsiVariable variable1 = getVariable(statement); + if (variable1 == null) { + return false; + } + final PsiVariable variable2 = getVariable(nextSibling); + return variable1.equals(variable2); + } + + @Nullable + private static PsiVariable getVariable(PsiStatement statement) { + if (!(statement instanceof PsiExpressionStatement)) { + return null; + } + final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)statement; + final PsiExpression expression = expressionStatement.getExpression(); + if (!(expression instanceof PsiMethodCallExpression)) { + return null; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + return getVariable(methodCallExpression); +} + @Nullable + private static PsiVariable getVariable(PsiMethodCallExpression methodCallExpression) { + final PsiType type = methodCallExpression.getType(); + if (!(type instanceof PsiClassType)) { + return null; + } + final PsiClassType classType = (PsiClassType)type; + final PsiClass aClass = classType.resolve(); + if (aClass == null) { + return null; + } + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) { + return null; + } + final PsiClass containingClass = method.getContainingClass(); + if (!aClass.equals(containingClass)) { + return null; + } + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + if (qualifierExpression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression expression = (PsiMethodCallExpression)qualifierExpression; + return getVariable(expression); + } else if (!(qualifierExpression instanceof PsiReferenceExpression)) { + return null; + }final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifierExpression; + final PsiElement target = referenceExpression.resolve(); + if (!(target instanceof PsiVariable)) { + return null; + } + return (PsiVariable)target; + } +} diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntention.java new file mode 100644 index 000000000000..6f314dce5a6b --- /dev/null +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntention.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2012 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.siyeh.ipp.concatenation; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import com.siyeh.ipp.base.Intention; +import com.siyeh.ipp.base.PsiElementPredicate; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class MergeCallSequenceToChainIntention extends Intention { + + @NotNull + @Override + protected PsiElementPredicate getElementPredicate() { + return new CallSequencePredicate(); + } + + @Override + protected void processIntention(@NotNull PsiElement element) throws IncorrectOperationException { + if (!(element instanceof PsiExpressionStatement)) { + return; + } + final PsiExpressionStatement statement = (PsiExpressionStatement)element; + final PsiExpressionStatement nextSibling = PsiTreeUtil.getNextSiblingOfType(statement, PsiExpressionStatement.class); + if (nextSibling == null) { + return; + } + final PsiExpression expression = statement.getExpression(); + final StringBuilder newMethodCallExpression = new StringBuilder(expression.getText()); + final PsiExpression expression1 = nextSibling.getExpression(); + if (!(expression1 instanceof PsiMethodCallExpression)) { + return; + } + PsiMethodCallExpression methodCallExpression = getRootMethodCallExpression((PsiMethodCallExpression)expression1); + while (true) { + final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final String methodName = methodExpression.getReferenceName(); + newMethodCallExpression.append('.').append(methodName).append(argumentList.getText()); + final PsiElement parent = methodCallExpression.getParent(); + final PsiElement grandParent = parent.getParent(); + if (!(grandParent instanceof PsiMethodCallExpression)) { + break; + } + methodCallExpression = (PsiMethodCallExpression)grandParent; + } + replaceExpression(newMethodCallExpression.toString(), expression); + nextSibling.delete(); + } + + public static PsiMethodCallExpression getRootMethodCallExpression(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + if (qualifierExpression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)qualifierExpression; + return getRootMethodCallExpression(methodCallExpression); + } + return expression; + } +} diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/after.java.template b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/after.java.template new file mode 100644 index 000000000000..8473270fe4ae --- /dev/null +++ b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/after.java.template @@ -0,0 +1,6 @@ +public class X { + void f(String a, String b) { + StringBuffer buffer = new StringBuffer(); + buffer.append(a).append(b); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/before.java.template b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/before.java.template new file mode 100644 index 000000000000..434470b03b25 --- /dev/null +++ b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/before.java.template @@ -0,0 +1,7 @@ +public class X { + void f(String a, String b) { + StringBuffer buffer = new StringBuffer(); + buffer.append(a); + buffer.append(b); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/description.html b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/description.html new file mode 100644 index 000000000000..4b4963b357ea --- /dev/null +++ b/plugins/IntentionPowerPak/src/intentionDescriptions/MergeCallSequenceToChainIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces a sequence of two method call statements with the equivalent chain of method calls (may alter semantics). + + diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append.java new file mode 100644 index 000000000000..5cf927048b3f --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append.java @@ -0,0 +1,9 @@ +package com.siyeh.ipp.concatenation.merge_sequence; + +class Append { + + void foo(StringBuilder s) { + s.append(1).append(2); + s.append(3).append(4); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append_after.java new file mode 100644 index 000000000000..ba7b83a3f4af --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/merge_sequence/Append_after.java @@ -0,0 +1,8 @@ +package com.siyeh.ipp.concatenation.merge_sequence; + +class Append { + + void foo(StringBuilder s) { + s.append(1).append(2).append(3).append(4); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntentionTest.java new file mode 100644 index 000000000000..5bd7f85847f7 --- /dev/null +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/MergeCallSequenceToChainIntentionTest.java @@ -0,0 +1,23 @@ + +package com.siyeh.ipp.concatenation; + +import com.siyeh.IntentionPowerPackBundle; +import com.siyeh.ipp.IPPTestCase; + +/** + * @author Bas Leijdekkers + */ +public class MergeCallSequenceToChainIntentionTest extends IPPTestCase { + + public void testAppend() { doTest(); } + + @Override + protected String getIntentionName() { + return IntentionPowerPackBundle.message("merge.call.sequence.to.chain.intention.name"); + } + + @Override + protected String getRelativePath() { + return "concatenation/merge_sequence"; + } +}