View | Details | Raw Unified | Return to bug 337977 | Differences between
and this patch

Collapse All | Expand All

(-)a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationQuickFixTest.java (+474 lines)
Line 0 Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2012 GK Software AG and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Stephan Herrmann - initial API and implementation
10
 *******************************************************************************/
11
package org.eclipse.jdt.ui.tests.quickfix;
12
13
import java.io.File;
14
import java.util.ArrayList;
15
import java.util.Hashtable;
16
17
import junit.framework.Test;
18
import junit.framework.TestSuite;
19
20
import org.eclipse.jdt.testplugin.JavaProjectHelper;
21
import org.eclipse.jdt.testplugin.TestOptions;
22
23
import org.eclipse.core.runtime.FileLocator;
24
import org.eclipse.core.runtime.Path;
25
import org.eclipse.core.runtime.Platform;
26
27
import org.eclipse.jface.preference.IPreferenceStore;
28
29
import org.eclipse.jdt.core.ICompilationUnit;
30
import org.eclipse.jdt.core.IJavaProject;
31
import org.eclipse.jdt.core.IPackageFragment;
32
import org.eclipse.jdt.core.IPackageFragmentRoot;
33
import org.eclipse.jdt.core.JavaCore;
34
import org.eclipse.jdt.core.dom.CompilationUnit;
35
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
36
37
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
38
import org.eclipse.jdt.internal.corext.template.java.CodeTemplateContextType;
39
40
import org.eclipse.jdt.ui.PreferenceConstants;
41
import org.eclipse.jdt.ui.tests.core.ProjectTestSetup;
42
import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal;
43
44
import org.eclipse.jdt.internal.ui.JavaPlugin;
45
46
public class NullAnnotationQuickFixTest extends QuickFixTest {
47
48
	private static final Class THIS= NullAnnotationQuickFixTest.class;
49
50
	private IJavaProject fJProject1;
51
	private IPackageFragmentRoot fSourceFolder;
52
53
	private String ANNOTATION_JAR_PATH;
54
55
56
	public NullAnnotationQuickFixTest(String name) {
57
		super(name);
58
	}
59
60
	public static Test suite() {
61
		return setUpTest(new TestSuite(THIS));
62
	}
63
64
	public static Test setUpTest(Test test) {
65
		return new ProjectTestSetup(test);
66
	}
67
68
69
	protected void setUp() throws Exception {
70
		Hashtable options= TestOptions.getDefaultOptions();
71
		options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
72
		options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4");
73
		options.put(DefaultCodeFormatterConstants.FORMATTER_NUMBER_OF_EMPTY_LINES_TO_PRESERVE, String.valueOf(99));
74
		options.put(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, JavaCore.ERROR);
75
		options.put(JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION, JavaCore.IGNORE);
76
		options.put(JavaCore.COMPILER_PB_MISSING_HASHCODE_METHOD, JavaCore.WARNING);
77
		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
78
		options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, JavaCore.ERROR);
79
		options.put(JavaCore.COMPILER_PB_NULL_REFERENCE, JavaCore.ERROR);
80
		options.put(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE, JavaCore.WARNING);
81
		options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, JavaCore.WARNING);
82
		options.put(JavaCore.COMPILER_PB_NULL_UNCHECKED_CONVERSION, JavaCore.WARNING);
83
		options.put(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.WARNING);
84
85
		JavaCore.setOptions(options);
86
87
		IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
88
		store.setValue(PreferenceConstants.CODEGEN_ADD_COMMENTS, false);
89
90
		StubUtility.setCodeTemplate(CodeTemplateContextType.CATCHBLOCK_ID, "", null);
91
		StubUtility.setCodeTemplate(CodeTemplateContextType.CONSTRUCTORSTUB_ID, "", null);
92
		StubUtility.setCodeTemplate(CodeTemplateContextType.METHODSTUB_ID, "", null);
93
94
		fJProject1= ProjectTestSetup.getProject();
95
		
96
		if (this.ANNOTATION_JAR_PATH == null) {
97
			File bundleFile = FileLocator.getBundleFile(Platform.getBundle("org.eclipse.jdt.annotation"));
98
			if (bundleFile.isDirectory())
99
				this.ANNOTATION_JAR_PATH = bundleFile.getPath()+"/bin";
100
			else
101
				this.ANNOTATION_JAR_PATH = bundleFile.getPath();
102
		}
103
		JavaProjectHelper.addLibrary(fJProject1, new Path(ANNOTATION_JAR_PATH));
104
105
106
		fSourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
107
	}
108
109
110
	protected void tearDown() throws Exception {
111
		JavaProjectHelper.clear(fJProject1, ProjectTestSetup.getDefaultClasspath());
112
	}
