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";
+ }
+}