diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/JavaMethodContractUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/JavaMethodContractUtil.java index 72901ec078f9..a4586a76ce51 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/JavaMethodContractUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/JavaMethodContractUtil.java @@ -2,6 +2,9 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.InferredAnnotationsManagerImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiMethod; @@ -11,6 +14,7 @@ import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,6 +70,24 @@ public class JavaMethodContractUtil { return getContractInfo(method).isExplicit(); } + /** + * Creates a new {@link PsiAnnotation} describing the updated contract. Only contract clauses are updated; + * purity and mutation signature (if exist) are left as is. + * + * @param annotation original annotation to update + * @param contracts new contracts + * @return new {@link PsiAnnotation} object which describes updated contracts or null if no annotation is required to represent + * the target contracts (i.e. contracts is empty, method has no mutation signature and is not marked as pure). + */ + @Nullable + public static PsiAnnotation updateContract(PsiAnnotation annotation, List contracts) { + boolean pure = Boolean.TRUE.equals(AnnotationUtil.getBooleanAttributeValue(annotation, "pure")); + String mutates = StringUtil.notNullize(AnnotationUtil.getStringAttributeValue(annotation, MutationSignature.ATTR_MUTATES)); + String resultValue = StreamEx.of(contracts).joining("; "); + Project project = annotation.getProject(); + return InferredAnnotationsManagerImpl.createContractAnnotation(project, pure, resultValue, mutates); + } + static class ContractInfo { static final ContractInfo EMPTY = new ContractInfo(Collections.emptyList(), false, false, MutationSignature.UNKNOWN); diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/ContractConverter.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/ContractConverter.java index 8ee414984cf6..429371e5d43b 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/ContractConverter.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/ContractConverter.java @@ -2,13 +2,10 @@ package com.intellij.refactoring.changeSignature; import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.InferredAnnotationsManagerImpl; import com.intellij.codeInspection.dataFlow.ContractReturnValue; import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil; import com.intellij.codeInspection.dataFlow.MutationSignature; import com.intellij.codeInspection.dataFlow.StandardMethodContract; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiMethod; import one.util.streamex.IntStreamEx; @@ -62,11 +59,7 @@ public final class ContractConverter { result.add(convertContract(contract, newToOldIndex, oldToNewIndex, oldParameterNames)); } if (result.equals(contracts)) return annotation; - boolean pure = Boolean.TRUE.equals(AnnotationUtil.getBooleanAttributeValue(annotation, "pure")); - String mutates = StringUtil.notNullize(AnnotationUtil.getStringAttributeValue(annotation, MutationSignature.ATTR_MUTATES)); - String resultValue = StreamEx.of(result).joining("; "); - Project project = method.getProject(); - return InferredAnnotationsManagerImpl.createContractAnnotation(project, pure, resultValue, mutates); + return JavaMethodContractUtil.updateContract(annotation, result); } @NotNull diff --git a/java/java-impl/src/com/intellij/refactoring/invertBoolean/JavaInvertBooleanDelegate.java b/java/java-impl/src/com/intellij/refactoring/invertBoolean/JavaInvertBooleanDelegate.java index 22fd2d9885fb..f6fd29044298 100644 --- a/java/java-impl/src/com/intellij/refactoring/invertBoolean/JavaInvertBooleanDelegate.java +++ b/java/java-impl/src/com/intellij/refactoring/invertBoolean/JavaInvertBooleanDelegate.java @@ -15,7 +15,11 @@ */ package com.intellij.refactoring.invertBoolean; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.daemon.impl.RecursiveCallLineMarkerProvider; +import com.intellij.codeInspection.dataFlow.ContractReturnValue; +import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil; +import com.intellij.codeInspection.dataFlow.StandardMethodContract; import com.intellij.ide.util.SuperMethodWarningUtil; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.editor.Editor; @@ -30,12 +34,14 @@ import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.LambdaRefactoringUtil; import com.intellij.usageView.UsageInfo; import com.intellij.util.Query; -import java.util.HashSet; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.siyeh.ig.psiutils.BoolUtils; import org.jetbrains.annotations.NotNull; import java.util.Collection; +import java.util.HashSet; +import java.util.List; public class JavaInvertBooleanDelegate extends InvertBooleanDelegate { @Override @@ -199,6 +205,33 @@ public class JavaInvertBooleanDelegate extends InvertBooleanDelegate { if (initializer != null) { replaceWithNegatedExpression(initializer); } + } else if (element instanceof PsiMethod) { + updateContract((PsiMethod)element); + } + } + + private static void updateContract(@NotNull PsiMethod method) { + PsiAnnotation annotation = JavaMethodContractUtil.findContractAnnotation(method); + if (annotation == null || annotation.getOwner() != method.getModifierList()) return; + String text = AnnotationUtil.getStringAttributeValue(annotation, null); + if (text == null || text.trim().isEmpty()) return; + List contracts; + try { + contracts = StandardMethodContract.parseContract(text); + } + catch (StandardMethodContract.ParseException ignore) { + return; + } + List newContracts = ContainerUtil.map(contracts, contract -> { + ContractReturnValue value = contract.getReturnValue(); + return value instanceof ContractReturnValue.BooleanReturnValue ? + contract.withReturnValue(((ContractReturnValue.BooleanReturnValue)value).negate()) : + contract; + }); + if (newContracts.equals(contracts)) return; + PsiAnnotation newAnnotation = JavaMethodContractUtil.updateContract(annotation, newContracts); + if (newAnnotation != null) { + annotation.replace(newAnnotation); } } diff --git a/java/java-tests/testData/refactoring/invertBoolean/contractUpdate.java b/java/java-tests/testData/refactoring/invertBoolean/contractUpdate.java new file mode 100644 index 000000000000..7382c48bc106 --- /dev/null +++ b/java/java-tests/testData/refactoring/invertBoolean/contractUpdate.java @@ -0,0 +1,12 @@ +import org.jetbrains.annotations.Contract; + +class C { + @Contract(value = "!null, _ -> false; null, null -> false; null, !null -> true", pure = true) + boolean foo(String s, String s2) { + return s == null && s2 != null; + } + + void bar() { + foo(); + } +} diff --git a/java/java-tests/testData/refactoring/invertBoolean/contractUpdate_after.java b/java/java-tests/testData/refactoring/invertBoolean/contractUpdate_after.java new file mode 100644 index 000000000000..14456171eb0f --- /dev/null +++ b/java/java-tests/testData/refactoring/invertBoolean/contractUpdate_after.java @@ -0,0 +1,12 @@ +import org.jetbrains.annotations.Contract; + +class C { + @Contract(value = "!null, _ -> true; null, null -> true; null, !null -> false", pure = true) + boolean fooInverted(String s, String s2) { + return s != null || s2 == null; + } + + void bar() { + fooInverted(); + } +}