113
114
	// ==== Problem:	dereferencing a @Nullable field
115
	// ==== Fix:		extract field access to a fresh local variable and add a null-check
116
	
117
	// basic case
118
	public void testExtractNullableField1() throws Exception {
119
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
120
		StringBuffer buf= new StringBuffer();
121
		buf.append("package test1;\n");
122
		buf.append("import org.eclipse.jdt.annotation.*;\n");
123
		buf.append("public class E {\n");
124
		buf.append("    @Nullable String f;\n");
125
		buf.append("    public void foo() {\n");
126
		buf.append("        System.out.println(f.toUpperCase());\n");
127
		buf.append("    }\n");
128
		buf.append("}\n");
129
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
130
131
		CompilationUnit astRoot= getASTRoot(cu);
132
		ArrayList proposals= collectCorrections(cu, astRoot);
133
		assertNumberOfProposals(proposals, 1);
134
		assertCorrectLabels(proposals);
135
136
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
137
		String preview= getPreviewContent(proposal);
138
139
		buf= new StringBuffer();
140
		buf.append("package test1;\n");
141
		buf.append("import org.eclipse.jdt.annotation.*;\n");
142
		buf.append("public class E {\n");
143
		buf.append("    @Nullable String f;\n");
144
		buf.append("    public void foo() {\n");
145
		buf.append("        String f2 = f;\n");
146
		buf.append("        if (f2 != null) {\n");
147
		buf.append("            System.out.println(f2.toUpperCase());\n");
148
		buf.append("        } else {\n");
149
		buf.append("            // TODO handle null value\n");
150
		buf.append("        }\n");
151
		buf.append("    }\n");
152
		buf.append("}\n");
153
		assertEqualString(preview, buf.toString());
154
	}
155
156
	// statement is not element of a block - need to create a new block - local name f2 already in use
157
	public void testExtractNullableField2() throws Exception {
158
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
159
		StringBuffer buf= new StringBuffer();
160
		buf.append("package test1;\n");
161
		buf.append("import org.eclipse.jdt.annotation.*;\n");
162
		buf.append("public class E {\n");
163
		buf.append("    @Nullable String f;\n");
164
		buf.append("    public void foo(boolean b) {\n");
165
		buf.append("        @SuppressWarnings(\"unused\") boolean f2 = false;\n");
166
		buf.append("        if (b)\n");
167
		buf.append("          System.out.println(f.toUpperCase());\n");
168
		buf.append("    }\n");
169
		buf.append("}\n");
170
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
171
172
		CompilationUnit astRoot= getASTRoot(cu);
173
		ArrayList proposals= collectCorrections(cu, astRoot);
174
		assertNumberOfProposals(proposals, 1);
175
		assertCorrectLabels(proposals);
176
177
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
178
		String preview= getPreviewContent(proposal);
179
180
		buf= new StringBuffer();
181
		buf.append("package test1;\n");
182
		buf.append("import org.eclipse.jdt.annotation.*;\n");
183
		buf.append("public class E {\n");
184
		buf.append("    @Nullable String f;\n");
185
		buf.append("    public void foo(boolean b) {\n");
186
		buf.append("        @SuppressWarnings(\"unused\") boolean f2 = false;\n");
187
		buf.append("        if (b) {\n");
188
		buf.append("            String f3 = f;\n");
189
		buf.append("            if (f3 != null) {\n");
190
		buf.append("                System.out.println(f3.toUpperCase());\n");
191
		buf.append("            } else {\n");
192
		buf.append("                // TODO handle null value\n");
193
		buf.append("            }\n");
194
		buf.append("        }\n");
195
		buf.append("    }\n");
196
		buf.append("}\n");
197
		assertEqualString(preview, buf.toString());
198
	}
199
200
	// field name is part of a qualified field reference - inside a return statement (type: int)
201
	public void testExtractNullableField3() throws Exception {
202
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
203
		StringBuffer buf= new StringBuffer();
204
		buf.append("package test1;\n");
205
		buf.append("import org.eclipse.jdt.annotation.*;\n");
206
		buf.append("public class E {\n");
207
		buf.append("    @Nullable E other;\n");
208
		buf.append("    int f;\n");
209
		buf.append("    public int foo(E that) {\n");
210
		buf.append("        return that.other.f;\n");
211
		buf.append("    }\n");
212
		buf.append("}\n");
213
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
214
215
		CompilationUnit astRoot= getASTRoot(cu);
216
		ArrayList proposals= collectCorrections(cu, astRoot);
217
		assertNumberOfProposals(proposals, 1);
218
		assertCorrectLabels(proposals);
219
220
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
221
		String preview= getPreviewContent(proposal);
222
223
		buf= new StringBuffer();
224
		buf.append("package test1;\n");
225
		buf.append("import org.eclipse.jdt.annotation.*;\n");
226
		buf.append("public class E {\n");
227
		buf.append("    @Nullable E other;\n");
228
		buf.append("    int f;\n");
229
		buf.append("    public int foo(E that) {\n");
230
		buf.append("        E other2 = that.other;\n");
231
		buf.append("        if (other2 != null) {\n");
232
		buf.append("            return other2.f;\n");
233
		buf.append("        } else {\n");
234
		buf.append("            // TODO handle null value\n");
235
		buf.append("            return 0;\n");
236
		buf.append("        }\n");
237
		buf.append("    }\n");
238
		buf.append("}\n");
239
		assertEqualString(preview, buf.toString());
240
	}
