diff options
| author | Fabrice Tiercelin | 2020-12-22 17:38:01 +0000 |
|---|---|---|
| committer | Fabrice Tiercelin | 2020-12-22 17:38:01 +0000 |
| commit | 16051ece96111f64ce9bea65b133c80e5bc56b30 (patch) | |
| tree | 5d34911294c301006153ad5187b804d6c727d7ac | |
| parent | 6362683b50c2c0ecf9e431ab0ededc4ad41970d4 (diff) | |
| download | eclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.tar.gz eclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.tar.xz eclipse.jdt.ui-16051ece96111f64ce9bea65b133c80e5bc56b30.zip | |
Bug 569879 - [AutoRefactor immigration #52/146] [cleanup & saveaction]I20201227-1800I20201226-1800I20201226-0940I20201225-1800I20201225-0450I20201225-0130I20201224-1800I20201223-1800
Multi-catch
Change-Id: Iba9d8a728725278dc5f201db59fa8577fbeae4c8
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
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); |
