diff --git a/java/java-analysis-impl/src/messages/QuickFixBundle.properties b/java/java-analysis-impl/src/messages/QuickFixBundle.properties index d0af5648aaff..46dc74384e11 100644 --- a/java/java-analysis-impl/src/messages/QuickFixBundle.properties +++ b/java/java-analysis-impl/src/messages/QuickFixBundle.properties @@ -13,6 +13,7 @@ add.exception.to.throws.inherited.method.warning.text=Method ''{0}'' is inherite method.is.inherited.warning.title=Method Is Inherited add.exception.to.throws.text=Add {0, choice, 0#exception|2#exceptions} to method signature add.exception.to.throws.family=Add exception to method signature +add.exception.to.existing.catch.family=Add exception to existing catch clause add.method.body.text=Add method body add.method.family=Add Method add.method.text=Add Method ''{0}'' to Class ''{1}'' diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 36b6c51d7a74..ada72a53920d 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1156,5 +1156,8 @@ com.intellij.codeInsight.intention.impl.SplitFilterAction Java/Streams + + com.intellij.codeInsight.intention.impl.AddExceptionToExistingCatchAction + \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddExceptionToExistingCatchAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddExceptionToExistingCatchAction.java new file mode 100644 index 000000000000..54a213f6486b --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddExceptionToExistingCatchAction.java @@ -0,0 +1,122 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.intention.impl; + +import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.ui.components.JBList; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class AddExceptionToExistingCatchAction extends PsiElementBaseIntentionAction { + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { + PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class); + if (tryStatement == null) return; + PsiCatchSection[] catchSections = tryStatement.getCatchSections(); + if (catchSections.length == 0) return; + List unhandledExceptions = new ArrayList<>(ExceptionUtil.getOwnUnhandledExceptions(element)); + if (unhandledExceptions.size() != 1) return; + List catchTexts = getAvailableCatchSections(catchSections) + .map(s -> s.getCatchType()) + .filter(Objects::nonNull) + .map(type -> type.getPresentableText()) + .collect(Collectors.toList()); + + Application application = ApplicationManager.getApplication(); + if (catchSections.length == 1 || application.isUnitTestMode()) { + PsiCatchSection selectedSection = catchSections[0]; + addTypeToCatch(unhandledExceptions.get(0), selectedSection); + } + else { + JBList list = new JBList<>(catchTexts); + JBPopupFactory.getInstance().createListPopupBuilder(list) + .setTitle("Select catch block") + .setMovable(false) + .setResizable(false) + .setRequestFocus(true) + .setItemChoosenCallback(() -> { + int selectedIndex = list.getSelectedIndex(); + PsiCatchSection selectedSection = catchSections[selectedIndex]; + addTypeToCatch(unhandledExceptions.get(0), selectedSection); + }) + .createPopup() + .showInBestPositionFor(editor); + } + } + + @NotNull + private static Stream getAvailableCatchSections(PsiCatchSection[] catchSections) { + return Arrays.stream(catchSections) + .filter(catchSection -> { + PsiParameter parameter = catchSection.getParameter(); + if (parameter == null) return false; + return parameter.getTypeElement() != null; + }); + } + + private static void addTypeToCatch(@NotNull PsiClassType exceptionToAdd, @NotNull PsiCatchSection catchSection) { + WriteCommandAction.runWriteCommandAction(catchSection.getProject(), () -> { + if (!catchSection.isValid() || !exceptionToAdd.isValid()) return; + PsiParameter parameter = catchSection.getParameter(); + if (parameter == null) return; + PsiTypeElement typeElement = parameter.getTypeElement(); + if (typeElement == null) return; + PsiType parameterType = parameter.getType(); + boolean needReplace = exceptionToAdd.isAssignableFrom(parameterType); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(catchSection.getProject()); + String typeText = needReplace ? exceptionToAdd.getCanonicalText() + : parameterType.getCanonicalText() + " | " + exceptionToAdd.getCanonicalText(); + typeElement.replace(factory.createTypeElementFromText(typeText, parameter)); + }); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class); + if (tryStatement == null) return false; + PsiCatchSection[] catchSections = tryStatement.getCatchSections(); + if (catchSections.length == 0) return false; + if (notFinishedCatches(catchSections)) return false; + PsiElement parent = PsiTreeUtil.getParentOfType(element, PsiCallExpression.class, PsiThrowStatement.class); + if (parent == null) return false; + List unhandledExceptions = new ArrayList<>(ExceptionUtil.collectUnhandledExceptions(tryStatement.getParent(), parent)); + return !unhandledExceptions.isEmpty(); + } + + private static boolean notFinishedCatches(PsiCatchSection[] catchSections) { + return getAvailableCatchSections(catchSections) + .map(catchSection -> catchSection.getParameter()) + .noneMatch(parameter -> parameter != null && parameter.getTypeElement() != null); + } + + + @Nls + @NotNull + @Override + public String getFamilyName() { + return QuickFixBundle.message("add.exception.to.existing.catch.family"); + } + + @NotNull + @Override + public String getText() { + return getFamilyName(); + } +} diff --git a/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/after.java.template b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/after.java.template new file mode 100644 index 000000000000..62c5a478d26d --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/after.java.template @@ -0,0 +1,13 @@ +class A extends Exception {} +class B extends A {} +class C extends A {} + +class Test { + public static void main(String[] args) { + try { + throw new A(); + } catch (A e) { + } catch (C e) { + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/before.java.template b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/before.java.template new file mode 100644 index 000000000000..8e6b97c3e4b3 --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/before.java.template @@ -0,0 +1,13 @@ +class A extends Exception {} +class B extends A {} +class C extends A {} + +class Test { + public static void main(String[] args) { + try { + throw new A(); + } catch (B e) { + } catch (C e) { + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/description.html b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/description.html new file mode 100644 index 000000000000..39607fce2352 --- /dev/null +++ b/java/java-impl/src/intentionDescriptions/AddExceptionToExistingCatchAction/description.html @@ -0,0 +1,6 @@ + + +

Intention to add exception to existing catch clause

+ + + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterReplace.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterReplace.java new file mode 100644 index 000000000000..18215ff69151 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterReplace.java @@ -0,0 +1,16 @@ +// "Add exception to existing catch clause" "true" +import java.io.IOException; + +class A extends Exception {} +class B extends A {} +class C extends A {} + +class Test { + public static void main(String[] args) { + try { + throw new A(); + } catch (A e) { + } catch (C e) { + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterSimple.java new file mode 100644 index 000000000000..8db6762d32ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterSimple.java @@ -0,0 +1,11 @@ +// "Add exception to existing catch clause" "true" +import java.io.IOException; + +class Test { + public static void main(String[] args) { + try { + throw new IOException(); + } catch (IndexOutOfBoundsException | IOException e) { + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeReplace.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeReplace.java new file mode 100644 index 000000000000..07fd991f4f64 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeReplace.java @@ -0,0 +1,16 @@ +// "Add exception to existing catch clause" "true" +import java.io.IOException; + +class A extends Exception {} +class B extends A {} +class C extends A {} + +class Test { + public static void main(String[] args) { + try { + throw new A(); + } catch (B e) { + } catch (C e) { + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeSimple.java new file mode 100644 index 000000000000..dc22faae4118 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeSimple.java @@ -0,0 +1,11 @@ +// "Add exception to existing catch clause" "true" +import java.io.IOException; + +class Test { + public static void main(String[] args) { + try { + throw new IOException(); + } catch (IndexOutOfBoundsException e) { + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/AddExceptionToExistingCatchTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/AddExceptionToExistingCatchTest.java new file mode 100644 index 000000000000..eddbeb80acee --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/AddExceptionToExistingCatchTest.java @@ -0,0 +1,16 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.LightIntentionActionTestCase; + +public class AddExceptionToExistingCatchTest extends LightIntentionActionTestCase { + public void test() { doAllTests(); } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch"; + } + + +} +