241
242
	// field name is part of a this-qualified field reference - inside a return statement (type: String)
243
	public void testExtractNullableField4() throws Exception {
244
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
245
		StringBuffer buf= new StringBuffer();
246
		buf.append("package test1;\n");
247
		buf.append("import org.eclipse.jdt.annotation.*;\n");
248
		buf.append("public class E {\n");
249
		buf.append("    @Nullable E other;\n");
250
		buf.append("    @Nullable String f;\n");
251
		buf.append("    public String foo() {\n");
252
		buf.append("        return this.other.f;\n");
253
		buf.append("    }\n");
254
		buf.append("}\n");
255
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
256
257
		CompilationUnit astRoot= getASTRoot(cu);
258
		ArrayList proposals= collectCorrections(cu, astRoot);
259
		assertNumberOfProposals(proposals, 1);
260
		assertCorrectLabels(proposals);
261
262
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
263
		String preview= getPreviewContent(proposal);
264
265
		buf= new StringBuffer();
266
		buf.append("package test1;\n");
267
		buf.append("import org.eclipse.jdt.annotation.*;\n");
268
		buf.append("public class E {\n");
269
		buf.append("    @Nullable E other;\n");
270
		buf.append("    @Nullable String f;\n");
271
		buf.append("    public String foo() {\n");
272
		buf.append("        E other2 = this.other;\n");
273
		buf.append("        if (other2 != null) {\n");
274
		buf.append("            return other2.f;\n");
275
		buf.append("        } else {\n");
276
		buf.append("            // TODO handle null value\n");
277
		buf.append("            return null;\n");
278
		buf.append("        }\n");
279
		buf.append("    }\n");
280
		buf.append("}\n");
281
		assertEqualString(preview, buf.toString());
282
	}
283
284
	// field referenced inside the rhs of an assignment-as-expression
285
	public void testExtractNullableField5() throws Exception {
286
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
287
		StringBuffer buf= new StringBuffer();
288
		buf.append("package test1;\n");
289
		buf.append("import org.eclipse.jdt.annotation.*;\n");
290
		buf.append("public class E {\n");
291
		buf.append("    @Nullable E other;\n");
292
		buf.append("    @Nullable String f;\n");
293
		buf.append("    public void foo() {\n");
294
		buf.append("        String lo;\n");
295
		buf.append("        if ((lo = this.other.f) != null)\n");
296
		buf.append("            System.out.println(lo);\n");
297
		buf.append("    }\n");
298
		buf.append("}\n");
299
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
300
301
		CompilationUnit astRoot= getASTRoot(cu);
302
		ArrayList proposals= collectCorrections(cu, astRoot);
303
		assertNumberOfProposals(proposals, 1);
304
		assertCorrectLabels(proposals);
305
306
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
307
		String preview= getPreviewContent(proposal);
308
309
		buf= new StringBuffer();
310
		buf.append("package test1;\n");
311
		buf.append("import org.eclipse.jdt.annotation.*;\n");
312
		buf.append("public class E {\n");
313
		buf.append("    @Nullable E other;\n");
314
		buf.append("    @Nullable String f;\n");
315
		buf.append("    public void foo() {\n");
316
		buf.append("        String lo;\n");
317
		buf.append("        E other2 = this.other;\n");
318
		buf.append("        if (other2 != null) {\n");
319
		buf.append("            if ((lo = other2.f) != null)\n");
320
		buf.append("                System.out.println(lo);\n");
321
		buf.append("        } else {\n");
322
		buf.append("            // TODO handle null value\n");
323
		buf.append("        }\n");
324
		buf.append("    }\n");
325
		buf.append("}\n");
326
		assertEqualString(preview, buf.toString());
327
	}
328
329
	// reference to field of array type - dereferenced by f[0] and f.length
