aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Herrmann2013-02-13 07:48:34 (EST)
committerDani Megert2013-02-13 07:48:34 (EST)
commit9f5838bea1419b66d88c7e62d7617e995b5aa58e (patch)
tree1aa6895208042e659be61f4c417c3def8a9c524f
parent44c9da3a02bf981e6321cc2044b1df465546f9c5 (diff)
downloadeclipse.jdt.ui-9f5838bea1419b66d88c7e62d7617e995b5aa58e.zip
eclipse.jdt.ui-9f5838bea1419b66d88c7e62d7617e995b5aa58e.tar.gz
eclipse.jdt.ui-9f5838bea1419b66d88c7e62d7617e995b5aa58e.tar.bz2
Fixed bug 398995: [quick fix] Extract field access to checked localv20130213-124834
variable
-rw-r--r--org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java514
-rw-r--r--org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java5
-rw-r--r--org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties5
-rw-r--r--org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java7
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java6
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/NullAnnotationsCorrectionProcessor.java60
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java11
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ExtractToNullCheckedLocalProposal.java345
8 files changed, 938 insertions, 15 deletions
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
index 9725b96..82699f2 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012 GK Software AG and others.
+ * Copyright (c) 2012, 2013 GK Software AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -106,6 +106,514 @@ public class NullAnnotationsQuickFixTest extends QuickFixTest {
JavaProjectHelper.clear(fJProject1, ProjectTestSetup.getDefaultClasspath());
}
+ // ==== Problem: dereferencing a @Nullable field
+ // ==== Fix: extract field access to a fresh local variable and add a null-check
+
+ // basic case
+ public void testExtractNullableField1() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" System.out.println(f.toUpperCase());\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" final String f2 = f;\n");
+ buf.append(" if (f2 != null) {\n");
+ buf.append(" System.out.println(f2.toUpperCase());\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // statement is not element of a block - need to create a new block - local name f2 already in use
+ public void testExtractNullableField2() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo(boolean b) {\n");
+ buf.append(" @SuppressWarnings(\"unused\") boolean f2 = false;\n");
+ buf.append(" if (b)\n");
+ buf.append(" System.out.println(f.toUpperCase());\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo(boolean b) {\n");
+ buf.append(" @SuppressWarnings(\"unused\") boolean f2 = false;\n");
+ buf.append(" if (b) {\n");
+ buf.append(" final String f3 = f;\n");
+ buf.append(" if (f3 != null) {\n");
+ buf.append(" System.out.println(f3.toUpperCase());\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // field name is part of a qualified field reference - inside a return statement (type: int)
+ public void testExtractNullableField3() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" int f;\n");
+ buf.append(" public int foo(E that) {\n");
+ buf.append(" return that.other.f;\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" int f;\n");
+ buf.append(" public int foo(E that) {\n");
+ buf.append(" final E other2 = that.other;\n");
+ buf.append(" if (other2 != null) {\n");
+ buf.append(" return other2.f;\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" return 0;\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // field name is part of a this-qualified field reference - inside a return statement (type: String)
+ public void testExtractNullableField4() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public String foo() {\n");
+ buf.append(" return this.other.f;\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public String foo() {\n");
+ buf.append(" final E other2 = this.other;\n");
+ buf.append(" if (other2 != null) {\n");
+ buf.append(" return other2.f;\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" return null;\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // field referenced inside the rhs of an assignment-as-expression
+ public void testExtractNullableField5() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" String lo;\n");
+ buf.append(" if ((lo = this.other.f) != null)\n");
+ buf.append(" System.out.println(lo);\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable E other;\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" String lo;\n");
+ buf.append(" final E other2 = this.other;\n");
+ buf.append(" if (other2 != null) {\n");
+ buf.append(" if ((lo = other2.f) != null)\n");
+ buf.append(" System.out.println(lo);\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // reference to field of array type - dereferenced by f[0] and f.length
+ public void testExtractNullableField6() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String[] f1;\n");
+ buf.append(" @Nullable String[] f2;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" System.out.println(f1[0]);\n");
+ buf.append(" System.out.println(f2.length);\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot, 2, 0); // get correction for first of two problems
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String[] f1;\n");
+ buf.append(" @Nullable String[] f2;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" final String[] f12 = f1;\n");
+ buf.append(" if (f12 != null) {\n");
+ buf.append(" System.out.println(f12[0]);\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" System.out.println(f2.length);\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposals= collectCorrections(cu, astRoot, 2, 1); // get correction for second of two problems
+ assertNumberOfProposals(proposals, 1);
+ proposal= (CUCorrectionProposal) proposals.get(0);
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String[] f1;\n");
+ buf.append(" @Nullable String[] f2;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" System.out.println(f1[0]);\n");
+ buf.append(" final String[] f22 = f2;\n");
+ buf.append(" if (f22 != null) {\n");
+ buf.append(" System.out.println(f22.length);\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // field has a generic type
+ public void testExtractNullableField7() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("import java.util.List;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable List<String> f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" System.out.println(f.size());\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("import java.util.List;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable List<String> f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" final List<String> f2 = f;\n");
+ buf.append(" if (f2 != null) {\n");
+ buf.append(" System.out.println(f2.size());\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // occurrences inside a class initializer
+ public void testExtractNullableField8() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable Exception e;\n");
+ buf.append(" {\n");
+ buf.append(" e.printStackTrace();\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable Exception e;\n");
+ buf.append(" {\n");
+ buf.append(" final Exception e2 = e;\n");
+ buf.append(" if (e2 != null) {\n");
+ buf.append(" e2.printStackTrace();\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // field reference inside a local variable initialization - ensure correct scoping of this local
+ public void testExtractNullableField9() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public String foo() {\n");
+ buf.append(" String upper = f.toUpperCase();\n");
+ buf.append(" System.out.println(upper);\n");
+ buf.append(" return upper;\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public String foo() {\n");
+ buf.append(" final String f2 = f;\n");
+ buf.append(" if (f2 != null) {\n");
+ buf.append(" String upper = f2.toUpperCase();\n");
+ buf.append(" System.out.println(upper);\n");
+ buf.append(" return upper;\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // ==== Problem: using a @Nullable or un-annotated field in assignment/return context expecting @NonNull
+ // ==== Fix: extract field access to a fresh local variable and add a null-check
+
+ // return situation, field reference is this.f
+ public void testExtractPotentiallyNullField1() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public @NonNull String foo() {\n");
+ buf.append(" return this.f;\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 2);
+ assertCorrectLabels(proposals);
+
+ // primary proposal: Extract to checked local variable
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public @NonNull String foo() {\n");
+ buf.append(" final String f2 = this.f;\n");
+ buf.append(" if (f2 != null) {\n");
+ buf.append(" return f2;\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" return null;\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+
+ // secondary proposal: Change return type of 'foo(..)' to '@Nullable'
+ proposal= (CUCorrectionProposal) proposals.get(1);
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public @Nullable String foo() {\n");
+ buf.append(" return this.f;\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ // message send argument situation, field reference is local.f
+ public void testExtractPotentiallyNullField2() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" E local = this;\n");
+ buf.append(" bar(local.f);\n");
+ buf.append(" }\n");
+ buf.append(" public void bar(@NonNull String s) { }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 1);
+ assertCorrectLabels(proposals);
+
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("public class E {\n");
+ buf.append(" @Nullable String f;\n");
+ buf.append(" public void foo() {\n");
+ buf.append(" E local = this;\n");
+ buf.append(" final String f2 = local.f;\n");
+ buf.append(" if (f2 != null) {\n");
+ buf.append(" bar(f2);\n");
+ buf.append(" } else {\n");
+ buf.append(" // TODO handle null value\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append(" public void bar(@NonNull String s) { }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+
// @Nullable argument is used where @NonNull is required -> change to @NonNull
public void testChangeParameter1a() throws Exception {
IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
@@ -154,8 +662,8 @@ public class NullAnnotationsQuickFixTest extends QuickFixTest {
CompilationUnit astRoot= getASTRoot(cu);
ArrayList proposals= collectCorrections(cu, astRoot);
- assertNumberOfProposals(proposals, 2); // other is add @SW
- CUCorrectionProposal proposal= (CUCorrectionProposal)proposals.get(0);
+ assertNumberOfProposals(proposals, 2); // other is add @SW - TODO: check when this is offered
+ CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
String preview= getPreviewContent(proposal);
buf= new StringBuffer();
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
index a87c8bc..66b3db3 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
@@ -125,6 +126,10 @@ public final class FixMessages extends NLS {
public static String NullAnnotationsRewriteOperations_change_overridden_return_nullness;
public static String NullAnnotationsRewriteOperations_remove_redundant_nullness_annotation;
+ public static String ExtractToNullCheckedLocalProposal_extractCheckedLocal_editName;
+ public static String ExtractToNullCheckedLocalProposal_extractToCheckedLocal_proposalName;
+ public static String ExtractToNullCheckedLocalProposal_todoHandleNullDescription;
+
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, FixMessages.class);
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
index 0429628..23e5f9e 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
@@ -7,6 +7,7 @@
#
# Contributors:
# IBM Corporation - initial API and implementation
+# Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
###############################################################################
CleanUpRefactoring_Refactoring_name=Clean Up
CleanUpRefactoring_Initialize_message=Checking preconditions for project ''{0}''
@@ -111,3 +112,7 @@ NullAnnotationsRewriteOperations_change_overridden_parameter_nullness=Change par
NullAnnotationsRewriteOperations_change_overridden_return_nullness=Change return type of overridden ''{0}(..)'' to ''@{1}''
NullAnnotationsRewriteOperations_remove_redundant_nullness_annotation=Remove redundant nullness annotation
+
+ExtractToNullCheckedLocalProposal_extractCheckedLocal_editName=Extract checked local
+ExtractToNullCheckedLocalProposal_extractToCheckedLocal_proposalName=Extract to checked local variable
+ExtractToNullCheckedLocalProposal_todoHandleNullDescription=handle null value
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
index 37e70e5..5e1037f 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
@@ -1,12 +1,12 @@
/*******************************************************************************
- * Copyright (c) 2011, 2012 GK Software AG and others.
+ * Copyright (c) 2011, 2013 GK Software AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
- * Stephan Herrmann <stephan@cs.tu-berlin.de> - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
+ * Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
@@ -151,9 +151,8 @@ public class NullAnnotationsFix extends CompilationUnitRewriteOperationsFix {
continue; // problem was filtered out by createCleanUp()
String annotationToAdd= nullableAnnotationName;
String annotationToRemove= nonNullAnnotationName;
+ // cf. createNullAnnotationInSignatureFix() but modifyOverridden is constantly false:
switch (problem.getProblemId()) {
- case IProblem.IllegalDefinitionToNonNullParameter:
- case IProblem.IllegalRedefinitionToNonNullParameter:
case IProblem.ParameterLackingNonNullAnnotation:
case IProblem.IllegalReturnNullityRedefinition:
annotationToAdd= nonNullAnnotationName;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java
index e082828..6c9fb03 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java
@@ -6,7 +6,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
- * Stephan Herrmann <stephan@cs.tu-berlin.de> - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
+ * Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.jdt.internal.ui.fix;
@@ -106,11 +106,11 @@ public class NullAnnotationsCleanUp extends AbstractMultiFix {
case IProblem.RequiredNonNullButProvidedPotentialNull:
case IProblem.RequiredNonNullButProvidedSpecdNullable:
case IProblem.RequiredNonNullButProvidedUnknown:
+ case IProblem.IllegalDefinitionToNonNullParameter:
+ case IProblem.IllegalRedefinitionToNonNullParameter:
case IProblem.ParameterLackingNullableAnnotation:
result.add(MultiFixMessages.NullAnnotationsCleanUp_add_nullable_annotation);
break;
- case IProblem.IllegalDefinitionToNonNullParameter:
- case IProblem.IllegalRedefinitionToNonNullParameter:
case IProblem.ParameterLackingNonNullAnnotation:
result.add(MultiFixMessages.NullAnnotationsCleanUp_add_nonnull_annotation);
break;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/NullAnnotationsCorrectionProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/NullAnnotationsCorrectionProcessor.java
index ecbb3a0..6dd3808 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/NullAnnotationsCorrectionProcessor.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/NullAnnotationsCorrectionProcessor.java
@@ -1,12 +1,12 @@
/*******************************************************************************
- * Copyright (c) 2011, 2012 GK Software AG and others.
+ * Copyright (c) 2011, 2013 GK Software AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
- * Stephan Herrmann <stephan@cs.tu-berlin.de> - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
+ * Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;
@@ -18,9 +18,18 @@ import java.util.Map;
import org.eclipse.swt.graphics.Image;
import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
-
+import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.core.dom.Initializer;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.SimpleName;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.NullAnnotationsFix;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
@@ -29,6 +38,7 @@ import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.fix.NullAnnotationsCleanUp;
+import org.eclipse.jdt.internal.ui.text.correction.proposals.ExtractToNullCheckedLocalProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
/**
@@ -98,4 +108,48 @@ public class NullAnnotationsCorrectionProcessor {
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), IProposalRelevance.REMOVE_REDUNDANT_NULLNESS_ANNOTATION, image, context);
proposals.add(proposal);
}
+
+ /**
+ * Fix for {@link IProblem#NullableFieldReference}
+ * @param context context
+ * @param problem problem to be fixed
+ * @param proposals accumulator for computed proposals
+ */
+ public static void addExtractCheckedLocalProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
+ CompilationUnit compilationUnit = context.getASTRoot();
+ ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
+
+ ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
+
+ SimpleName name= findProblemFieldName(selectedNode, problem.getProblemId());
+ if (name == null)
+ return;
+
+ ASTNode method= ASTNodes.getParent(selectedNode, MethodDeclaration.class);
+ if (method == null)
+ method= ASTNodes.getParent(selectedNode, Initializer.class);
+ if (method == null)
+ return;
+
+ proposals.add(new ExtractToNullCheckedLocalProposal(cu, compilationUnit, name, method));
+ }
+ private static SimpleName findProblemFieldName(ASTNode selectedNode, int problemID) {
+ // if a field access occurs in an compatibility situation (assignment/return/argument)
+ // with expected type declared @NonNull we first need to find the SimpleName inside:
+ if (selectedNode instanceof FieldAccess)
+ selectedNode= ((FieldAccess) selectedNode).getName();
+ else if (selectedNode instanceof QualifiedName)
+ selectedNode= ((QualifiedName) selectedNode).getName();
+
+ if (selectedNode instanceof SimpleName) {
+ SimpleName name= (SimpleName) selectedNode;
+ if (problemID == IProblem.NullableFieldReference)
+ return name;
+ // not field dereference, but compatibility issue - is value a field reference?
+ IBinding binding= name.resolveBinding();
+ if ((binding instanceof IVariableBinding) && ((IVariableBinding) binding).isField())
+ return name;
+ }
+ return null;
+ }
}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
index 50305fd..bdf4b7f 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
@@ -8,6 +8,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Benjamin Muskalla <b.muskalla@gmx.net> - [quick fix] Quick fix for missing synchronized modifier - https://bugs.eclipse.org/bugs/show_bug.cgi?id=245250
+ * Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;
@@ -251,6 +252,7 @@ public class QuickFixProcessor implements IQuickFixProcessor {
case IProblem.RedundantNullCheckOnNonNullLocalVariable:
case IProblem.RedundantNullAnnotation:
case IProblem.UnusedTypeParameter:
+ case IProblem.NullableFieldReference:
return true;
default:
return SuppressWarningsSubProcessor.hasSuppressWarningsProposal(cu.getJavaProject(), problemId);
@@ -689,14 +691,16 @@ public class QuickFixProcessor implements IQuickFixProcessor {
NullAnnotationsCorrectionProcessor.addNullAnnotationInSignatureProposal(context, problem, proposals, false, true);
NullAnnotationsCorrectionProcessor.addNullAnnotationInSignatureProposal(context, problem, proposals, true, true);
break;
+ case IProblem.RequiredNonNullButProvidedSpecdNullable:
+ case IProblem.RequiredNonNullButProvidedUnknown:
+ NullAnnotationsCorrectionProcessor.addExtractCheckedLocalProposal(context, problem, proposals);
+ //$FALL-THROUGH$
case IProblem.RequiredNonNullButProvidedNull:
case IProblem.RequiredNonNullButProvidedPotentialNull:
- case IProblem.RequiredNonNullButProvidedUnknown:
case IProblem.ParameterLackingNonNullAnnotation:
case IProblem.ParameterLackingNullableAnnotation:
case IProblem.NonNullLocalVariableComparisonYieldsFalse:
case IProblem.RedundantNullCheckOnNonNullLocalVariable:
- case IProblem.RequiredNonNullButProvidedSpecdNullable:
NullAnnotationsCorrectionProcessor.addReturnAndArgumentTypeProposal(context, problem, proposals);
break;
case IProblem.RedundantNullAnnotation:
@@ -708,6 +712,9 @@ public class QuickFixProcessor implements IQuickFixProcessor {
case IProblem.UnusedTypeParameter:
LocalCorrectionsSubProcessor.addUnusedTypeParameterProposal(context, problem, proposals);
break;
+ case IProblem.NullableFieldReference:
+ NullAnnotationsCorrectionProcessor.addExtractCheckedLocalProposal(context, problem, proposals);
+ break;
default:
}
if (JavaModelUtil.is50OrHigher(context.getCompilationUnit().getJavaProject())) {
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ExtractToNullCheckedLocalProposal.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ExtractToNullCheckedLocalProposal.java
new file mode 100644
index 0000000..d36e400
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ExtractToNullCheckedLocalProposal.java
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.text.correction.proposals;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.EmptyStatement;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IfStatement;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.Modifier;
+import org.eclipse.jdt.core.dom.ParameterizedType;
+import org.eclipse.jdt.core.dom.PrimitiveType;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.ReturnStatement;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.Type;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+
+import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
+import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
+import org.eclipse.jdt.internal.corext.fix.FixMessages;
+import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
+
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+
+
+/**
+ * Fix for field related null-issues:
+ * <ol>
+ * <li>{@link IProblem#NullableFieldReference}</li>
+ * <li>{@link IProblem#RequiredNonNullButProvidedSpecdNullable} <em>if relating to a field</em></li>
+ * <li>{@link IProblem#RequiredNonNullButProvidedUnknown} <em>if relating to a field</em></li>
+ * </ol>
+ * Extract the field reference to a fresh local variable.
+ * Add a null check for that local variable and move
+ * the dereference into the then-block of this null-check:
+ * <pre>
+ * {@code @Nullable Exception e;}
+ * void test() {
+ * e.printStackTrace();
+ * }</pre>
+ * will be converted to:
+ * <pre>
+ * {@code @Nullable Exception e;}
+ * void test() {
+ * final Exception e2 = e;
+ * if (e2 != null) {
+ * e2.printStackTrace();
+ * } else {
+ * // TODO handle null value
+ * }
+ * }</pre>
+ * <p>
+ * The <code>final</code> keyword is added to remind the user that writing
+ * to the local variable has no effect on the original field.</p>
+ * <p>Rrespects scoping if the problem occurs inside the initialization
+ * of a local variable (by moving statements into the new then block).</p>
+ *
+ * @since 3.9
+ */
+public class ExtractToNullCheckedLocalProposal extends LinkedCorrectionProposal {
+
+ private static final String LOCAL_NAME_POSITION_GROUP = "localName"; //$NON-NLS-1$
+
+ /** Protocol for rearranging the bits and pieces into a new code structure. */
+ private static abstract class RearrangeStrategy {
+
+ final Statement origStmt;
+ final Block block;
+ final TextEditGroup group;
+
+ RearrangeStrategy(Statement origStmt, Block block, TextEditGroup group) {
+ this.origStmt= origStmt;
+ this.block= block;
+ this.group= group;
+ }
+ /** Step 1 of the protocol: insert the new local variable.
+ * @param localDecl new local variable initialized with the original expression */
+ public abstract void insertLocalDecl(VariableDeclarationStatement localDecl);
+ /** Step 2 of the protocol: create a move target for repositioning the original enclosing statement.
+ * @return a move target representing the original statement */
+ public abstract Statement createMoveTargetForOrigStmt();
+ /** Step 3 of the protocol: integrate the new if statement into the existing structure.
+ * @param ifStmt the new if statement, fully created
+ * @param thenBlock the then statement of the given if statement (is a block by construction) */
+ public abstract void insertIfStatement(IfStatement ifStmt, Block thenBlock);
+
+ public static RearrangeStrategy create(Statement origStmt, ASTRewrite rewrite, TextEditGroup group) {
+ ASTNode parent = origStmt.getParent();
+ if (parent instanceof Block) {
+ Block block= (Block)parent;
+ if (origStmt instanceof VariableDeclarationStatement)
+ return new ModifyBlockWithLocalDecl(origStmt, block, rewrite, group);
+ else
+ return new ModifyBlock(origStmt, block, rewrite, group);
+ } else {
+ return new ReplaceStatement(origStmt, rewrite, group);
+ }
+ }
+
+ /** Strategy implementation for modifying statement list of the parent block. */
+ private static class ModifyBlock extends RearrangeStrategy {
+
+ final ListRewrite blockRewrite;
+
+ ModifyBlock(Statement origStmt, Block enclosingBlock, ASTRewrite rewrite, TextEditGroup group) {
+ super(origStmt, enclosingBlock, group);
+ // we're going to modify this block, create the rewrite for this task:
+ this.blockRewrite= rewrite.getListRewrite(enclosingBlock, Block.STATEMENTS_PROPERTY);
+ }
+ @Override
+ public void insertLocalDecl(VariableDeclarationStatement localDecl) {
+ this.blockRewrite.insertBefore(localDecl, this.origStmt, this.group);
+ }
+ @Override
+ public Statement createMoveTargetForOrigStmt() {
+ return (Statement) this.blockRewrite.createMoveTarget(this.origStmt, this.origStmt, null, this.group);
+ }
+ @Override
+ public void insertIfStatement(IfStatement ifStmt, Block thenBlock) {
+ // inside a block replace old statement with wrapping if-statement
+ this.blockRewrite.replace(this.origStmt, ifStmt, this.group);
+ }
+ }
+
+ /** Variant that also respects scoping of an existing local variable declaration. */
+ private static class ModifyBlockWithLocalDecl extends ModifyBlock {
+ ModifyBlockWithLocalDecl(Statement origStmt, Block enclosingBlock, ASTRewrite rewrite, TextEditGroup group) {
+ super(origStmt, enclosingBlock, rewrite, group);
+ }
+ @Override
+ public void insertIfStatement(IfStatement ifStmt, Block thenBlock) {
+ // when stmt declares a local variable (see RearrangeStrategy.create(..)) we need to move all
+ // subsequent statements into the then-block to ensure that the existing declared local is visible:
+ List<ASTNode> blockStmts= this.block.statements();
+ int stmtIdx= blockStmts.indexOf(this.origStmt);
+ int lastIdx= blockStmts.size()-1;
+ if (stmtIdx != -1 && stmtIdx < lastIdx) {
+ thenBlock.statements()
+ .add(this.blockRewrite.createMoveTarget(blockStmts.get(stmtIdx+1), blockStmts.get(lastIdx), null, this.group));
+ }
+ super.insertIfStatement(ifStmt, thenBlock);
+ }
+ }
+
+ /** Strategy implementation for replacing a single statement with a new block. */
+ private static class ReplaceStatement extends RearrangeStrategy {
+
+ final ASTRewrite rewrite;
+
+ ReplaceStatement(Statement origStmt, ASTRewrite rewrite, TextEditGroup group) {
+ // did not have a block, create one now to hold new statements:
+ super(origStmt, rewrite.getAST().newBlock(), group);
+ this.rewrite = rewrite;
+ }
+ @Override
+ public void insertLocalDecl(VariableDeclarationStatement localDecl) {
+ this.block.statements().add(localDecl);
+ }
+ @Override
+ public Statement createMoveTargetForOrigStmt() {
+ return (Statement) this.rewrite.createMoveTarget(this.origStmt); // group is unused
+ }
+ @Override
+ public void insertIfStatement(IfStatement ifStmt, Block thenBlock) {
+ // did not have a block: add if-statement to new block
+ this.block.statements().add(ifStmt);
+ // and replace the single statement with this block
+ this.rewrite.replace(this.origStmt, this.block, this.group);
+ }
+ }
+ }
+
+ private SimpleName fieldReference;
+ private CompilationUnit compilationUnit;
+ private ASTNode enclosingMethod; // MethodDeclaration or Initializer
+
+ public ExtractToNullCheckedLocalProposal(ICompilationUnit cu, CompilationUnit compilationUnit, SimpleName fieldReference, ASTNode enclosingMethod) {
+ super(FixMessages.ExtractToNullCheckedLocalProposal_extractToCheckedLocal_proposalName, cu, null, 100, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE));
+ this.compilationUnit= compilationUnit;
+ this.fieldReference= fieldReference;
+ this.enclosingMethod= enclosingMethod;
+ }
+
+ @Override
+ protected ASTRewrite getRewrite() throws CoreException {
+
+ // infrastructure:
+ AST ast= this.compilationUnit.getAST();
+ ASTRewrite rewrite= ASTRewrite.create(ast);
+ ImportRewrite imports= ImportRewrite.create(this.compilationUnit, true);
+ TextEditGroup group= new TextEditGroup(FixMessages.ExtractToNullCheckedLocalProposal_extractCheckedLocal_editName);
+ LinkedProposalPositionGroup localNameGroup= new LinkedProposalPositionGroup(LOCAL_NAME_POSITION_GROUP);
+ getLinkedProposalModel().addPositionGroup(localNameGroup);
+
+ // AST context:
+ Statement origStmt= (Statement) ASTNodes.getParent(this.fieldReference, Statement.class);
+ // determine suitable strategy for rearranging elements towards a new code structure:
+ RearrangeStrategy rearrangeStrategy= RearrangeStrategy.create(origStmt, rewrite, group);
+
+ Expression toReplace;
+ ASTNode directParent= this.fieldReference.getParent();
+ if (directParent instanceof FieldAccess) {
+ toReplace= (Expression) directParent;
+ } else if (directParent instanceof QualifiedName && this.fieldReference.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
+ toReplace= (Expression) directParent;
+ } else {
+ toReplace= this.fieldReference;
+ }
+
+ // new local declaration initialized from the field reference
+ VariableDeclarationFragment localFrag = ast.newVariableDeclarationFragment();
+ VariableDeclarationStatement localDecl= ast.newVariableDeclarationStatement(localFrag);
+ // ... type
+ localDecl.setType(newType(toReplace.resolveTypeBinding(), ast, imports));
+ localDecl.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
+ // ... name
+ String localName= proposeLocalName(this.fieldReference, this.compilationUnit, getCompilationUnit().getJavaProject());
+ localFrag.setName(ast.newSimpleName(localName));
+ // ... initialization
+ localFrag.setInitializer((Expression) ASTNode.copySubtree(ast, toReplace));
+
+ rearrangeStrategy.insertLocalDecl(localDecl);
+
+ // if statement:
+ IfStatement ifStmt= ast.newIfStatement();
+
+ // condition:
+ InfixExpression nullCheck= ast.newInfixExpression();
+ nullCheck.setLeftOperand(ast.newSimpleName(localName));
+ nullCheck.setRightOperand(ast.newNullLiteral());
+ nullCheck.setOperator(InfixExpression.Operator.NOT_EQUALS);
+ ifStmt.setExpression(nullCheck);
+
+ // then block: the original statement
+ Block thenBlock = ast.newBlock();
+ thenBlock.statements().add(rearrangeStrategy.createMoveTargetForOrigStmt());
+ ifStmt.setThenStatement(thenBlock);
+ // ... but with the field reference replaced by the new local:
+ SimpleName dereferencedName= ast.newSimpleName(localName);
+ rewrite.replace(toReplace, dereferencedName, group);
+
+
+ // else block: a Todo comment
+ Block elseBlock = ast.newBlock();
+ String elseStatement= "// TODO "+FixMessages.ExtractToNullCheckedLocalProposal_todoHandleNullDescription; //$NON-NLS-1$
+ if (origStmt instanceof ReturnStatement) {
+ Type returnType= newType(((ReturnStatement)origStmt).getExpression().resolveTypeBinding(), ast, imports);
+ ReturnStatement returnStatement= ast.newReturnStatement();
+ returnStatement.setExpression(ASTNodeFactory.newDefaultExpression(ast, returnType, 0));
+ elseStatement+= '\n'+ASTNodes.asFormattedString(returnStatement, 0, String.valueOf('\n'), getCompilationUnit().getJavaProject().getOptions(true));
+ }
+
+ EmptyStatement todoNode= (EmptyStatement) rewrite.createStringPlaceholder(elseStatement, ASTNode.EMPTY_STATEMENT);
+ elseBlock.statements().add(todoNode);
+ ifStmt.setElseStatement(elseBlock);
+
+ // link all three occurrences of the new local variable:
+ addLinkedPosition(rewrite.track(localFrag.getName()), true/*first*/, LOCAL_NAME_POSITION_GROUP);
+ addLinkedPosition(rewrite.track(nullCheck.getLeftOperand()), false, LOCAL_NAME_POSITION_GROUP);
+ addLinkedPosition(rewrite.track(dereferencedName), false, LOCAL_NAME_POSITION_GROUP);
+
+ rearrangeStrategy.insertIfStatement(ifStmt, thenBlock);
+
+ return rewrite;
+ }
+
+ String proposeLocalName(SimpleName fieldName, CompilationUnit root, IJavaProject javaProject) {
+ // don't propose names that are already in use:
+ Collection<String> variableNames= new ScopeAnalyzer(root).getUsedVariableNames(this.enclosingMethod.getStartPosition(), this.enclosingMethod.getLength());
+ String[] names = new String[variableNames.size()+1];
+ variableNames.toArray(names);
+ // don't propose the field name itself, either:
+ String identifier= fieldName.getIdentifier();
+ names[names.length-1] = identifier;
+ return StubUtility.getLocalNameSuggestions(javaProject, identifier, 0, names)[0];
+ }
+
+ /**
+ * Create a fresh type reference
+ * @param typeBinding the type we want to refer to
+ * @param ast AST for creating new nodes
+ * @param imports use this for optimal type names
+ * @return a fully features non-null type reference (can be parameterized and/or array).
+ */
+ public static Type newType(ITypeBinding typeBinding, AST ast, ImportRewrite imports) {
+ // unwrap array type:
+ int dimensions= typeBinding.getDimensions();
+ if (dimensions > 0)
+ typeBinding= typeBinding.getElementType();
+
+ // unwrap parameterized type:
+ ITypeBinding[] typeArguments= typeBinding.getTypeArguments();
+ typeBinding= typeBinding.getErasure();
+
+ // create leaf type:
+ Type elementType = (typeBinding.isPrimitive())
+ ? ast.newPrimitiveType(PrimitiveType.toCode(typeBinding.getName()))
+ : ast.newSimpleType(ast.newName(imports.addImport(typeBinding)));
+
+ // re-wrap as parameterized type:
+ if (typeArguments.length > 0) {
+ ParameterizedType parameterizedType= ast.newParameterizedType(elementType);
+ for (ITypeBinding typeArgument : typeArguments)
+ parameterizedType.typeArguments().add(newType(typeArgument, ast, imports));
+ elementType = parameterizedType;
+ }
+
+ // re-wrap as array type:
+ if (dimensions > 0)
+ return ast.newArrayType(elementType, dimensions);
+ else
+ return elementType;
+ }
+} \ No newline at end of file