From 250940c824c770f71df7eed8732ff4b627832c37 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 28 Nov 2014 14:37:51 +0300 Subject: [PATCH] Change Signature: Propagate nullability annotations to overriding methods in Java --- .../JetChangeSignatureUsageProcessor.java | 84 ++++++++++++++++++- ...JavaUsagesAndOverridesAddParamAfter.1.java | 27 ++++++ ...tionJavaUsagesAndOverridesAddParamAfter.kt | 7 ++ ...vaUsagesAndOverridesAddParamBefore.1.java} | 0 ...onJavaUsagesAndOverridesAddParamBefore.kt} | 0 ...FunctionJavaUsagesAndOverridesAfter.1.java | 22 ----- .../FunctionJavaUsagesAndOverridesAfter.kt | 7 -- ...sAndOverridesChangeNullabilityAfter.1.java | 26 ++++++ ...sagesAndOverridesChangeNullabilityAfter.kt | 3 + ...AndOverridesChangeNullabilityBefore.1.java | 25 ++++++ ...agesAndOverridesChangeNullabilityBefore.kt | 3 + ...aUsagesAndOverridesChangeTypesAfter.1.java | 10 +++ ...nJavaUsagesAndOverridesChangeTypesAfter.kt | 3 + ...UsagesAndOverridesChangeTypesBefore.1.java | 9 ++ ...JavaUsagesAndOverridesChangeTypesBefore.kt | 3 + .../JetChangeSignatureTest.java | 39 ++++++++- 16 files changed, 231 insertions(+), 37 deletions(-) create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.1.java create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.kt rename idea/testData/refactoring/changeSignature/{FunctionJavaUsagesAndOverridesBefore.1.java => FunctionJavaUsagesAndOverridesAddParamBefore.1.java} (100%) rename idea/testData/refactoring/changeSignature/{FunctionJavaUsagesAndOverridesBefore.kt => FunctionJavaUsagesAndOverridesAddParamBefore.kt} (100%) delete mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.1.java delete mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.kt create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.1.java create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.kt create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.1.java create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.kt create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.1.java create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.kt create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.1.java create mode 100644 idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.kt diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java b/idea/src/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java index 9f26ce851c7..0002e48ae1b 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java @@ -16,12 +16,11 @@ package org.jetbrains.jet.plugin.refactoring.changeSignature; +import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiReference; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.changeSignature.*; @@ -317,6 +316,75 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro return callElement != null ? new JavaMethodKotlinCallUsage(callElement, javaMethodChangeInfo) : null; } + private static class NullabilityPropagator { + private final NullableNotNullManager nullManager; + private final JavaPsiFacade javaPsiFacade; + private final JavaCodeStyleManager javaCodeStyleManager; + private final PsiAnnotation methodAnnotation; + private final PsiAnnotation[] parameterAnnotations; + + public NullabilityPropagator(@NotNull PsiMethod baseMethod) { + Project project = baseMethod.getProject(); + this.nullManager = NullableNotNullManager.getInstance(project); + this.javaPsiFacade = JavaPsiFacade.getInstance(project); + this.javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); + + this.methodAnnotation = getNullabilityAnnotation(baseMethod); + this.parameterAnnotations = ContainerUtil.map2Array( + baseMethod.getParameterList().getParameters(), + PsiAnnotation.class, + new Function() { + @Override + public PsiAnnotation fun(PsiParameter parameter) { + return getNullabilityAnnotation(parameter); + } + } + ); + } + + @Nullable + private PsiAnnotation getNullabilityAnnotation(@NotNull PsiModifierListOwner element) { + PsiAnnotation nullAnnotation = nullManager.getNullableAnnotation(element, false); + PsiAnnotation notNullAnnotation = nullManager.getNotNullAnnotation(element, false); + if ((nullAnnotation == null) == (notNullAnnotation == null)) return null; + return nullAnnotation != null ? nullAnnotation : notNullAnnotation; + } + + private void addNullabilityAnnotationIfApplicable(@NotNull PsiModifierListOwner element, @Nullable PsiAnnotation annotation) { + PsiAnnotation nullableAnnotation = nullManager.getNullableAnnotation(element, false); + PsiAnnotation notNullAnnotation = nullManager.getNotNullAnnotation(element, false); + + if (notNullAnnotation != null && nullableAnnotation == null && element instanceof PsiMethod) return; + + String annotationQualifiedName = annotation != null ? annotation.getQualifiedName() : null; + if (annotationQualifiedName != null + && javaPsiFacade.findClass(annotationQualifiedName, element.getResolveScope()) == null) return; + + if (notNullAnnotation != null) { + notNullAnnotation.delete(); + } + if (nullableAnnotation != null) { + nullableAnnotation.delete(); + } + + if (annotationQualifiedName == null) return; + + PsiModifierList modifierList = element.getModifierList(); + if (modifierList != null) { + modifierList.addAnnotation(annotationQualifiedName); + javaCodeStyleManager.shortenClassReferences(element); + } + } + + public void processMethod(@NotNull PsiMethod currentMethod) { + PsiParameter[] currentParameters = currentMethod.getParameterList().getParameters(); + addNullabilityAnnotationIfApplicable(currentMethod, methodAnnotation); + for (int i = 0; i < parameterAnnotations.length; i++) { + addNullabilityAnnotationIfApplicable(currentParameters[i], parameterAnnotations[i]); + } + } + } + @Override public boolean processUsage(ChangeInfo changeInfo, UsageInfo usageInfo, boolean beforeMethodChange, UsageInfo[] usages) { PsiElement method = changeInfo.getMethod(); @@ -328,6 +396,8 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro UsageInfo[] javaUsageInfos = ((KotlinWrapperForJavaUsageInfos) usageInfo).getJavaUsageInfos(); ChangeSignatureUsageProcessor[] processors = ChangeSignatureUsageProcessor.EP_NAME.getExtensions(); + NullabilityPropagator nullabilityPropagator = new NullabilityPropagator(javaChangeInfo.getMethod()); + for (UsageInfo usage : javaUsageInfos) { if (usage instanceof OverriderUsageInfo && beforeMethodChange) continue; for (ChangeSignatureUsageProcessor processor : processors) { @@ -337,6 +407,12 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro } if (processor.processUsage(javaChangeInfo, usage, beforeMethodChange, javaUsageInfos)) break; } + if (usage instanceof OverriderUsageInfo) { + PsiMethod overridingMethod = ((OverriderUsageInfo)usage).getElement(); + if (overridingMethod != null) { + nullabilityPropagator.processMethod(overridingMethod); + } + } } } diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.1.java new file mode 100644 index 00000000000..4a73ce9bb1d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.1.java @@ -0,0 +1,27 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class X extends A { + @NotNull + @Override + String foo(int n, @NotNull String s, @Nullable Object o) { + return ""; + } +} + +class Y extends X { + @NotNull + @Override + String foo(int n, @NotNull String s, @Nullable Object o) { + return super.foo(n, s, o); + } +} + +class Test { + void test() { + new A().foo(1, "abc", "def"); + new B().foo(2, "abc", "def"); + new X().foo(3, "abc", "def"); + new Y().foo(4, "abc", "def"); + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.kt new file mode 100644 index 00000000000..a0f6b5dcb7d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamAfter.kt @@ -0,0 +1,7 @@ +open class A { + open fun foo(n: Int, s: String, o: Any?): String = "" +} + +class B: A() { + override fun foo(n: Int, s: String, o: Any?): String = "" +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesBefore.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamBefore.1.java similarity index 100% rename from idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesBefore.1.java rename to idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamBefore.1.java diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesBefore.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamBefore.kt similarity index 100% rename from idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesBefore.kt rename to idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAddParamBefore.kt diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.1.java deleted file mode 100644 index 3a3110565e6..00000000000 --- a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.1.java +++ /dev/null @@ -1,22 +0,0 @@ -class X extends A { - @Override - String foo(int n, String s) { - return ""; - } -} - -class Y extends X { - @Override - String foo(int n, String s) { - return super.foo(n, s); - } -} - -class Test { - void test() { - new A().foo(1, "abc"); - new B().foo(2, "abc"); - new X().foo(3, "abc"); - new Y().foo(4, "abc"); - } -} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.kt deleted file mode 100644 index 6e7862604e8..00000000000 --- a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesAfter.kt +++ /dev/null @@ -1,7 +0,0 @@ -open class A { - open fun foo(n: Int, s: String): String = "" -} - -class B: A() { - override fun foo(n: Int, s: String): String = "" -} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.1.java new file mode 100644 index 00000000000..cdfddffcd58 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.1.java @@ -0,0 +1,26 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class X extends A { + @NotNull + @Override + String foo(int n, @Nullable String s, @NotNull Object o) { + return ""; + } +} + +class Y extends A { + @Nullable + @Override + String foo(int n, @Nullable String s, @NotNull Object o) { + return ""; + } +} + +class Z extends A { + @Nullable + @Override + String foo(int n, @Nullable String s, @NotNull Object o) { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.kt new file mode 100644 index 00000000000..ef22973f37b --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityAfter.kt @@ -0,0 +1,3 @@ +open class A { + open fun foo(n: Int, s: String?, o: Any): String? = "" +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.1.java new file mode 100644 index 00000000000..7a48cd71607 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.1.java @@ -0,0 +1,25 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class X extends A { + @NotNull + @Override + String foo(int n, @NotNull String s, @Nullable Object o) { + return ""; + } +} + +class Y extends A { + @Override + String foo(int n, String s, Object o) { + return ""; + } +} + +class Z extends A { + @Nullable + @Override + String foo(int n, @Nullable String s, @NotNull Object o) { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.kt new file mode 100644 index 00000000000..79ed19be4fb --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeNullabilityBefore.kt @@ -0,0 +1,3 @@ +open class A { + open fun foo(n: Int, s: String, o: Any?): String = "" +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.1.java new file mode 100644 index 00000000000..f44bb2c4f83 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.1.java @@ -0,0 +1,10 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class Y extends A { + @Nullable + @Override + Object foo(@Nullable String n, int s, @Nullable Long o) { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.kt new file mode 100644 index 00000000000..07344a819f9 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesAfter.kt @@ -0,0 +1,3 @@ +open class A { + open fun foo(n: String?, s: Int, o: Long?): Any? = "" +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.1.java b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.1.java new file mode 100644 index 00000000000..c5f7e27f3c5 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.1.java @@ -0,0 +1,9 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class Y extends A { + @Override + String foo(int n, String s, Object o) { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.kt b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.kt new file mode 100644 index 00000000000..79ed19be4fb --- /dev/null +++ b/idea/testData/refactoring/changeSignature/FunctionJavaUsagesAndOverridesChangeTypesBefore.kt @@ -0,0 +1,3 @@ +open class A { + open fun foo(n: Int, s: String, o: Any?): String = "" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureTest.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureTest.java index 085f11c6275..cdf97ea9803 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/changeSignature/JetChangeSignatureTest.java @@ -319,11 +319,42 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase { doTest(changeInfo); } - public void testFunctionJavaUsagesAndOverrides() throws Exception { + public void testFunctionJavaUsagesAndOverridesAddParam() throws Exception { JetChangeInfo changeInfo = getChangeInfo(); - JetParameterInfo newParameter = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType()); - newParameter.setDefaultValueText("\"abc\""); - changeInfo.addParameter(newParameter); + + JetParameterInfo param1 = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType()); + param1.setDefaultValueText("\"abc\""); + changeInfo.addParameter(param1); + + JetParameterInfo param2 = new JetParameterInfo("o", KotlinBuiltIns.getInstance().getNullableAnyType()); + param2.setDefaultValueText("\"def\""); + changeInfo.addParameter(param2); + + doTest(changeInfo); + } + + public void testFunctionJavaUsagesAndOverridesChangeNullability() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + + JetParameterInfo[] newParameters = changeInfo.getNewParameters(); + newParameters[1].setTypeText("String?"); + newParameters[2].setTypeText("Any"); + + changeInfo.setNewReturnTypeText("String?"); + + doTest(changeInfo); + } + + public void testFunctionJavaUsagesAndOverridesChangeTypes() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + + JetParameterInfo[] newParameters = changeInfo.getNewParameters(); + newParameters[0].setTypeText("String?"); + newParameters[1].setTypeText("Int"); + newParameters[2].setTypeText("Long?"); + + changeInfo.setNewReturnTypeText("Any?"); + doTest(changeInfo); }