330
	public void testExtractNullableField6() throws Exception {
331
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
332
		StringBuffer buf= new StringBuffer();
333
		buf.append("package test1;\n");
334
		buf.append("import org.eclipse.jdt.annotation.*;\n");
335
		buf.append("public class E {\n");
336
		buf.append("    @Nullable String[] f1;\n");
337
		buf.append("    @Nullable String[] f2;\n");
338
		buf.append("    public void foo() {\n");
339
		buf.append("        System.out.println(f1[0]);\n");
340
		buf.append("        System.out.println(f2.length);\n");
341
		buf.append("    }\n");
342
		buf.append("}\n");
343
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
344
345
		CompilationUnit astRoot= getASTRoot(cu);
346
		ArrayList proposals= collectCorrections(cu, astRoot, 2, 0); // get correction for first of two problems
347
		assertNumberOfProposals(proposals, 1);
348
		assertCorrectLabels(proposals);
349
350
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
351
		String preview= getPreviewContent(proposal);
352
353
		buf= new StringBuffer();
354
		buf.append("package test1;\n");
355
		buf.append("import org.eclipse.jdt.annotation.*;\n");
356
		buf.append("public class E {\n");
357
		buf.append("    @Nullable String[] f1;\n");
358
		buf.append("    @Nullable String[] f2;\n");
359
		buf.append("    public void foo() {\n");
360
		buf.append("        String[] f12 = f1;\n");
361
		buf.append("        if (f12 != null) {\n");
362
		buf.append("            System.out.println(f12[0]);\n");
363
		buf.append("        } else {\n");
364
		buf.append("            // TODO handle null value\n");
365
		buf.append("        }\n");
366
		buf.append("        System.out.println(f2.length);\n");
367
		buf.append("    }\n");
368
		buf.append("}\n");
369
		assertEqualString(preview, buf.toString());
370
371
		proposals= collectCorrections(cu, astRoot, 2, 1); // get correction for second of two problems
372
		assertNumberOfProposals(proposals, 1);
373
		proposal= (CUCorrectionProposal) proposals.get(0);
374
		preview= getPreviewContent(proposal);
375
376
		buf= new StringBuffer();
377
		buf.append("package test1;\n");
378
		buf.append("import org.eclipse.jdt.annotation.*;\n");
379
		buf.append("public class E {\n");
380
		buf.append("    @Nullable String[] f1;\n");
381
		buf.append("    @Nullable String[] f2;\n");
382
		buf.append("    public void foo() {\n");
383
		buf.append("        System.out.println(f1[0]);\n");
384
		buf.append("        String[] f22 = f2;\n");
385
		buf.append("        if (f22 != null) {\n");
386
		buf.append("            System.out.println(f22.length);\n");
387
		buf.append("        } else {\n");
388
		buf.append("            // TODO handle null value\n");
389
		buf.append("        }\n");
390
		buf.append("    }\n");
391
		buf.append("}\n");
392
		assertEqualString(preview, buf.toString());
393
	}
394
395
	// field has a generic type
396
	public void testExtractNullableField7() throws Exception {
397
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
398
		StringBuffer buf= new StringBuffer();
399
		buf.append("package test1;\n");
400
		buf.append("import org.eclipse.jdt.annotation.*;\n");
401
		buf.append("import java.util.List;\n");
402
		buf.append("public class E {\n");
403
		buf.append("    @Nullable List<String> f;\n");
404
		buf.append("    public void foo() {\n");
405
		buf.append("        System.out.println(f.size());\n");
406
		buf.append("    }\n");
407
		buf.append("}\n");
408
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
409
410
		CompilationUnit astRoot= getASTRoot(cu);
411
		ArrayList proposals= collectCorrections(cu, astRoot);
412
		assertNumberOfProposals(proposals, 1);
413
		assertCorrectLabels(proposals);
414
415
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
416
		String preview= getPreviewContent(proposal);
417
418
		buf= new StringBuffer();
419
		buf.append("package test1;\n");
420
		buf.append("import org.eclipse.jdt.annotation.*;\n");
421
		buf.append("import java.util.List;\n");
422
		buf.append("public class E {\n");
423
		buf.append("    @Nullable List<String> f;\n");
424
		buf.append("    public void foo() {\n");
425
		buf.append("        List<String> f2 = f;\n");
426
		buf.append("        if (f2 != null) {\n");
427
		buf.append("            System.out.println(f2.size());\n");
428
		buf.append("        } else {\n");
429
		buf.append("            // TODO handle null value\n");
430
		buf.append("        }\n");
431
		buf.append("    }\n");
432
		buf.append("}\n");
433
		assertEqualString(preview, buf.toString());
434
	}
435
436
	// occurrences inside a class initializer
