Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Tiercelin2020-12-22 17:38:01 +0000
committerFabrice Tiercelin2020-12-22 17:38:01 +0000
commit16051ece96111f64ce9bea65b133c80e5bc56b30 (patch)
tree5d34911294c301006153ad5187b804d6c727d7ac
parent6362683b50c2c0ecf9e431ab0ededc4ad41970d4 (diff)
downloadeclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.tar.gz
eclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.tar.xz
eclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.zip
Multi-catch Change-Id: Iba9d8a728725278dc5f201db59fa8577fbeae4c8 Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
-rw-r--r--org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java1
-rw-r--r--org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties1
-rw-r--r--org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java59
-rw-r--r--org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java12
-rw-r--r--org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java403
-rw-r--r--org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java2
-rw-r--r--org.eclipse.jdt.ui/plugin.xml7
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/MultiCatchCleanUp.java506
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java1
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties1
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/JavaFeatureTabPage.java5
11 files changed, 995 insertions, 3 deletions
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
index 63e91afbf5..e313fb9882 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
@@ -162,6 +162,7 @@ public class MultiFixMessages extends NLS {
public static String CheckSignOfBitwiseOperation_description;
public static String TryWithResourceCleanup_description;
+ public static String MultiCatchCleanUp_description;
static {
// initialize resource bundle
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
index 3b1eb5d6d6..3119c40ca4 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
@@ -144,3 +144,4 @@ CheckSignOfBitwiseOperation_description=Use != 0 instead of > 0 when comparing t
SwitchExpressionsCleanUp_ConvertToSwitchExpressions_description=Convert to switch expression where possible
TryWithResourceCleanup_description=Use try-with-resource
+MultiCatchCleanUp_description=Multi-catch
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
index 0c9a480ad1..cfcf14e49c 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
@@ -29,6 +29,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1380,6 +1381,24 @@ public class ASTNodes {
}
/**
+ * Returns the {@link ITypeBinding} of the {@link VariableDeclaration}.
+ *
+ * @param varDecl the variable declaration
+ * @return the fragment's type binding, or null if none can be found
+ */
+ public static ITypeBinding resolveTypeBinding(final VariableDeclaration varDecl) {
+ if (varDecl != null) {
+ IVariableBinding varBinding= varDecl.resolveBinding();
+
+ if (varBinding != null) {
+ return varBinding.getType();
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Returns whether the provided operator is the same as the one of provided node.
*
* @param node the node for which to test the operator
@@ -2388,6 +2407,46 @@ public class ASTNodes {
}
/**
+ * Returns a set made of all the method bindings which are overridden by the
+ * provided method binding.
+ *
+ * @param overridingMethod the overriding method binding
+ * @return a set made of all the method bindings which are overridden by the
+ * provided method binding
+ */
+ public static Set<IMethodBinding> getOverridenMethods(final IMethodBinding overridingMethod) {
+ Set<IMethodBinding> results= new HashSet<>();
+ findOverridenMethods(overridingMethod, results, overridingMethod.getDeclaringClass());
+ return results;
+ }
+
+ private static void findOverridenMethods(final IMethodBinding overridingMethod, final Set<IMethodBinding> results,
+ final ITypeBinding declaringClass) {
+ ITypeBinding superclass= declaringClass.getSuperclass();
+ if (superclass != null && !addOverridenMethods(overridingMethod, superclass, results)) {
+ findOverridenMethods(overridingMethod, results, superclass);
+ }
+
+ for (ITypeBinding itf : declaringClass.getInterfaces()) {
+ if (!addOverridenMethods(overridingMethod, itf, results)) {
+ findOverridenMethods(overridingMethod, results, itf);
+ }
+ }
+ }
+
+ private static boolean addOverridenMethods(final IMethodBinding overridingMethod, final ITypeBinding superType,
+ final Set<IMethodBinding> results) {
+ for (IMethodBinding methodFromType : superType.getDeclaredMethods()) {
+ if (overridingMethod.overrides(methodFromType) && !results.add(methodFromType)) {
+ // Type has already been visited
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Returns the null-checked expression if the provided node is a null check.
*
* @param expression the suspected null-checked expression
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
index f58adad7a4..509bcb8431 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
@@ -1755,6 +1755,18 @@ public class CleanUpConstants {
public static final String TRY_WITH_RESOURCE= "cleanup.try_with_resource"; //$NON-NLS-1$
/**
+ * Refactors <code>catch</code> clauses with the same body to use Java 7's multi-catch.
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.19
+ */
+ public static final String MULTI_CATCH= "cleanup.multi_catch"; //$NON-NLS-1$
+
+ /**
* Should the Clean Up Wizard be shown when executing the Clean Up Action? <br>
* <br>
* Possible values: {<code><b>true</b></code>, <code><b>false</b></code>} <br>
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
index f69519d888..1721c99f47 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
@@ -252,7 +252,7 @@ public class CleanUpTest1d7 extends CleanUpTestCase {
+ " Arrays.hashCode(texts), anotherDouble);\n" //
+ " }\n" //
+ "}\n";
- assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new String[] { MultiFixMessages.HashCleanup_description });
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.HashCleanup_description)));
assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { output });
}
@@ -653,6 +653,405 @@ public class CleanUpTest1d7 extends CleanUpTestCase {
}
@Test
+ public void testMultiCatch() throws Exception {
+ // Given
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String given= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.io.IOException;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " private static final class ThrowingObject<E1 extends Throwable, E2 extends Throwable> {\n" //
+ + " private void throwingMethod() throws E1, E2 {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex1 extends Exception {\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String getExplanation() {\n" //
+ + " return \"\";\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex2 extends Exception {\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class OverridingException1 extends Exception {\n" //
+ + " @Override\n" //
+ + " public void printStackTrace() {\n" //
+ + " super.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class OverridingException2 extends Exception {\n" //
+ + " @Override\n" //
+ + " public void printStackTrace() {\n" //
+ + " super.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatch(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (IOException ioe) {\n" //
+ + " ioe.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorAddToMultiCatch(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | IllegalStateException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (IOException ioe) {\n" //
+ + " ioe.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void removeMoreSpecializedException(ThrowingObject<IllegalArgumentException, RuntimeException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (RuntimeException re) {\n" //
+ + " re.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithOverridenMethods(ThrowingObject<IllegalArgumentException, OverridingException1> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (OverridingException1 oe1) {\n" //
+ + " oe1.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithOverridenMethodsFromSupertype(ThrowingObject<OverridingException1, OverridingException2> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (OverridingException1 oe1) {\n" //
+ + " oe1.printStackTrace();\n" //
+ + " } catch (OverridingException2 oe2) {\n" //
+ + " oe2.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorUp(ThrowingObject<IllegalArgumentException, IllegalAccessException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (RuntimeException re) {\n" //
+ + " re.toString();\n" //
+ + " } catch (IllegalAccessException ne) {\n" //
+ + " ne.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorDown(ThrowingObject<IllegalAccessException, RuntimeException> obj, int errorCount) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalAccessException iae) {\n" //
+ + " errorCount++;\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (RuntimeException ioe) {\n" //
+ + " errorCount++;\n" //
+ + " ioe.toString();\n" //
+ + " } catch (Exception e) {\n" //
+ + " errorCount = errorCount + 1;\n" //
+ + " e.printStackTrace();\n" //
+ + " }\n" //
+ + " System.out.println(\"Error count: \" + errorCount);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithLocalVariables(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " String s = \"[\" + iae;\n" //
+ + " String s1 = \"]\";\n" //
+ + " System.out.println(s + s1);\n" //
+ + " } catch (IOException ioe) {\n" //
+ + " String s = \"[\" + ioe;\n" //
+ + " String s2 = \"]\";\n" //
+ + " System.out.println(s + s2);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class EA extends Exception {}\n" //
+ + " public class EB extends Exception {}\n" //
+ + " public class EB1 extends EB {}\n" //
+ + " public class EC extends Exception {}\n" //
+ + "\n" //
+ + " public String refactorUp2() {\n" //
+ + " try {\n" //
+ + " return throwingMethod();\n" //
+ + " } catch (EA | EB1 e) {\n" //
+ + " throw new RuntimeException(\"v1\", e);\n" //
+ + " } catch (EB e) {\n" //
+ + " throw new RuntimeException(\"v2\", e);\n" //
+ + " } catch (EC e) {\n" //
+ + " throw new RuntimeException(\"v1\", e);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String throwingMethod() throws EA, EB1, EB, EC {\n" //
+ + " return null;\n" //
+ + " }\n" //
+ + "}\n";
+
+ String expected= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.io.IOException;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " private static final class ThrowingObject<E1 extends Throwable, E2 extends Throwable> {\n" //
+ + " private void throwingMethod() throws E1, E2 {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex1 extends Exception {\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String getExplanation() {\n" //
+ + " return \"\";\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex2 extends Exception {\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class OverridingException1 extends Exception {\n" //
+ + " @Override\n" //
+ + " public void printStackTrace() {\n" //
+ + " super.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class OverridingException2 extends Exception {\n" //
+ + " @Override\n" //
+ + " public void printStackTrace() {\n" //
+ + " super.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatch(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | IOException ioe) {\n" //
+ + " ioe.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorAddToMultiCatch(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | IllegalStateException | IOException ioe) {\n" //
+ + " ioe.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void removeMoreSpecializedException(ThrowingObject<IllegalArgumentException, RuntimeException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (RuntimeException re) {\n" //
+ + " re.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithOverridenMethods(ThrowingObject<IllegalArgumentException, OverridingException1> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | OverridingException1 oe1) {\n" //
+ + " oe1.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithOverridenMethodsFromSupertype(ThrowingObject<OverridingException1, OverridingException2> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (OverridingException1 | OverridingException2 oe2) {\n" //
+ + " oe2.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorUp(ThrowingObject<IllegalArgumentException, IllegalAccessException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | IllegalAccessException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (RuntimeException re) {\n" //
+ + " re.toString();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorDown(ThrowingObject<IllegalAccessException, RuntimeException> obj, int errorCount) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (RuntimeException ioe) {\n" //
+ + " errorCount++;\n" //
+ + " ioe.toString();\n" //
+ + " } catch (Exception e) {\n" //
+ + " errorCount = errorCount + 1;\n" //
+ + " e.printStackTrace();\n" //
+ + " }\n" //
+ + " System.out.println(\"Error count: \" + errorCount);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void refactorMultiCatchWithLocalVariables(ThrowingObject<IllegalArgumentException, IOException> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException | IOException ioe) {\n" //
+ + " String s = \"[\" + ioe;\n" //
+ + " String s2 = \"]\";\n" //
+ + " System.out.println(s + s2);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class EA extends Exception {}\n" //
+ + " public class EB extends Exception {}\n" //
+ + " public class EB1 extends EB {}\n" //
+ + " public class EC extends Exception {}\n" //
+ + "\n" //
+ + " public String refactorUp2() {\n" //
+ + " try {\n" //
+ + " return throwingMethod();\n" //
+ + " } catch (EA | EB1 | EC e) {\n" //
+ + " throw new RuntimeException(\"v1\", e);\n" //
+ + " } catch (EB e) {\n" //
+ + " throw new RuntimeException(\"v2\", e);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String throwingMethod() throws EA, EB1, EB, EC {\n" //
+ + " return null;\n" //
+ + " }\n" //
+ + "}\n";
+
+ // When
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+ enable(CleanUpConstants.MULTI_CATCH);
+
+ // Then
+ assertNotEquals("The class must be changed", given, expected);
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.MultiCatchCleanUp_description)));
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected });
+ }
+
+ @Test
+ public void testDoNotUseMultiCatch() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " private static final class MyException extends RuntimeException {\n" //
+ + " private static final long serialVersionUID = 1L;\n" //
+ + "\n" //
+ + " private MyException(Ex1 ex1) {\n" //
+ + " }\n" //
+ + "\n" //
+ + " private MyException(Ex2 ex2) {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class ThrowingObject<E1 extends Throwable, E2 extends Throwable> {\n" //
+ + " private void throwingMethod() throws E1, E2 {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex1 extends Exception {\n" //
+ + " private static final long serialVersionUID = 1L;\n" //
+ + "\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String getExplanation() {\n" //
+ + " return \"\";\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex2 extends Exception {\n" //
+ + " private static final long serialVersionUID = 1L;\n" //
+ + "\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final class Ex3 extends Exception {\n" //
+ + " private static final long serialVersionUID = 1L;\n" //
+ + "\n" //
+ + " private void print() {\n" //
+ + " }\n" //
+ + "\n" //
+ + " private String getExplanation() {\n" //
+ + " return \"\";\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void doNotRefactorMultiCatchWithNoOverridenMethods(ThrowingObject<Ex3, Ex1> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (Ex3 ne) {\n" //
+ + " ne.getExplanation();\n" //
+ + " } catch (Ex1 ex1) {\n" //
+ + " ex1.getExplanation();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void doNotRefactorNoCommonSuperType(ThrowingObject<Ex1, Ex2> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (Ex1 e1) {\n" //
+ + " e1.print();\n" //
+ + " } catch (Ex2 e2) {\n" //
+ + " e2.print();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void doNotRefactorChangeInBehaviourClassHierarchy(ThrowingObject<IllegalArgumentException, Exception> obj) {\n" //
+ + " try {\n" //
+ + " obj.throwingMethod();\n" //
+ + " } catch (IllegalArgumentException iae) {\n" //
+ + " iae.printStackTrace();\n" //
+ + " } catch (Exception ioe) {\n" //
+ + " ioe.toString();\n" //
+ + " } catch (Throwable t) {\n" //
+ + " t.printStackTrace();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void doNotRefactorMultiCatchWhenMethodDoesNotCallCommonSupertype(ThrowingObject<Ex1, Ex2> object) {\n" //
+ + " try {\n" //
+ + " object.throwingMethod();\n" //
+ + " } catch (Ex1 ex1) {\n" //
+ + " throw new MyException(ex1);\n" //
+ + " } catch (Ex2 ex2) {\n" //
+ + " throw new MyException(ex2);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+ enable(CleanUpConstants.MULTI_CATCH);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
+
+ @Test
public void testObjectsEqualsWithImportConflict() throws Exception {
IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
String sample= "" //
@@ -778,7 +1177,7 @@ public class CleanUpTest1d7 extends CleanUpTestCase {
+ "}\n";
String expected1= sample;
- assertGroupCategoryUsed(new ICompilationUnit[] { cu1 }, new String[] { FixMessages.Java50Fix_ConvertToEnhancedForLoop_description });
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu1 }, new HashSet<>(Arrays.asList(FixMessages.Java50Fix_ConvertToEnhancedForLoop_description)));
assertRefactoringResultAsExpected(new ICompilationUnit[] { cu1 }, new String[] { expected1 });
}
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index b90b5ae43a..bcda64ee67 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
@@ -165,6 +165,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
// Java Features
options.setOption(TRY_WITH_RESOURCE, CleanUpOptions.FALSE);
+ options.setOption(MULTI_CATCH, CleanUpOptions.FALSE);
}
private static void setSaveParticipantSettings(CleanUpOptions options) {
@@ -308,6 +309,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
// Java Features
options.setOption(TRY_WITH_RESOURCE, CleanUpOptions.FALSE);
+ options.setOption(MULTI_CATCH, CleanUpOptions.FALSE);
}
public static void initDefaults(IPreferenceStore store) {
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 17b3bed5e4..8916a45c25 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7317,9 +7317,14 @@
runAfter="org.eclipse.jdt.ui.cleanup.join">
</cleanUp>
<cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.MultiCatchCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.multi_catch"
+ runAfter="org.eclipse.jdt.ui.cleanup.try_with_resource">
+ </cleanUp>
+ <cleanUp
class="org.eclipse.jdt.internal.ui.fix.TypeParametersCleanUp"
id="org.eclipse.jdt.ui.cleanup.type_parameters"
- runAfter="org.eclipse.jdt.ui.cleanup.try_with_resource">
+ runAfter="org.eclipse.jdt.ui.cleanup.multi_catch">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.HashCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/MultiCatchCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/MultiCatchCleanUp.java
new file mode 100644
index 0000000000..35f2c35462
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/MultiCatchCleanUp.java
@@ -0,0 +1,506 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Fabrice TIERCELIN and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Fabrice TIERCELIN - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.fix;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CatchClause;
+import org.eclipse.jdt.core.dom.ClassInstanceCreation;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
+import org.eclipse.jdt.core.dom.SuperMethodInvocation;
+import org.eclipse.jdt.core.dom.TryStatement;
+import org.eclipse.jdt.core.dom.Type;
+import org.eclipse.jdt.core.dom.UnionType;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.ASTSemanticMatcher;
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
+import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+
+/**
+ * A fix that refactors <code>catch</code> clauses with the same body to use Java 7's multi-catch:
+ * <ul>
+ * <li>The JVM must be Java 7 or higher,</li>
+ * <li>The <code>catch</code> blocks should do the same thing,</li>
+ * <li>The <code>catch</code> blocks must be able to move.</li>
+ * </ul>
+ */
+public class MultiCatchCleanUp extends AbstractMultiFix {
+ private enum MergeDirection {
+ NONE, UP, DOWN;
+ }
+
+ public MultiCatchCleanUp() {
+ this(Collections.emptyMap());
+ }
+
+ public MultiCatchCleanUp(final Map<String, String> options) {
+ super(options);
+ }
+
+ @Override
+ public CleanUpRequirements getRequirements() {
+ boolean requireAST= isEnabled(CleanUpConstants.MULTI_CATCH);
+ return new CleanUpRequirements(requireAST, false, false, null);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ if (isEnabled(CleanUpConstants.MULTI_CATCH)) {
+ return new String[] { MultiFixMessages.MultiCatchCleanUp_description };
+ }
+
+ return new String[0];
+ }
+
+ @Override
+ public String getPreview() {
+ StringBuilder bld= new StringBuilder();
+ bld.append("try {\n"); //$NON-NLS-1$
+ bld.append(" obj.throwingMethod();\n"); //$NON-NLS-1$
+
+ if (isEnabled(CleanUpConstants.MULTI_CATCH)) {
+ bld.append("} catch (IllegalArgumentException | IOException ioe) {\n"); //$NON-NLS-1$
+ bld.append(" ioe.printStackTrace();\n"); //$NON-NLS-1$
+ bld.append("}\n\n\n"); //$NON-NLS-1$
+ } else {
+ bld.append("} catch (IllegalArgumentException iae) {\n"); //$NON-NLS-1$
+ bld.append(" iae.printStackTrace();\n"); //$NON-NLS-1$
+ bld.append("} catch (IOException ioe) {\n"); //$NON-NLS-1$
+ bld.append(" ioe.printStackTrace();\n"); //$NON-NLS-1$
+ bld.append("}\n"); //$NON-NLS-1$
+ }
+
+ return bld.toString();
+ }
+
+ @Override
+ protected ICleanUpFix createFix(final CompilationUnit unit) throws CoreException {
+ if (!isEnabled(CleanUpConstants.MULTI_CATCH) || !JavaModelUtil.is1d7OrHigher(unit.getJavaElement().getJavaProject())) {
+ return null;
+ }
+
+ final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+ unit.accept(new ASTVisitor() {
+ abstract class AbstractBinding {
+ protected abstract boolean isSubTypeCompatible(AbstractBinding type);
+ }
+
+ class SingleBinding extends AbstractBinding {
+ private final ITypeBinding typeBinding;
+
+ public SingleBinding(final ITypeBinding typeBinding) {
+ this.typeBinding= typeBinding;
+ }
+
+ @Override
+ protected boolean isSubTypeCompatible(final AbstractBinding other) {
+ if (typeBinding == null) {
+ return false;
+ }
+
+ if (other instanceof SingleBinding) {
+ SingleBinding o= (SingleBinding) other;
+
+ if (o.typeBinding == null) {
+ return false;
+ }
+
+ return typeBinding.isSubTypeCompatible(o.typeBinding);
+ }
+
+ if (other instanceof MultiBinding) {
+ MultiBinding o= (MultiBinding) other;
+
+ for (ITypeBinding otherTypeBinding : o.typeBindings) {
+ if (!isSubTypeCompatible(new SingleBinding(otherTypeBinding))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ class MultiBinding extends AbstractBinding {
+ private final ITypeBinding[] typeBindings;
+
+ public MultiBinding(final ITypeBinding[] typeBindings) {
+ this.typeBindings= typeBindings;
+ }
+
+ @Override
+ protected boolean isSubTypeCompatible(final AbstractBinding other) {
+ if (other instanceof SingleBinding) {
+ for (ITypeBinding selfTypeBinding : typeBindings) {
+ if (new SingleBinding(selfTypeBinding).isSubTypeCompatible(other)) {
+ return true;
+ }
+ }
+ }
+
+ if (other instanceof MultiBinding) {
+ MultiBinding o= (MultiBinding) other;
+
+ for (ITypeBinding otherTypeBinding : o.typeBindings) {
+ if (!isSubTypeCompatible(new SingleBinding(otherTypeBinding))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ final class MultiCatchASTMatcher extends ASTSemanticMatcher {
+ private final Map<ASTNode, ASTNode> matchingVariables= new HashMap<>();
+
+ public MultiCatchASTMatcher(final CatchClause catchClause1, final CatchClause catchClause2) {
+ matchingVariables.put(catchClause1.getException(), catchClause2.getException());
+ }
+
+ @Override
+ public boolean match(final VariableDeclarationStatement node, final Object other) {
+ return super.match(node, other) || matchVariableDeclarationsWithDifferentNames(node, other);
+ }
+
+ private boolean matchVariableDeclarationsWithDifferentNames(final VariableDeclarationStatement node, final Object other) {
+ if (!(other instanceof VariableDeclarationStatement)) {
+ return false;
+ }
+
+ List<VariableDeclarationFragment> fragments1= node.fragments();
+ List<VariableDeclarationFragment> fragments2= ((VariableDeclarationStatement) other).fragments();
+
+ if (fragments1.size() == fragments2.size()) {
+ Iterator<VariableDeclarationFragment> it1= fragments1.iterator();
+ Iterator<VariableDeclarationFragment> it2= fragments2.iterator();
+ // Do not make all efforts to try to reconcile fragments declared in different
+ // order
+ while (it1.hasNext() && it2.hasNext()) {
+ VariableDeclarationFragment f1= it1.next();
+ VariableDeclarationFragment f2= it2.next();
+
+ if (ASTNodes.resolveTypeBinding(f1) != null
+ && Objects.equals(ASTNodes.resolveTypeBinding(f1), ASTNodes.resolveTypeBinding(f2))
+ && ASTNodes.match(this, f1.getInitializer(), f2.getInitializer())) {
+ this.matchingVariables.put(f1, f2);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean match(final SimpleName node, final Object other) {
+ return super.match(node, other) || areBothReferringToSameVariables(node, other);
+ }
+
+ @Override
+ public boolean match(final MethodInvocation methodInvocation1, final Object other) {
+ if (other instanceof MethodInvocation) {
+ MethodInvocation methodInvocation2= (MethodInvocation) other;
+ return super.match(methodInvocation1, methodInvocation2)
+ && isSameMethodBinding(methodInvocation1.resolveMethodBinding(), methodInvocation2.resolveMethodBinding());
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean match(final SuperMethodInvocation superMethodInvocation1, final Object other) {
+ if (other instanceof SuperMethodInvocation) {
+ SuperMethodInvocation superMethodInvocation2= (SuperMethodInvocation) other;
+ return super.match(superMethodInvocation1, superMethodInvocation2)
+ && isSameMethodBinding(superMethodInvocation1.resolveMethodBinding(), superMethodInvocation2.resolveMethodBinding());
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean match(final ClassInstanceCreation classInstanceCreation1, final Object other) {
+ if (other instanceof ClassInstanceCreation) {
+ ClassInstanceCreation classInstanceCreation2= (ClassInstanceCreation) other;
+ return super.match(classInstanceCreation1, classInstanceCreation2)
+ && isSameMethodBinding(classInstanceCreation1.resolveConstructorBinding(), classInstanceCreation2.resolveConstructorBinding());
+ }
+
+ return false;
+ }
+
+ private boolean isSameMethodBinding(final IMethodBinding binding1, final IMethodBinding binding2) {
+ return binding1 != null && binding2 != null
+ && (binding1.equals(binding2) || binding1.overrides(binding2) || binding2.overrides(binding1)
+ // This is a really expensive check. Do it at the very end
+ || areOverridingSameMethod(binding1, binding2));
+ }
+
+ private boolean areOverridingSameMethod(final IMethodBinding binding1, final IMethodBinding binding2) {
+ Set<IMethodBinding> commonOverridenMethods= ASTNodes.getOverridenMethods(binding1);
+ commonOverridenMethods.retainAll(ASTNodes.getOverridenMethods(binding2));
+ return !commonOverridenMethods.isEmpty();
+ }
+
+ private boolean areBothReferringToSameVariables(final ASTNode node, final Object other) {
+ for (Entry<ASTNode, ASTNode> pairedVariables : matchingVariables.entrySet()) {
+ if (ASTNodes.isSameVariable(node, pairedVariables.getKey())) {
+ return other instanceof ASTNode && ASTNodes.isSameVariable((ASTNode) other, pairedVariables.getValue());
+ }
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ public boolean visit(final TryStatement visited) {
+ List<CatchClause> catchClauses= visited.catchClauses();
+ AbstractBinding[] typeBindings= resolveTypeBindings(catchClauses);
+
+ for (int i= 0; i < catchClauses.size() - 1; i++) {
+ List<CatchClause> mergeableCatchClauses= new ArrayList<>(catchClauses.size());
+ mergeableCatchClauses.add(catchClauses.get(i));
+ MergeDirection direction= null;
+
+ for (int j= i + 1; j < catchClauses.size(); j++) {
+ MergeDirection newDirection= mergeDirection(typeBindings, i, j);
+
+ if (!MergeDirection.NONE.equals(newDirection)
+ && (direction == null || direction.equals(newDirection))
+ && matchMultiCatch(catchClauses.get(i), catchClauses.get(j))) {
+ direction= newDirection;
+ mergeableCatchClauses.add(catchClauses.get(j));
+ }
+ }
+
+ if (mergeableCatchClauses.size() > 1) {
+ rewriteOperations.add(new MultiCatchOperation(mergeableCatchClauses, direction));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private AbstractBinding[] resolveTypeBindings(final List<CatchClause> catchClauses) {
+ AbstractBinding[] results= new AbstractBinding[catchClauses.size()];
+
+ for (int i= 0; i < catchClauses.size(); i++) {
+ results[i]= resolveBinding(catchClauses.get(i));
+ }
+
+ return results;
+ }
+
+ private AbstractBinding resolveBinding(final CatchClause catchClause) {
+ SingleVariableDeclaration singleVariableDeclaration= catchClause.getException();
+ Type type= singleVariableDeclaration.getType();
+
+ switch (type.getNodeType()) {
+ case ASTNode.SIMPLE_TYPE:
+ return new SingleBinding(type.resolveBinding());
+
+ case ASTNode.UNION_TYPE:
+ List<Type> types= ((UnionType) type).types();
+ ITypeBinding[] typeBindings= new ITypeBinding[types.size()];
+
+ for (int i= 0; i < types.size(); i++) {
+ typeBindings[i]= types.get(i).resolveBinding();
+ }
+
+ return new MultiBinding(typeBindings);
+
+ default:
+ return null;
+ }
+ }
+
+ private MergeDirection mergeDirection(final AbstractBinding[] typeBindings, final int start, final int end) {
+ if (canMergeTypesDown(typeBindings, start, end)) {
+ return MergeDirection.DOWN;
+ }
+
+ if (canMergeTypesUp(typeBindings, start, end)) {
+ return MergeDirection.UP;
+ }
+
+ return MergeDirection.NONE;
+ }
+
+ private boolean canMergeTypesDown(final AbstractBinding[] types, final int start, final int end) {
+ AbstractBinding startType= types[start];
+
+ for (int i= start + 1; i < end; i++) {
+ AbstractBinding type= types[i];
+
+ if (startType.isSubTypeCompatible(type)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean canMergeTypesUp(final AbstractBinding[] types, final int start, final int end) {
+ AbstractBinding endType= types[end];
+
+ for (int i= start + 1; i < end; i++) {
+ AbstractBinding type= types[i];
+
+ if (type.isSubTypeCompatible(endType)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean matchMultiCatch(final CatchClause firstCatchClause, final CatchClause secondCatchClause) {
+ MultiCatchASTMatcher matcher= new MultiCatchASTMatcher(firstCatchClause, secondCatchClause);
+ return ASTNodes.match(matcher, firstCatchClause.getBody(), secondCatchClause.getBody());
+ }
+ });
+
+ if (rewriteOperations.isEmpty()) {
+ return null;
+ }
+
+ return new CompilationUnitRewriteOperationsFix(MultiFixMessages.MultiCatchCleanUp_description, unit,
+ rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
+ }
+
+ @Override
+ public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) {
+ return false;
+ }
+
+ @Override
+ protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException {
+ return null;
+ }
+
+ private static class MultiCatchOperation extends CompilationUnitRewriteOperation {
+ private final List<CatchClause> mergeableCatchClauses;
+ private final MergeDirection direction;
+
+ public MultiCatchOperation(final List<CatchClause> mergeableCatchClauses, final MergeDirection direction) {
+ this.mergeableCatchClauses= mergeableCatchClauses;
+ this.direction= direction;
+ }
+
+ @Override
+ public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
+ ASTRewrite rewrite= cuRewrite.getASTRewrite();
+ AST ast= cuRewrite.getRoot().getAST();
+ TextEditGroup group= createTextEditGroup(MultiFixMessages.MultiCatchCleanUp_description, cuRewrite);
+
+ List<Type> typesByClause= new ArrayList<>();
+
+ for (CatchClause mergeableCatchClause : mergeableCatchClauses) {
+ typesByClause.add(mergeableCatchClause.getException().getType());
+ }
+
+ List<Type> allTypes= new ArrayList<>();
+ collectAllUnionedTypes(typesByClause, allTypes);
+ removeSupersededAlternatives(allTypes);
+
+ UnionType newUnionType= ast.newUnionType();
+ newUnionType.types().addAll(ASTNodes.createMoveTarget(rewrite, allTypes));
+ List<CatchClause> removedClauses= new ArrayList<>(mergeableCatchClauses);
+
+ CatchClause mergedClause;
+ if (MergeDirection.UP.equals(direction)) {
+ mergedClause= removedClauses.remove(0);
+ } else {
+ mergedClause= removedClauses.remove(removedClauses.size() - 1);
+ }
+
+ rewrite.set(mergedClause.getException(), SingleVariableDeclaration.TYPE_PROPERTY, newUnionType, group);
+
+ for (CatchClause mergeableCatchClause : removedClauses) {
+ rewrite.remove(mergeableCatchClause, group);
+ }
+ }
+
+ private void collectAllUnionedTypes(final Collection<Type> inputTypes, final List<Type> outputTypes) {
+ for (Type type : inputTypes) {
+ if (type instanceof UnionType) {
+ UnionType unionType= (UnionType) type;
+ collectAllUnionedTypes(unionType.types(), outputTypes);
+ } else {
+ outputTypes.add(type);
+ }
+ }
+ }
+
+ private void removeSupersededAlternatives(final List<Type> allTypes) {
+ for (ListIterator<Type> it1= allTypes.listIterator(); it1.hasNext();) {
+ ITypeBinding binding1= it1.next().resolveBinding();
+
+ for (ListIterator<Type> it2= allTypes.listIterator(it1.nextIndex()); it2.hasNext();) {
+ ITypeBinding binding2= it2.next().resolveBinding();
+
+ if (binding1 != null && binding1.isSubTypeCompatible(binding2)) {
+ it1.remove();
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
index 35614e4836..c035ca04d5 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
@@ -179,6 +179,7 @@ public class CleanUpMessages extends NLS {
public static String JavaFeatureTabPage_GroupName_Java1d7;
public static String JavaFeatureTabPage_CheckboxName_TryWithResource;
+ public static String JavaFeatureTabPage_CheckboxName_MultiCatch;
public static String JavaFeatureTabPage_CheckboxName_RedundantTypeArguments;
public static String JavaFeatureTabPage_CheckboxName_Hash;
public static String JavaFeatureTabPage_CheckboxName_ObjectsEquals;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
index cef230fbde..19a0c502bc 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
@@ -156,6 +156,7 @@ JavaFeatureTabPage_CheckboxName_Join=Use String.join() &when possible
JavaFeatureTabPage_GroupName_Java1d7=Java 7
JavaFeatureTabPage_CheckboxName_TryWithResource=Use try-with-resource
+JavaFeatureTabPage_CheckboxName_MultiCatch=Use Multi-catch
JavaFeatureTabPage_CheckboxName_RedundantTypeArguments=Use di&amond operator
JavaFeatureTabPage_CheckboxName_Hash=Use Objects.hash()
JavaFeatureTabPage_CheckboxName_ObjectsEquals=Use Objects.e&quals() in the equals method implementation
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/JavaFeatureTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/JavaFeatureTabPage.java
index c8c5ff020d..99df1bc01e 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/JavaFeatureTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/JavaFeatureTabPage.java
@@ -26,6 +26,7 @@ import org.eclipse.jdt.internal.ui.fix.ConvertLoopCleanUp;
import org.eclipse.jdt.internal.ui.fix.HashCleanUp;
import org.eclipse.jdt.internal.ui.fix.JoinCleanUp;
import org.eclipse.jdt.internal.ui.fix.LambdaExpressionsCleanUp;
+import org.eclipse.jdt.internal.ui.fix.MultiCatchCleanUp;
import org.eclipse.jdt.internal.ui.fix.ObjectsEqualsCleanUp;
import org.eclipse.jdt.internal.ui.fix.PatternMatchingForInstanceofCleanUp;
import org.eclipse.jdt.internal.ui.fix.SwitchExpressionsCleanUp;
@@ -46,6 +47,7 @@ public final class JavaFeatureTabPage extends AbstractCleanUpTabPage {
new LambdaExpressionsCleanUp(values),
new JoinCleanUp(values),
new TryWithResourceCleanUp(values),
+ new MultiCatchCleanUp(values),
new TypeParametersCleanUp(values),
new HashCleanUp(values),
new ObjectsEqualsCleanUp(values),
@@ -88,6 +90,9 @@ public final class JavaFeatureTabPage extends AbstractCleanUpTabPage {
CheckboxPreference tryWithResource= createCheckboxPref(java1d7Group, numColumns, CleanUpMessages.JavaFeatureTabPage_CheckboxName_TryWithResource, CleanUpConstants.TRY_WITH_RESOURCE, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(tryWithResource);
+ CheckboxPreference multiCatch= createCheckboxPref(java1d7Group, numColumns, CleanUpMessages.JavaFeatureTabPage_CheckboxName_MultiCatch, CleanUpConstants.MULTI_CATCH, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(multiCatch);
+
CheckboxPreference typeArgs= createCheckboxPref(java1d7Group, numColumns, CleanUpMessages.JavaFeatureTabPage_CheckboxName_RedundantTypeArguments, CleanUpConstants.REMOVE_REDUNDANT_TYPE_ARGUMENTS, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(typeArgs);

Back to the top