1 /*******************************************************************************
2 * Copyright (c) 2011, 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
9 * Stephan Herrmann <stephan@cs.tu-berlin.de> - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
10 * IBM Corporation - bug fixes
11 *******************************************************************************/
12 package org.eclipse.jdt.internal.ui.fix;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
22 import org.eclipse.swt.graphics.Image;
24 import org.eclipse.jdt.core.ICompilationUnit;
25 import org.eclipse.jdt.core.IJavaElement;
26 import org.eclipse.jdt.core.JavaCore;
27 import org.eclipse.jdt.core.compiler.IProblem;
28 import org.eclipse.jdt.core.dom.ASTNode;
29 import org.eclipse.jdt.core.dom.CompilationUnit;
30 import org.eclipse.jdt.core.dom.IBinding;
31 import org.eclipse.jdt.core.dom.IVariableBinding;
32 import org.eclipse.jdt.core.dom.SimpleName;
33 import org.eclipse.jdt.core.dom.VariableDeclaration;
35 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
36 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
37 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
38 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
40 import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
41 import org.eclipse.jdt.ui.text.java.IInvocationContext;
42 import org.eclipse.jdt.ui.text.java.IProblemLocation;
43 import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
45 import org.eclipse.jdt.internal.ui.JavaPluginImages;
46 import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation;
47 import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
50 * Quick Fixes for null-annotation related problems.
52 public class NullQuickFixes {
54 /** Small adaptation just to make available the 'compilationUnit' passed at instantiation time. */
55 private static class MyCURewriteOperationsFix extends CompilationUnitRewriteOperationsFix {
58 public MyCURewriteOperationsFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] operations) {
59 super(name, compilationUnit, operations);
60 this.cu= compilationUnit;
64 public static void addReturnAndArgumentTypeProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
65 CompilationUnit astRoot= context.getASTRoot();
66 ASTNode selectedNode= problem.getCoveringNode(astRoot);
68 if (isComplainingAboutArgument(selectedNode) || isComplainingAboutReturn(selectedNode))
69 addNullAnnotationInSignatureProposal(context, problem, proposals, false);
72 public static void addNullAnnotationInSignatureProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals, boolean modifyOverridden) {
73 MyCURewriteOperationsFix fix= createNullAnnotationInSignatureFix(context.getASTRoot(), problem, modifyOverridden);
76 Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
77 Map<String, String> options= new Hashtable<String, String>();
78 if (fix.cu != context.getASTRoot()) {
79 // workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..)
80 final CompilationUnit cu= fix.cu;
81 final IInvocationContext originalContext= context;
82 context= new IInvocationContext() {
83 public int getSelectionOffset() {
84 return originalContext.getSelectionOffset();
87 public int getSelectionLength() {
88 return originalContext.getSelectionLength();
91 public ASTNode getCoveringNode() {
92 return originalContext.getCoveringNode();
95 public ASTNode getCoveredNode() {
96 return originalContext.getCoveredNode();
99 public ICompilationUnit getCompilationUnit() {
100 return (ICompilationUnit) cu.getJavaElement();
103 public CompilationUnit getASTRoot() {
108 int relevance= modifyOverridden ? 9 : 10; //raise local change above change in overridden method
109 FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), relevance, image, context);
110 proposals.add(proposal);
114 public static boolean isComplainingAboutArgument(ASTNode selectedNode) {
115 if (!(selectedNode instanceof SimpleName))
117 SimpleName nameNode= (SimpleName) selectedNode;
118 IBinding binding= nameNode.resolveBinding();
119 if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
121 VariableDeclaration argDecl= (VariableDeclaration) ASTNodes.getParent(selectedNode, VariableDeclaration.class);
123 binding= argDecl.resolveBinding();
124 if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
129 public static boolean isComplainingAboutReturn(ASTNode selectedNode) {
130 return selectedNode.getParent().getNodeType() == ASTNode.RETURN_STATEMENT;
133 private static MyCURewriteOperationsFix createNullAnnotationInSignatureFix(CompilationUnit compilationUnit, IProblemLocation problem, boolean modifyOverridden) {
134 String nullableAnnotationName= getNullableAnnotationName(compilationUnit.getJavaElement(), false);
135 String nonNullAnnotationName= getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
136 String annotationToAdd= nullableAnnotationName;
137 String annotationToRemove= nonNullAnnotationName;
139 switch (problem.getProblemId()) {
140 case IProblem.IllegalDefinitionToNonNullParameter:
141 case IProblem.IllegalRedefinitionToNonNullParameter:
142 // case ParameterLackingNullableAnnotation: // never proposed with modifyOverridden
143 if (modifyOverridden) {
144 annotationToAdd= nonNullAnnotationName;
145 annotationToRemove= nullableAnnotationName;
148 case IProblem.ParameterLackingNonNullAnnotation:
149 case IProblem.IllegalReturnNullityRedefinition:
150 if (!modifyOverridden) {
151 annotationToAdd= nonNullAnnotationName;
152 annotationToRemove= nullableAnnotationName;
155 case IProblem.RequiredNonNullButProvidedNull:
156 case IProblem.RequiredNonNullButProvidedPotentialNull:
157 case IProblem.RequiredNonNullButProvidedUnknown:
158 annotationToAdd= nonNullAnnotationName;
160 // all others propose to add @Nullable
163 // when performing one change at a time we can actually modify another CU than the current one:
164 NullRewriteOperations.SignatureAnnotationRewriteOperation operation= NullRewriteOperations.createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, null,
165 false/*thisUnitOnly*/, true/*allowRemove*/, modifyOverridden);
166 if (operation == null)
169 return new MyCURewriteOperationsFix(operation.getMessage(), operation.getCompilationUnit(), // note that this uses the findings from createAddAnnotationOperation(..)
170 new NullRewriteOperations.SignatureAnnotationRewriteOperation[] { operation });
173 // Entry for NullAnnotationsCleanup:
174 public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] locations, int problemID) {
175 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
176 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
179 List<CompilationUnitRewriteOperation> operations= new ArrayList<CompilationUnitRewriteOperation>();
180 if (locations == null) {
181 org.eclipse.jdt.core.compiler.IProblem[] problems= compilationUnit.getProblems();
182 locations= new IProblemLocation[problems.length];
183 for (int i= 0; i < problems.length; i++) {
184 if (problems[i].getID() == problemID)
185 locations[i]= new ProblemLocation(problems[i]);
189 createAddNullAnnotationOperations(compilationUnit, locations, operations);
190 if (operations.size() == 0)
192 CompilationUnitRewriteOperation[] operationsArray= operations.toArray(new CompilationUnitRewriteOperation[operations.size()]);
193 return new MyCURewriteOperationsFix(NullFixMessages.QuickFixes_add_annotation_change_name, compilationUnit, operationsArray);
196 private static void createAddNullAnnotationOperations(CompilationUnit compilationUnit, IProblemLocation[] locations, List<CompilationUnitRewriteOperation> result) {
197 String nullableAnnotationName= getNullableAnnotationName(compilationUnit.getJavaElement(), false);
198 String nonNullAnnotationName= getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
199 Set<String> handledPositions= new HashSet<String>();
200 for (int i= 0; i < locations.length; i++) {
201 IProblemLocation problem= locations[i];
203 continue; // problem was filtered out by createCleanUp()
204 String annotationToAdd= nullableAnnotationName;
205 String annotationToRemove= nonNullAnnotationName;
206 switch (problem.getProblemId()) {
207 case IProblem.IllegalDefinitionToNonNullParameter:
208 case IProblem.IllegalRedefinitionToNonNullParameter:
209 case IProblem.ParameterLackingNonNullAnnotation:
210 case IProblem.IllegalReturnNullityRedefinition:
211 annotationToAdd= nonNullAnnotationName;
212 annotationToRemove= nullableAnnotationName;
214 case IProblem.RequiredNonNullButProvidedNull:
215 case IProblem.RequiredNonNullButProvidedPotentialNull:
216 case IProblem.RequiredNonNullButProvidedUnknown:
217 annotationToAdd= nonNullAnnotationName;
219 // all others propose to add @Nullable
221 // when performing multiple changes we can only modify the one CU that the CleanUp infrastructure provides to the operation.
222 CompilationUnitRewriteOperation fix= NullRewriteOperations.createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions,
223 true/*thisUnitOnly*/, false/*allowRemove*/, false/*modifyOverridden*/);
229 // private static boolean isMissingNullAnnotationProblem(int id) {
230 // return id == IProblem.RequiredNonNullButProvidedNull || id == IProblem.RequiredNonNullButProvidedPotentialNull || id == IProblem.IllegalReturnNullityRedefinition
231 // || mayIndicateParameterNullcheck(id);
234 // private static boolean mayIndicateParameterNullcheck(int problemId) {
235 // return problemId == IProblem.NonNullLocalVariableComparisonYieldsFalse || problemId == IProblem.RedundantNullCheckOnNonNullLocalVariable;
238 public static boolean hasExplicitNullAnnotation(ICompilationUnit compilationUnit, int offset) {
239 // FIXME(SH): check for existing annotations disabled due to lack of precision:
240 // should distinguish what is actually annotated (return? param? which?)
242 // IJavaElement problemElement = compilationUnit.getElementAt(offset);
243 // if (problemElement.getElementType() == IJavaElement.METHOD) {
244 // IMethod method = (IMethod) problemElement;
245 // String nullable = getNullableAnnotationName(compilationUnit, true);
246 // String nonnull = getNonNullAnnotationName(compilationUnit, true);
247 // for (IAnnotation annotation : method.getAnnotations()) {
248 // if ( annotation.getElementName().equals(nonnull)
249 // || annotation.getElementName().equals(nullable))
253 // } catch (JavaModelException jme) {
259 public static String getNullableAnnotationName(IJavaElement javaElement, boolean makeSimple) {
260 String qualifiedName= javaElement.getJavaProject().getOption(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, true);
262 if (makeSimple && qualifiedName != null && (lastDot= qualifiedName.lastIndexOf('.')) != -1)
263 return qualifiedName.substring(lastDot + 1);
264 return qualifiedName;
267 public static String getNonNullAnnotationName(IJavaElement javaElement, boolean makeSimple) {
268 String qualifiedName= javaElement.getJavaProject().getOption(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, true);
270 if (makeSimple && qualifiedName != null && (lastDot= qualifiedName.lastIndexOf('.')) != -1)
271 return qualifiedName.substring(lastDot + 1);
272 return qualifiedName;