437
	public void testExtractNullableField8() throws Exception {
438
		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
439
		StringBuffer buf= new StringBuffer();
440
		buf.append("package test1;\n");
441
		buf.append("import org.eclipse.jdt.annotation.*;\n");
442
		buf.append("public class E {\n");
443
		buf.append("    @Nullable Exception e;\n");
444
		buf.append("    {\n");
445
		buf.append("        e.printStackTrace();\n");
446
		buf.append("    }\n");
447
		buf.append("}\n");
448
		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
449
450
		CompilationUnit astRoot= getASTRoot(cu);
451
		ArrayList proposals= collectCorrections(cu, astRoot);
452
		assertNumberOfProposals(proposals, 1);
453
		assertCorrectLabels(proposals);
454
455
		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
456
		String preview= getPreviewContent(proposal);
457
458
		buf= new StringBuffer();
459
		buf.append("package test1;\n");
460
		buf.append("import org.eclipse.jdt.annotation.*;\n");
461
		buf.append("public class E {\n");
462
		buf.append("    @Nullable Exception e;\n");
463
		buf.append("    {\n");
464
		buf.append("        Exception e2 = e;\n");
465
		buf.append("        if (e2 != null) {\n");
466
		buf.append("            e2.printStackTrace();\n");
467
		buf.append("        } else {\n");
468
		buf.append("            // TODO handle null value\n");
469
		buf.append("        }\n");
470
		buf.append("    }\n");
471
		buf.append("}\n");
472
		assertEqualString(preview, buf.toString());
473
	}
474
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ExtractToNullCheckedLocalProposal.java (+245 lines)
Line 0 Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2012 GK Software AG and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Stephan Herrmann - initial API and implementation
10
 *******************************************************************************/
11
package org.eclipse.jdt.internal.ui.fix;
12
13
import java.util.Collection;
14
15
import org.eclipse.core.runtime.CoreException;
16
17
import org.eclipse.text.edits.TextEditGroup;
18
19
import org.eclipse.jdt.core.ICompilationUnit;
20
import org.eclipse.jdt.core.IJavaProject;
21
import org.eclipse.jdt.core.compiler.IProblem;
22
import org.eclipse.jdt.core.dom.AST;
23
import org.eclipse.jdt.core.dom.ASTNode;
24
import org.eclipse.jdt.core.dom.Block;
25
import org.eclipse.jdt.core.dom.CompilationUnit;
26
import org.eclipse.jdt.core.dom.Expression;
27
import org.eclipse.jdt.core.dom.FieldAccess;
28
import org.eclipse.jdt.core.dom.ITypeBinding;
29
import org.eclipse.jdt.core.dom.IfStatement;
30
import org.eclipse.jdt.core.dom.InfixExpression;
31
import org.eclipse.jdt.core.dom.ParameterizedType;
32
import org.eclipse.jdt.core.dom.PrimitiveType;
33
import org.eclipse.jdt.core.dom.QualifiedName;
34
import org.eclipse.jdt.core.dom.ReturnStatement;
35
import org.eclipse.jdt.core.dom.SimpleName;
36
import org.eclipse.jdt.core.dom.Statement;
37
import org.eclipse.jdt.core.dom.Type;
38
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
39
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
40
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
41
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
42
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
43
44
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
45
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
46
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
47
import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
48
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
49
50
import org.eclipse.jdt.internal.ui.JavaPluginImages;
51
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal;
52
53
/**
54
 * Fix for {@link IProblem#NullableFieldReference}:
55
 * Extract the field reference to a fresh local variable.
56
 * Add a null check for that local variable and move
57
 * the dereference into the then-block of this null-check:
58
 * <pre>
59
 * {@code @Nullable Exception e;}
60
 * void test() {
61
 *     e.printStackTrace();
62
 * }</pre>
63
 * will be converted to:
64
 * <pre>
65
 * {@code @Nullable Exception e;}
66
 * void test() {
67
 *     Exception e2 = e;
68
 *     if (e2 != null) {
69
 *         e2.printStackTrace();
70
 *     } else {
71
 *         // TODO handle null value
72
 *     }
73
 * }</pre>
74
 * 
75
 * @since 3.9
76
 */
77
class ExtractToNullCheckedLocalProposal extends LinkedCorrectionProposal {
78
	
79
	private static final String LOCAL_NAME_POSITION_GROUP = "localName"; //$NON-NLS-1$
80
	
81
	private SimpleName fieldReference;
82
	private CompilationUnit compilationUnit;
83
	private ASTNode enclosingMethod; // MethodDeclaration or Initializer 
84
85
	public ExtractToNullCheckedLocalProposal(ICompilationUnit cu, CompilationUnit compilationUnit, SimpleName fieldReference, ASTNode enclosingMethod) {
86
		super(NullFixMessages.NullQuickFixes_extractToCheckedLocal_proposalName, cu, null, 100, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE));
87
		this.compilationUnit= compilationUnit;
88
		this.fieldReference= fieldReference;
89
		this.enclosingMethod= enclosingMethod;
90
	}
