From 6afbed569ab0f5e20bfd7c70240386934319cc08 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Fri, 13 Nov 2015 15:58:42 +0300 Subject: [PATCH] quick fix which adds exception from field initializer to constructor signature (IDEA-142284) --- .../intention/QuickFixFactory.java | 2 + .../daemon/impl/analysis/HighlightUtil.java | 1 + .../intention/EmptyQuickFixFactory.java | 6 + ...ieldInitializerToConstructorThrowsFix.java | 114 ++++++++++++++++++ .../quickfix/AddExceptionToThrowsFix.java | 2 +- .../impl/config/QuickFixFactoryImpl.java | 6 + .../afterMultipleConstructors.java | 23 ++++ .../afterNoConstructor.java | 10 ++ .../afterSingleConstructor.java | 11 ++ .../beforeMultipleConstructors.java | 23 ++++ .../beforeNoConstructor.java | 7 ++ .../beforeNotShown1.java | 15 +++ .../beforeNotShown2.java | 10 ++ .../beforeNotShown3.java | 11 ++ .../beforeSingleConstructor.java | 10 ++ ...eldInitializerToConstructorThrowsTest.java | 38 ++++++ .../src/messages/QuickFixBundle.properties | 5 +- 17 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionFromFieldInitializerToConstructorThrowsFix.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterMultipleConstructors.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterNoConstructor.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterSingleConstructor.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeMultipleConstructors.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNoConstructor.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown1.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown3.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeSingleConstructor.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddExceptionFromFieldInitializerToConstructorThrowsTest.java diff --git a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java index 5085f1ba8914..90165ab91308 100644 --- a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java +++ b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java @@ -98,6 +98,8 @@ public abstract class QuickFixFactory { @NotNull public abstract IntentionAction createAddExceptionToThrowsFix(@NotNull PsiElement element); + @NotNull public abstract IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@NotNull PsiElement element); + @NotNull public abstract IntentionAction createSurroundWithTryCatchFix(@NotNull PsiElement element); @NotNull public abstract IntentionAction createGeneralizeCatchFix(@NotNull PsiElement element, @NotNull PsiClassType type); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 45b01e75ca56..589e91ba9f90 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -799,6 +799,7 @@ public class HighlightUtil extends HighlightUtilBase { private static void registerUnhandledExceptionFixes(PsiElement element, HighlightInfo errorResult, List unhandled) { QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToCatchFix()); QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToThrowsFix(element)); + QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionFromFieldInitializerToConstructorThrowsFix(element)); QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSurroundWithTryCatchFix(element)); if (unhandled.size() == 1) { QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createGeneralizeCatchFix(element, unhandled.get(0))); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/EmptyQuickFixFactory.java b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/EmptyQuickFixFactory.java index cb9807f0ee7a..20202061254a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/EmptyQuickFixFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/EmptyQuickFixFactory.java @@ -167,6 +167,12 @@ public class EmptyQuickFixFactory extends QuickFixFactory { return QuickFixes.EMPTY_FIX; } + @NotNull + @Override + public IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@NotNull PsiElement element) { + return QuickFixes.EMPTY_FIX; + } + @NotNull @Override public IntentionAction createSurroundWithTryCatchFix(@NotNull PsiElement psiElement) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionFromFieldInitializerToConstructorThrowsFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionFromFieldInitializerToConstructorThrowsFix.java new file mode 100644 index 000000000000..1bac4a9539fd --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionFromFieldInitializerToConstructorThrowsFix.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import gnu.trove.THashSet; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +public class AddExceptionFromFieldInitializerToConstructorThrowsFix extends BaseIntentionAction { + private final static Logger LOG = Logger.getInstance(AddExceptionFromFieldInitializerToConstructorThrowsFix.class); + + private final PsiElement myWrongElement; + + public AddExceptionFromFieldInitializerToConstructorThrowsFix(PsiElement element) { + myWrongElement = element; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!myWrongElement.isValid()) return false; + final NavigatablePsiElement maybeField = + PsiTreeUtil.getParentOfType(myWrongElement, PsiMethod.class, PsiFunctionalExpression.class, PsiField.class); + if (!(maybeField instanceof PsiField)) return false; + final PsiField field = (PsiField)maybeField; + if (field.hasModifierProperty(PsiModifier.STATIC)) return false; + final PsiClass containingClass = field.getContainingClass(); + if ((containingClass == null || + containingClass instanceof PsiAnonymousClass || + containingClass.isInterface() || + !containingClass.isWritable())) { + return false; + } + final List exceptions = ExceptionUtil.getUnhandledExceptions(field); + if (exceptions.isEmpty()) { + return false; + } + final PsiMethod[] existedConstructors = containingClass.getConstructors(); + setText(QuickFixBundle.message("add.exception.from.field.initializer.to.constructor.throws.text", existedConstructors.length)); + return true; + } + + @Override + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + final NavigatablePsiElement field = + PsiTreeUtil.getParentOfType(myWrongElement, PsiMethod.class, PsiFunctionalExpression.class, PsiField.class); + if (field instanceof PsiField) { + final PsiClass aClass = ((PsiField)field).getContainingClass(); + if (aClass != null) { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) { + final AddDefaultConstructorFix defaultConstructorFix = new AddDefaultConstructorFix(aClass); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + defaultConstructorFix.invoke(project, null, file); + } + }); + constructors = aClass.getConstructors(); + LOG.assertTrue(constructors.length != 0); + } + + Set unhandledExceptions = new THashSet(ExceptionUtil.getUnhandledExceptions(field)); + for (PsiMethod constructor : constructors) { + AddExceptionToThrowsFix.addExceptionsToThrowsList(project, constructor, unhandledExceptions); + } + } + } + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return QuickFixBundle.message("add.exception.from.field.initializer.to.constructor.throws.family.text"); + } + + @Override + public boolean startInWriteAction() { + return false; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToThrowsFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToThrowsFix.java index 4cc11715e09e..84f332d4a4e6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToThrowsFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToThrowsFix.java @@ -168,7 +168,7 @@ public class AddExceptionToThrowsFix extends BaseIntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { if (!(file instanceof PsiJavaFile)) return false; - if (myWrongElement == null || !myWrongElement.isValid()) return false; + if (!myWrongElement.isValid()) return false; final List unhandled = new ArrayList(); if (collectExceptions(unhandled) == null) return false; diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java index 5ee97347c570..8ad7b778a35f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java @@ -220,6 +220,12 @@ public class QuickFixFactoryImpl extends QuickFixFactory { return new AddExceptionToThrowsFix(element); } + @NotNull + @Override + public IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@NotNull PsiElement element) { + return new AddExceptionFromFieldInitializerToConstructorThrowsFix(element); + } + @NotNull @Override public IntentionAction createSurroundWithTryCatchFix(@NotNull PsiElement element) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterMultipleConstructors.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterMultipleConstructors.java new file mode 100644 index 000000000000..39a4b549af3b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterMultipleConstructors.java @@ -0,0 +1,23 @@ +// "Add exception to class constructors signature" "true" +import java.io.FileWriter; +import java.io.IOException; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C(int i) throws RuntimeException, IOException { + + } + + C(int i, int j) throws IOException { + + } + + C(int i, int j, int k) throws Exception { + + } + + C(int i, int j, int k, int l) throws IOException { + + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterNoConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterNoConstructor.java new file mode 100644 index 000000000000..a606e5b018f9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterNoConstructor.java @@ -0,0 +1,10 @@ +// "Add exception to class default constructor signature" "true" +import java.io.FileWriter; +import java.io.IOException; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C() throws IOException { + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterSingleConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterSingleConstructor.java new file mode 100644 index 000000000000..2158bef1bf3b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/afterSingleConstructor.java @@ -0,0 +1,11 @@ +// "Add exception to class constructor signature" "true" +import java.io.FileWriter; +import java.io.IOException; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C(int i) throws RuntimeException, IOException { + + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeMultipleConstructors.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeMultipleConstructors.java new file mode 100644 index 000000000000..b6b8627e69aa --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeMultipleConstructors.java @@ -0,0 +1,23 @@ +// "Add exception to class constructors signature" "true" +import java.io.FileWriter; +import java.io.IOException; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C(int i) throws RuntimeException { + + } + + C(int i, int j) throws IOException { + + } + + C(int i, int j, int k) throws Exception { + + } + + C(int i, int j, int k, int l) { + + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNoConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNoConstructor.java new file mode 100644 index 000000000000..5cbe2c3dcbc1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNoConstructor.java @@ -0,0 +1,7 @@ +// "Add exception to class default constructor signature" "true" +import java.io.FileWriter; + +class C { + final FileWriter fw = new FileWriter("asd"); + +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown1.java new file mode 100644 index 000000000000..36b1f3b24391 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown1.java @@ -0,0 +1,15 @@ +// "Add exception to class default constructor signature" "false" +import java.io.FileWriter; + +class C { + final FileWriter fw = new MyCallable() { + @Override + public FileWriter get() { + return new FileWriter(""); + } + }.get(); + + interface MyCallable { + FileWriter get(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown2.java new file mode 100644 index 000000000000..45ffb67fda8a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown2.java @@ -0,0 +1,10 @@ +// "Add exception to class default constructor signature" "false" +import java.io.FileWriter; + +class C { + final FileWriter fw = ((MyCallable) () -> new FileWriter("")).get(); + + interface MyCallable { + FileWriter get(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown3.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown3.java new file mode 100644 index 000000000000..d7166ff1b1c7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeNotShown3.java @@ -0,0 +1,11 @@ +// "Add exception to class constructor signature" "false" +import java.io.FileWriter; +import java.io.IOException; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C() throws IOException { + + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeSingleConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeSingleConstructor.java new file mode 100644 index 000000000000..41f3e11f9f55 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows/beforeSingleConstructor.java @@ -0,0 +1,10 @@ +// "Add exception to class constructor signature" "true" +import java.io.FileWriter; + +class C { + final FileWriter fw = new FileWriter("asd"); + + C(int i) throws RuntimeException { + + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddExceptionFromFieldInitializerToConstructorThrowsTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddExceptionFromFieldInitializerToConstructorThrowsTest.java new file mode 100644 index 000000000000..aa50430c9664 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddExceptionFromFieldInitializerToConstructorThrowsTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.daemon.quickFix; + +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.testFramework.IdeaTestUtil; + +/** + * @author Dmitry Batkovich + */ +public class AddExceptionFromFieldInitializerToConstructorThrowsTest extends LightQuickFixParameterizedTestCase { + public void test() throws Exception { + doAllTests(); + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/addToConstructorThrows"; + } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk18(); + } +} diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index 23c7786fc1a2..11532f4d3d79 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -274,4 +274,7 @@ collection.addall.can.be.replaced.with.constructor.fix.title=Replace 'addAll()' wrap.long.with.math.to.int.text=Wrap using 'Math.toIntExact()' wrap.long.with.math.to.int.parameter.single.text=Wrap parameter using 'Math.toIntExact()' -wrap.long.with.math.to.int.parameter.multiple.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''Math.toIntExact()'' \ No newline at end of file +wrap.long.with.math.to.int.parameter.multiple.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''Math.toIntExact()'' + +add.exception.from.field.initializer.to.constructor.throws.text=Add exception to class {0, choice, 0#default constructor|1#constructor|2#constructors} signature +add.exception.from.field.initializer.to.constructor.throws.family.text=Add exception to class constructors signature \ No newline at end of file