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 1762179b6c4a..bf84641b598e 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 @@ -28,6 +28,7 @@ import com.intellij.psi.util.PropertyMemberType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; /** @@ -251,4 +252,9 @@ public abstract class QuickFixFactory { @Nullable public abstract List registerOrderEntryFixes(@NotNull QuickFixActionRegistrar registrar, @NotNull PsiReference reference); + + @NotNull + public abstract IntentionAction createAddMissingRequiredAnnotationParametersFix(@NotNull PsiAnnotation annotation, + @NotNull PsiMethod[] annotationMethods, + @NotNull Collection missedElements); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java index c445ceccb35a..2ddc2fb10d07 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java @@ -294,7 +294,11 @@ public class AnnotationsHighlightUtil { } String description = JavaErrorMessages.message("annotation.missing.attribute", buff); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description).create(); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix( + annotation, annotationMethods, missed)); + return info; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java new file mode 100644 index 000000000000..348fcdeda8e5 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2014 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.daemon.QuickFixBundle; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.template.TemplateBuilderImpl; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Comparator; +import java.util.SortedSet; +import java.util.TreeSet; + +/** +* @author Dmitry Batkovich +*/ +public class AddMissingRequiredAnnotationParametersFix implements IntentionAction { + private static final Logger LOG = Logger.getInstance(AddMissingRequiredAnnotationParametersFix.class); + + private final PsiAnnotation myAnnotation; + private final PsiMethod[] myAnnotationMethods; + private final Collection myMissedElements; + + public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation, + final PsiMethod[] annotationMethods, + final Collection missedElements) { + if (missedElements.isEmpty()) { + throw new IllegalArgumentException("missedElements can't be empty"); + } + myAnnotation = annotation; + myAnnotationMethods = annotationMethods; + myMissedElements = missedElements; + } + + @NotNull + @Override + public String getText() { + return myMissedElements.size() == 1 + ? QuickFixBundle.message("add.missing.annotation.single.parameter.fix", ContainerUtil.getFirstItem(myMissedElements)) + : QuickFixBundle.message("add.missing.annotation.parameters.fix", StringUtil.join(myMissedElements, ", ")); + } + + @NotNull + @Override + public String getFamilyName() { + return QuickFixBundle.message("annotations.fix"); + } + + @Override + public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { + return true; + } + + @Override + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + final PsiNameValuePair[] addedParameters = myAnnotation.getParameterList().getAttributes(); + + final TObjectIntHashMap annotationsOrderMap = getAnnotationsOrderMap(); + final SortedSet> + newParameters = new TreeSet>(new Comparator>() { + @Override + public int compare(final Pair o1, final Pair o2) { + return annotationsOrderMap.get(o1.getFirst()) - annotationsOrderMap.get(o2.getFirst()); + } + }); + final boolean order = isAlreadyAddedOrdered(annotationsOrderMap, addedParameters); + if (order) { + if (addedParameters.length != 0) { + final PsiAnnotationParameterList parameterList = myAnnotation.getParameterList(); + parameterList.deleteChildRange(addedParameters[0], addedParameters[addedParameters.length - 1]); + for (final PsiNameValuePair addedParameter : addedParameters) { + final String name = addedParameter.getName(); + final PsiAnnotationMemberValue value = addedParameter.getValue(); + if (name == null || value == null) { + LOG.error(String.format("Invalid annotation parameter name = %s, value = %s", name, value)); + continue; + } + newParameters.add(Pair.create(name, value)); + } + } + } + + final PsiExpression nullValue = JavaPsiFacade.getElementFactory(myAnnotation.getProject()).createExpressionFromText(PsiKeyword.NULL, null); + for (final String misssedParameter : myMissedElements) { + newParameters.add(Pair.create(misssedParameter, nullValue)); + } + + TemplateBuilderImpl builder = null; + for (final Pair newParameter : newParameters) { + final PsiAnnotationMemberValue value = + myAnnotation.setDeclaredAttributeValue(newParameter.getFirst(), newParameter.getSecond()); + if (myMissedElements.contains(newParameter.getFirst())) { + if (builder == null) { + builder = new TemplateBuilderImpl(myAnnotation.getParameterList()); + } + builder.replaceElement(value, new EmptyExpression(), true); + } + } + + editor.getCaretModel().moveToOffset(myAnnotation.getParameterList().getTextRange().getStartOffset()); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + final Document document = documentManager.getDocument(file); + if (document == null) { + throw new IllegalStateException(); + } + documentManager.doPostponedOperationsAndUnblockDocument(document); + TemplateManager.getInstance(project).startTemplate(editor, builder.buildInlineTemplate(), null); + } + + @Override + public boolean startInWriteAction() { + return true; + } + + private TObjectIntHashMap getAnnotationsOrderMap() { + final TObjectIntHashMap map = new TObjectIntHashMap(); + for (int i = 0; i < myAnnotationMethods.length; i++) { + map.put(myAnnotationMethods[i].getName(), i); + } + return map; + } + + private static boolean isAlreadyAddedOrdered(final TObjectIntHashMap orderMap, final PsiNameValuePair[] addedParameters) { + if (addedParameters.length <= 1) { + return true; + } + int previousOrder = orderMap.get(addedParameters[0].getName()); + for (int i = 1; i < addedParameters.length; i++) { + final int currentOrder = orderMap.get(addedParameters[i].getName()); + if (currentOrder < previousOrder) { + return false; + } + previousOrder = currentOrder; + } + return true; + } +} 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 a66a85802d56..8fb04b7f5c7f 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 @@ -61,6 +61,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -740,6 +741,14 @@ public class QuickFixFactoryImpl extends QuickFixFactory { }); } + @NotNull + @Override + public IntentionAction createAddMissingRequiredAnnotationParametersFix(@NotNull final PsiAnnotation annotation, + @NotNull final PsiMethod[] annotationMethods, + @NotNull final Collection missedElements) { + return new AddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missedElements); + } + private static boolean timeToOptimizeImports(@NotNull PsiFile file) { if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters.java new file mode 100644 index 000000000000..889b0a118252 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters.java @@ -0,0 +1,15 @@ +// "Add missing annotation parameters - value3, value2, value1" "true" +class Test { + + @MyAnnotation(value3 = , value2 = , value1 = ) + void m() { + + } + + @interface MyAnnotation { + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters2.java new file mode 100644 index 000000000000..67886a6bb979 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParameters2.java @@ -0,0 +1,16 @@ +// "Add missing annotation parameters - value4, value1" "true" +class Test { + + @MyAnnotation(value4 = , value3 = "", value2 = "", value1 = ) + void m() { + + } + + @interface MyAnnotation { + String value4(); + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParametersWithoutOrder.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParametersWithoutOrder.java new file mode 100644 index 000000000000..414eb2bf7f35 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterFewParametersWithoutOrder.java @@ -0,0 +1,16 @@ +// "Add missing annotation parameters - value4, value1" "true" +class Test { + + @MyAnnotation(value2 = "", value3 = "", value4 = , value1 = ) + void m() { + + } + + @interface MyAnnotation { + String value4(); + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterSingleParameter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterSingleParameter.java new file mode 100644 index 000000000000..14f71e51a151 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterSingleParameter.java @@ -0,0 +1,13 @@ +// "Add missing annotation parameter 'value'" "true" +class Test { + + @MyAnnotation() + void m() { + + } + + @interface MyAnnotation { + String value(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterValueTyping.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterValueTyping.java new file mode 100644 index 000000000000..d6efb38f0792 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/afterValueTyping.java @@ -0,0 +1,14 @@ +class Test { + + @MyAnnotation(value3 = "value33", value2 = "value22", value1 = "value11") + void m() { + + } + + @interface MyAnnotation { + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters.java new file mode 100644 index 000000000000..d8e6b5afbe7a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters.java @@ -0,0 +1,15 @@ +// "Add missing annotation parameters - value3, value2, value1" "true" +class Test { + + @MyAnnotation + void m() { + + } + + @interface MyAnnotation { + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters2.java new file mode 100644 index 000000000000..bb0ad03903bf --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParameters2.java @@ -0,0 +1,16 @@ +// "Add missing annotation parameters - value4, value1" "true" +class Test { + + @MyAnnotation(value3 = "", value2 = "") + void m() { + + } + + @interface MyAnnotation { + String value4(); + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParametersWithoutOrder.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParametersWithoutOrder.java new file mode 100644 index 000000000000..4f210601e287 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeFewParametersWithoutOrder.java @@ -0,0 +1,16 @@ +// "Add missing annotation parameters - value4, value1" "true" +class Test { + + @MyAnnotation(value2 = "", value3 = "") + void m() { + + } + + @interface MyAnnotation { + String value4(); + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeParameterWithDefaultValue.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeParameterWithDefaultValue.java new file mode 100644 index 000000000000..15862bc3f427 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeParameterWithDefaultValue.java @@ -0,0 +1,13 @@ +// "Add missing annotation parameter 'value'" "false" +class Test { + + @MyAnnotation + void m() { + + } + + @interface MyAnnotation { + String value() default ""; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeSingleParameter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeSingleParameter.java new file mode 100644 index 000000000000..a627372e6db8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeSingleParameter.java @@ -0,0 +1,13 @@ +// "Add missing annotation parameter 'value'" "true" +class Test { + + @MyAnnotation + void m() { + + } + + @interface MyAnnotation { + String value(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeValueTyping.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeValueTyping.java new file mode 100644 index 000000000000..139f523bff7a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters/beforeValueTyping.java @@ -0,0 +1,14 @@ +class Test { + + @MyAnnotation + void m() { + + } + + @interface MyAnnotation { + String value3(); + String value2(); + String value1(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddMissingRequiredAnnotationParametersTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddMissingRequiredAnnotationParametersTest.java new file mode 100644 index 000000000000..f6f50fff7b24 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddMissingRequiredAnnotationParametersTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 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.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; + +/** + * @author Dmitry Batkovich + */ +public class AddMissingRequiredAnnotationParametersTest extends LightQuickFixTestCase { + + private void doTest() { + doSingleTest(getTestName(false) + ".java"); + } + + public void testFewParameters() { + doTest(); + } + + public void testFewParameters2() { + doTest(); + } + + public void testFewParametersWithoutOrder() { + doTest(); + } + + public void testSingleParameter() { + doTest(); + } + + public void testParameterWithDefaultValue() { + doTest(); + } + + public void testValueTyping() { + configureByFile(getBasePath() + "/beforeValueTyping.java"); + TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable()); + doAction("Add missing annotation parameters - value3, value2, value1"); + final TemplateState state = TemplateManagerImpl.getTemplateState(getEditor()); + assertNotNull(state); + type("\"value33\""); + state.nextTab(); + type("\"value22\""); + state.nextTab(); + type("\"value11\""); + state.nextTab(); + assertTrue(state.isFinished()); + checkResultByFile(getBasePath() + "/afterValueTyping.java"); + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/addMissingRequiredAnnotationParameters"; + } +} diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index a35554c22872..9e94379d7892 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -278,3 +278,7 @@ add.qualifier.original.class.chooser.title=Original class wrap.array.to.arrays.as.list.parameter.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''Arrays.asList'' wrap.array.to.arrays.as.list.single.parameter.text=Wrap using ''Arrays.asList'' + +annotations.fix=Annotations +add.missing.annotation.parameters.fix=Add missing annotation parameters - {0} +add.missing.annotation.single.parameter.fix=Add missing annotation parameter ''{0}'' \ No newline at end of file