91
	
92
	@Override
93
	protected ASTRewrite getRewrite() throws CoreException {
94
		
95
		// infrastructure:
96
		AST ast= this.compilationUnit.getAST();
97
		ASTRewrite rewrite= ASTRewrite.create(ast);
98
		ImportRewrite imports= ImportRewrite.create(this.compilationUnit, true);
99
		TextEditGroup group= new TextEditGroup(NullFixMessages.NullQuickFixes_extractCheckedLocal_editName);
100
		LinkedProposalPositionGroup localNameGroup= new LinkedProposalPositionGroup(LOCAL_NAME_POSITION_GROUP);
101
		getLinkedProposalModel().addPositionGroup(localNameGroup);
102
103
		// AST context:
104
		Statement stmt= (Statement) ASTNodes.getParent(this.fieldReference, Statement.class);
105
		ASTNode parent= stmt.getParent();
106
		ListRewrite blockRewrite= null;
107
		Block block;
108
		if (parent instanceof Block) {
109
			// modifying statement list of the parent block
110
			block= (Block) parent;
111
			blockRewrite= rewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
112
		} else {
113
			// replacing statement with a new block
114
			block= ast.newBlock();			
115
		}
116
117
		Expression toReplace;
118
		ASTNode directParent= this.fieldReference.getParent();
119
		if (directParent instanceof FieldAccess) {
120
			toReplace= (Expression) directParent;
121
		} else if (directParent instanceof QualifiedName && this.fieldReference.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
122
			toReplace= (Expression) directParent;
123
		} else {
124
			toReplace= this.fieldReference;
125
		}
126
127
		// new local declaration initialized from the field reference
128
		VariableDeclarationFragment local = ast.newVariableDeclarationFragment();
129
		VariableDeclarationStatement localDecl= ast.newVariableDeclarationStatement(local);
130
		// ... type
131
		localDecl.setType(newType(toReplace.resolveTypeBinding(), ast, imports));
132
		// ... name
133
		String localName= proposeLocalName(this.fieldReference, this.compilationUnit, getCompilationUnit().getJavaProject());
134
		local.setName(ast.newSimpleName(localName));
135
		// ... initialization
136
		local.setInitializer((Expression) ASTNode.copySubtree(ast, toReplace));
137
		
138
		if (blockRewrite != null)
139
			blockRewrite.insertBefore(localDecl, stmt, group);
140
		else
141
			block.statements().add(localDecl);
142
		
143
		// prepare replacing stmt with a wrapper
144
		Statement statementToMove = (Statement) 
145
				(blockRewrite != null ? blockRewrite.createMoveTarget(stmt, stmt) : rewrite.createMoveTarget(stmt));
146
		
147
		// if statement:
148
		IfStatement ifStmt= ast.newIfStatement();
149
		
150
		// condition:
151
		InfixExpression nullCheck= ast.newInfixExpression();
152
		nullCheck.setLeftOperand(ast.newSimpleName(localName));
153
		nullCheck.setRightOperand(ast.newNullLiteral());
154
		nullCheck.setOperator(InfixExpression.Operator.NOT_EQUALS);
155
		ifStmt.setExpression(nullCheck);
156
		
157
		// then block: the original statement
158
		Block thenBlock = ast.newBlock();
159
		thenBlock.statements().add(statementToMove);
160
		ifStmt.setThenStatement(thenBlock);
161
		// ... but with the field reference replaced by the new local:
162
		SimpleName dereferencedName= ast.newSimpleName(localName);
163
		rewrite.replace(toReplace, dereferencedName, group);
164
165
		
166
		// else block: a TODO comment
167
		Block elseBlock = ast.newBlock();
168
		String elseStatement= "// TODO "+NullFixMessages.NullQuickFixes_todoHandleNullDescription; //$NON-NLS-1$
169
		if (stmt instanceof ReturnStatement) {
170
			Type returnType= newType(((ReturnStatement)stmt).getExpression().resolveTypeBinding(), ast, imports);
171
			ReturnStatement returnStatement= ast.newReturnStatement();
172
			returnStatement.setExpression(ASTNodeFactory.newDefaultExpression(ast, returnType, 0));
173
			elseStatement+= '\n'+ASTNodes.asFormattedString(returnStatement, 0, String.valueOf('\n'), getCompilationUnit().getJavaProject().getOptions(true));
174
		}
175
176
		ReturnStatement todoNode= (ReturnStatement) rewrite.createStringPlaceholder(elseStatement, ASTNode.RETURN_STATEMENT);
177
		elseBlock.statements().add(todoNode);
178
		ifStmt.setElseStatement(elseBlock);
179
		
180
		// link all three occurrences of the new local variable:
181
		addLinkedPosition(rewrite.track(local.getName()), true/*first*/, LOCAL_NAME_POSITION_GROUP);
182
		addLinkedPosition(rewrite.track(nullCheck.getLeftOperand()), false, LOCAL_NAME_POSITION_GROUP);
183
		addLinkedPosition(rewrite.track(dereferencedName), false, LOCAL_NAME_POSITION_GROUP);
184
185
		if (blockRewrite != null) {
186
			// inside a block replace old statement with wrapping if-statement
187
			blockRewrite.replace(stmt, ifStmt, group);
188
		} else {
189
			// did not have a block: add if-statement to new block
190
			block.statements().add(ifStmt);
191
			// and replace the single statement with this block
192
			rewrite.replace(stmt, block, group);
193
		}
194
		
195
		return rewrite;
196
	}
197
198
	String proposeLocalName(SimpleName fieldName, CompilationUnit root, IJavaProject javaProject) {
199
		// don't propose names that are already in use:
200
		Collection<String> variableNames= new ScopeAnalyzer(root).getUsedVariableNames(this.enclosingMethod.getStartPosition(), this.enclosingMethod.getLength());
201
		String[] names = new String[variableNames.size()+1];
202
		variableNames.toArray(names);
203
		// don't propose the field name itself, either:
204
		String identifier= fieldName.getIdentifier();
205
		names[names.length-1] = identifier;
206
		return StubUtility.getLocalNameSuggestions(javaProject, identifier, 0, names)[0];
207
	}
208
209
	/** 
210
	 * Create a fresh type reference
211
	 * @param typeBinding the type we want to refer to
212
	 * @param ast AST for creating new nodes
213
	 * @param imports use this for optimal type names
214
	 * @return a fully features non-null type reference (can be parameterized and/or array).
215
	 */
216
	public static Type newType(ITypeBinding typeBinding, AST ast, ImportRewrite imports) {
217
		// unwrap array type:
218
		int dimensions= typeBinding.getDimensions();
219
		if (dimensions > 0)
220
			typeBinding= typeBinding.getElementType();
221
		
222
		// unwrap parameterized type:
223
		ITypeBinding[] typeArguments= typeBinding.getTypeArguments();
224
		typeBinding= typeBinding.getErasure();	
225
226
		// create leaf type:
227
		Type elementType = (typeBinding.isPrimitive())
228
					? ast.newPrimitiveType(PrimitiveType.toCode(typeBinding.getName()))
229
					: ast.newSimpleType(ast.newName(imports.addImport(typeBinding)));
230
231
		// re-wrap as parameterized type:
232
		if (typeArguments.length > 0) {
233
			ParameterizedType parameterizedType= ast.newParameterizedType(elementType);
234
			for (ITypeBinding typeArgument : typeArguments)
235
				parameterizedType.typeArguments().add(newType(typeArgument, ast, imports));
236
			elementType = parameterizedType;
237
		}
238
		
239
		// re-wrap as array type:
240
		if (dimensions > 0)
241
			return ast.newArrayType(elementType, dimensions);
242
		else
243
			return elementType;
244
	}
245
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullFixMessages.java (+5 lines)
Lines 20-25 public class NullFixMessages extends NLS { Link Here
20
	public static String NullAnnotationsCleanUp_add_nullable_annotation;
20
	public static String NullAnnotationsCleanUp_add_nullable_annotation;
21
	public static String NullAnnotationsCleanUp_add_nonnull_annotation;
21
	public static String NullAnnotationsCleanUp_add_nonnull_annotation;
22
	
22
	
23
	public static String NullQuickFixes_extractCheckedLocal_editName;
24
	public static String NullQuickFixes_extractToCheckedLocal_proposalName;
25
	
26
	public static String NullQuickFixes_todoHandleNullDescription;
27
23
	public static String QuickFixes_add_annotation_change_name;
28
	public static String QuickFixes_add_annotation_change_name;
24
	public static String QuickFixes_change_method_parameter_nullness;
29
	public static String QuickFixes_change_method_parameter_nullness;
25
	public static String QuickFixes_change_method_return_nullness;
30
	public static String QuickFixes_change_method_return_nullness;
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullFixMessages.properties (+4 lines)
Lines 11-16 Link Here
11
###############################################################################
11
###############################################################################
12
NullAnnotationsCleanUp_add_nullable_annotation=Add missing @Nullable annotation
12
NullAnnotationsCleanUp_add_nullable_annotation=Add missing @Nullable annotation
13
NullAnnotationsCleanUp_add_nonnull_annotation=Add missing @NonNull annotation
13
NullAnnotationsCleanUp_add_nonnull_annotation=Add missing @NonNull annotation
14
NullQuickFixes_extractCheckedLocal_editName=Extract checked local
15
NullQuickFixes_extractToCheckedLocal_proposalName=Extract to checked local variable
16
NullQuickFixes_todoHandleNullDescription=handle null value
17
14
QuickFixes_add_annotation_change_name=Add Annotations
18
QuickFixes_add_annotation_change_name=Add Annotations
15
QuickFixes_change_method_parameter_nullness=Change parameter type to ''@{0}''
19
QuickFixes_change_method_parameter_nullness=Change parameter type to ''@{0}''
16
QuickFixes_change_method_return_nullness=Change return type of ''{0}(..)'' to ''@{1}''
20
QuickFixes_change_method_return_nullness=Change return type of ''{0}(..)'' to ''@{1}''
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullQuickFixes.java (+22 lines)
Lines 29-34 import org.eclipse.jdt.core.dom.ASTNode; Link Here
29
import org.eclipse.jdt.core.dom.CompilationUnit;
29
import org.eclipse.jdt.core.dom.CompilationUnit;
30
import org.eclipse.jdt.core.dom.IBinding;
30
import org.eclipse.jdt.core.dom.IBinding;
31
import org.eclipse.jdt.core.dom.IVariableBinding;
31
import org.eclipse.jdt.core.dom.IVariableBinding;
32
import org.eclipse.jdt.core.dom.Initializer;
33
import org.eclipse.jdt.core.dom.MethodDeclaration;
32
import org.eclipse.jdt.core.dom.SimpleName;
34
import org.eclipse.jdt.core.dom.SimpleName;
33
import org.eclipse.jdt.core.dom.VariableDeclaration;
35
import org.eclipse.jdt.core.dom.VariableDeclaration;
34
36
Lines 271-274 public class NullQuickFixes { Link Here
271
			return qualifiedName.substring(lastDot + 1);
273
			return qualifiedName.substring(lastDot + 1);
272
		return qualifiedName;
274
		return qualifiedName;
273
	}
275
	}
276
277
	/**
278
	 * Fix for {@link IProblem#NullableFieldReference}
279
	 * @param context context
280
	 * @param problem problem to be fixed
281
	 * @param proposals accumulator for computed proposals
282
	 */
283
	public static void addExtractCheckedLocalProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
284
		CompilationUnit compilationUnit = context.getASTRoot();
285
		ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
286
287
		ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
288
		if (selectedNode instanceof SimpleName) {
289
			ASTNode method= ASTNodes.getParent(selectedNode, MethodDeclaration.class);
290
			if (method == null)
291
				method= ASTNodes.getParent(selectedNode, Initializer.class);
292
			if (method != null)
293
				proposals.add(new ExtractToNullCheckedLocalProposal(cu, compilationUnit, (SimpleName) selectedNode, method));
294
		}
295
	}
274
}
296
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java (+4 lines)
Lines 246-251 public class QuickFixProcessor implements IQuickFixProcessor { Link Here
246
			case IProblem.ParameterLackingNullableAnnotation:
246
			case IProblem.ParameterLackingNullableAnnotation:
247
			case IProblem.NonNullLocalVariableComparisonYieldsFalse:
247
			case IProblem.NonNullLocalVariableComparisonYieldsFalse:
248
			case IProblem.RedundantNullCheckOnNonNullLocalVariable:
248
			case IProblem.RedundantNullCheckOnNonNullLocalVariable:
249
			case IProblem.NullableFieldReference:
249
				return true;
250
				return true;
250
			default:
251
			default:
251
				return SuppressWarningsSubProcessor.hasSuppressWarningsProposal(cu.getJavaProject(), problemId);
252
				return SuppressWarningsSubProcessor.hasSuppressWarningsProposal(cu.getJavaProject(), problemId);
Lines 689-694 public class QuickFixProcessor implements IQuickFixProcessor { Link Here
689
			case IProblem.RedundantNullCheckOnNonNullLocalVariable:
690
			case IProblem.RedundantNullCheckOnNonNullLocalVariable:
690
				NullQuickFixes.addReturnAndArgumentTypeProposal(context, problem, proposals);
691
				NullQuickFixes.addReturnAndArgumentTypeProposal(context, problem, proposals);
691
				break;
692
				break;
693
			case IProblem.NullableFieldReference:
694
				NullQuickFixes.addExtractCheckedLocalProposal(context, problem, proposals);
695
				break;
692
			default:
696
			default:
693
		}
697
		}
694
		if (JavaModelUtil.is50OrHigher(context.getCompilationUnit().getJavaProject())) {
698
		if (JavaModelUtil.is50OrHigher(context.getCompilationUnit().getJavaProject())) {

Return to bug 337977