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.List;
17 import org.eclipse.core.runtime.CoreException;
19 import org.eclipse.text.edits.TextEditGroup;
21 import org.eclipse.jdt.core.ICompilationUnit;
22 import org.eclipse.jdt.core.JavaModelException;
23 import org.eclipse.jdt.core.compiler.IProblem;
24 import org.eclipse.jdt.core.dom.AST;
25 import org.eclipse.jdt.core.dom.ASTNode;
26 import org.eclipse.jdt.core.dom.Annotation;
27 import org.eclipse.jdt.core.dom.BodyDeclaration;
28 import org.eclipse.jdt.core.dom.CompilationUnit;
29 import org.eclipse.jdt.core.dom.IBinding;
30 import org.eclipse.jdt.core.dom.IExtendedModifier;
31 import org.eclipse.jdt.core.dom.IMethodBinding;
32 import org.eclipse.jdt.core.dom.ITypeBinding;
33 import org.eclipse.jdt.core.dom.IVariableBinding;
34 import org.eclipse.jdt.core.dom.MarkerAnnotation;
35 import org.eclipse.jdt.core.dom.MethodDeclaration;
36 import org.eclipse.jdt.core.dom.MethodInvocation;
37 import org.eclipse.jdt.core.dom.Name;
38 import org.eclipse.jdt.core.dom.SimpleName;
39 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
40 import org.eclipse.jdt.core.dom.VariableDeclaration;
41 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
42 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
44 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
45 import org.eclipse.jdt.internal.corext.dom.Bindings;
46 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
47 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
48 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
49 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
50 import org.eclipse.jdt.internal.corext.util.Messages;
52 import org.eclipse.jdt.ui.text.java.IProblemLocation;
54 import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
55 import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
57 public class NullRewriteOperations {
59 static abstract class SignatureAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
60 String fAnnotationToAdd;
61 String fAnnotationToRemove;
63 CompilationUnit fUnit;
64 protected String fKey;
65 protected String fMessage;
67 /* A globally unique key that identifies the position being annotated (for avoiding double annotations). */
68 public String getKey() {
72 public CompilationUnit getCompilationUnit() {
76 boolean checkExisting(List<IExtendedModifier> existingModifiers, ListRewrite listRewrite, TextEditGroup editGroup) {
77 for (Object mod : existingModifiers) {
78 if (mod instanceof MarkerAnnotation) {
79 MarkerAnnotation annotation= (MarkerAnnotation) mod;
80 String existingName= annotation.getTypeName().getFullyQualifiedName();
81 int lastDot= fAnnotationToRemove.lastIndexOf('.');
82 if (existingName.equals(fAnnotationToRemove) || (lastDot != -1 && fAnnotationToRemove.substring(lastDot + 1).equals(existingName))) {
84 return false; // veto this change
85 listRewrite.remove(annotation, editGroup);
88 // paranoia: check if by accident the annotation is already present (shouldn't happen):
89 lastDot= fAnnotationToAdd.lastIndexOf('.');
90 if (existingName.equals(fAnnotationToAdd) || (lastDot != -1 && fAnnotationToAdd.substring(lastDot + 1).equals(existingName))) {
91 return false; // already present
98 public String getMessage() {
104 * Rewrite operation that inserts an annotation into a method signature.
106 * Crafted after the lead of Java50Fix.AnnotationRewriteOperation
108 static class ReturnAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
110 private final BodyDeclaration fBodyDeclaration;
112 ReturnAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, boolean allowRemove, String message) {
114 fKey= method.resolveBinding().getKey() + "<return>"; //$NON-NLS-1$
115 fBodyDeclaration= method;
116 fAnnotationToAdd= annotationToAdd;
117 fAnnotationToRemove= annotationToRemove;
118 fAllowRemove= allowRemove;
123 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
124 AST ast= cuRewrite.getRoot().getAST();
125 ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fBodyDeclaration, fBodyDeclaration.getModifiersProperty());
126 TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
127 if (!checkExisting(fBodyDeclaration.modifiers(), listRewrite, group))
129 Annotation newAnnotation= ast.newMarkerAnnotation();
130 ImportRewrite importRewrite= cuRewrite.getImportRewrite();
131 String resolvableName= importRewrite.addImport(fAnnotationToAdd);
132 newAnnotation.setTypeName(ast.newName(resolvableName));
133 listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the return type
137 static class ParameterAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
139 private SingleVariableDeclaration fArgument;
141 ParameterAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, String paramName, boolean allowRemove, String message) {
143 fKey= method.resolveBinding().getKey();
144 fAnnotationToAdd= annotationToAdd;
145 fAnnotationToRemove= annotationToRemove;
146 fAllowRemove= allowRemove;
148 for (Object param : method.parameters()) {
149 SingleVariableDeclaration argument= (SingleVariableDeclaration) param;
150 if (argument.getName().getIdentifier().equals(paramName)) {
152 fKey+= argument.getName().getIdentifier();
156 // shouldn't happen, we've checked that paramName indeed denotes a parameter.
157 throw new RuntimeException("Argument " + paramName + " not found in method " + method.getName().getIdentifier()); //$NON-NLS-1$ //$NON-NLS-2$
160 ParameterAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, int paramIdx, boolean allowRemove, String message) {
162 fKey= method.resolveBinding().getKey();
163 fAnnotationToAdd= annotationToAdd;
164 fAnnotationToRemove= annotationToRemove;
165 fAllowRemove= allowRemove;
166 fArgument= (SingleVariableDeclaration) method.parameters().get(paramIdx);
167 fKey+= fArgument.getName().getIdentifier();
172 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException {
173 AST ast= cuRewrite.getRoot().getAST();
174 ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fArgument, SingleVariableDeclaration.MODIFIERS2_PROPERTY);
175 TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
176 if (!checkExisting(fArgument.modifiers(), listRewrite, group))
178 Annotation newAnnotation= ast.newMarkerAnnotation();
179 ImportRewrite importRewrite= cuRewrite.getImportRewrite();
180 String resolvableName= importRewrite.addImport(fAnnotationToAdd);
181 newAnnotation.setTypeName(ast.newName(resolvableName));
182 listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the type
186 // Entry for QuickFixes:
187 public static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd, String annotationToRemove,
188 Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove, boolean modifyOverridden) {
189 SignatureAnnotationRewriteOperation result= modifyOverridden ? createAddAnnotationToOverriddenOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions,
190 thisUnitOnly, allowRemove) : createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions, thisUnitOnly, allowRemove);
191 if (handledPositions != null && result != null) {
192 if (handledPositions.contains(result.getKey()))
194 handledPositions.add(result.getKey());
199 private static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd, String annotationToRemove,
200 Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove) {
201 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
202 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
205 ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
206 if (selectedNode == null)
208 ASTNode declaringNode= getDeclaringNode(selectedNode);
210 switch (problem.getProblemId()) {
211 case IProblem.IllegalDefinitionToNonNullParameter:
212 // case IllegalRedefinitionToNonNullParameter:
213 // these affect another method
215 case IProblem.IllegalReturnNullityRedefinition:
216 if (declaringNode == null)
217 declaringNode= selectedNode;
218 break; // do propose changes even if we already have an annotation
220 // if this method has annotations, don't change'em
221 if (NullQuickFixes.hasExplicitNullAnnotation(cu, problem.getOffset()))
225 String annotationNameLabel= annotationToAdd;
226 int lastDot= annotationToAdd.lastIndexOf('.');
228 annotationNameLabel= annotationToAdd.substring(lastDot + 1);
229 annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
231 if (selectedNode.getParent() instanceof MethodInvocation) {
232 // DefiniteNullToNonNullParameter || PotentialNullToNonNullParameter
233 MethodInvocation methodInvocation= (MethodInvocation) selectedNode.getParent();
234 int paramIdx= methodInvocation.arguments().indexOf(selectedNode);
235 IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
236 compilationUnit= findCUForMethod(compilationUnit, cu, methodBinding);
237 if (compilationUnit == null)
239 if (thisUnitOnly && !compilationUnit.getJavaElement().equals(cu))
241 ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getKey());
242 if (methodDecl == null)
244 String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
245 return new ParameterAnnotationRewriteOperation(compilationUnit, (MethodDeclaration) methodDecl, annotationToAdd, annotationToRemove, paramIdx, allowRemove, message);
246 } else if (declaringNode instanceof MethodDeclaration) {
247 // complaint is in signature of this method
248 MethodDeclaration declaration= (MethodDeclaration) declaringNode;
249 switch (problem.getProblemId()) {
250 case IProblem.ParameterLackingNonNullAnnotation:
251 case IProblem.ParameterLackingNullableAnnotation:
252 case IProblem.IllegalDefinitionToNonNullParameter:
253 case IProblem.IllegalRedefinitionToNonNullParameter:
254 case IProblem.NonNullLocalVariableComparisonYieldsFalse:
255 case IProblem.RedundantNullCheckOnNonNullLocalVariable:
256 // statements suggest changing parameters:
257 if (declaration.getNodeType() == ASTNode.METHOD_DECLARATION) {
258 String paramName= findAffectedParameterName(selectedNode);
259 if (paramName != null) {
260 String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
261 return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
265 case IProblem.RequiredNonNullButProvidedNull:
266 case IProblem.RequiredNonNullButProvidedPotentialNull:
267 case IProblem.RequiredNonNullButProvidedUnknown:
268 if (NullQuickFixes.isComplainingAboutArgument(selectedNode)) {
270 // statements suggest changing parameters:
271 if (declaration.getNodeType() == ASTNode.METHOD_DECLARATION) {
272 String paramName= findAffectedParameterName(selectedNode);
273 if (paramName != null) {
274 String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
275 return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
281 case IProblem.IllegalReturnNullityRedefinition:
282 String message= Messages.format(NullFixMessages.QuickFixes_change_method_return_nullness, new String[] { declaration.getName().getIdentifier(), annotationNameLabel });
283 return new ReturnAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, allowRemove, message);
289 private static SignatureAnnotationRewriteOperation createAddAnnotationToOverriddenOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd,
290 String annotationToRemove, Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove) {
291 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
292 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
295 ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
296 if (selectedNode == null)
299 ASTNode declaringNode= getDeclaringNode(selectedNode);
300 switch (problem.getProblemId()) {
301 case IProblem.IllegalDefinitionToNonNullParameter:
302 case IProblem.IllegalRedefinitionToNonNullParameter:
304 case IProblem.IllegalReturnNullityRedefinition:
305 if (declaringNode == null)
306 declaringNode= selectedNode;
312 String annotationNameLabel= annotationToAdd;
313 int lastDot= annotationToAdd.lastIndexOf('.');
315 annotationNameLabel= annotationToAdd.substring(lastDot + 1);
316 annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
318 if (declaringNode instanceof MethodDeclaration) {
319 // complaint is in signature of this method
320 MethodDeclaration declaration= (MethodDeclaration) declaringNode;
321 switch (problem.getProblemId()) {
322 case IProblem.IllegalDefinitionToNonNullParameter:
323 case IProblem.IllegalRedefinitionToNonNullParameter:
324 return createChangeOverriddenParameterOperation(compilationUnit, cu, declaration, selectedNode, allowRemove, annotationToAdd, annotationToRemove, annotationNameLabel);
325 case IProblem.IllegalReturnNullityRedefinition:
326 if (hasNullAnnotation(declaration)) { // don't adjust super if local has no explicit annotation (?)
327 return createChangeOverriddenReturnOperation(compilationUnit, cu, declaration, allowRemove, annotationToAdd, annotationToRemove, annotationNameLabel);
334 private static SignatureAnnotationRewriteOperation createChangeOverriddenParameterOperation(CompilationUnit compilationUnit, ICompilationUnit cu, MethodDeclaration declaration,
335 ASTNode selectedNode, boolean allowRemove, String annotationToAdd, String annotationToRemove, String annotationNameLabel) {
336 IMethodBinding methodDeclBinding= declaration.resolveBinding();
337 if (methodDeclBinding == null)
340 IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
341 if (overridden == null)
343 compilationUnit= findCUForMethod(compilationUnit, cu, overridden);
344 if (compilationUnit == null)
346 ASTNode methodDecl= compilationUnit.findDeclaringNode(overridden.getKey());
347 if (methodDecl == null)
349 declaration= (MethodDeclaration) methodDecl;
350 String message= Messages.format(NullFixMessages.QuickFixes_change_overridden_parameter_nullness, new String[] { overridden.getName(), annotationNameLabel });
351 String paramName= findAffectedParameterName(selectedNode);
352 return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
355 private static String findAffectedParameterName(ASTNode selectedNode) {
356 VariableDeclaration argDecl= (selectedNode instanceof VariableDeclaration) ? (VariableDeclaration) selectedNode : (VariableDeclaration) ASTNodes.getParent(selectedNode,
357 VariableDeclaration.class);
359 return argDecl.getName().getIdentifier();
360 if (selectedNode.getNodeType() == ASTNode.SIMPLE_NAME) {
361 IBinding binding= ((SimpleName) selectedNode).resolveBinding();
362 if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
363 return ((SimpleName) selectedNode).getIdentifier();
368 private static boolean hasNullAnnotation(MethodDeclaration decl) {
369 List<IExtendedModifier> modifiers= decl.modifiers();
370 String nonnull= NullQuickFixes.getNonNullAnnotationName(decl.resolveBinding().getJavaElement(), false);
371 String nullable= NullQuickFixes.getNullableAnnotationName(decl.resolveBinding().getJavaElement(), false);
372 for (Object mod : modifiers) {
373 if (mod instanceof Annotation) {
374 Name annotationName= ((Annotation) mod).getTypeName();
375 String fullyQualifiedName= annotationName.getFullyQualifiedName();
376 if (annotationName.isSimpleName() ? nonnull.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nonnull))
378 if (annotationName.isSimpleName() ? nullable.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nullable))
385 private static SignatureAnnotationRewriteOperation createChangeOverriddenReturnOperation(CompilationUnit compilationUnit, ICompilationUnit cu, MethodDeclaration declaration, boolean allowRemove,
386 String annotationToAdd, String annotationToRemove, String annotationNameLabel) {
387 IMethodBinding methodDeclBinding= declaration.resolveBinding();
388 if (methodDeclBinding == null)
391 IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
392 if (overridden == null)
394 compilationUnit= findCUForMethod(compilationUnit, cu, overridden);
395 if (compilationUnit == null)
397 ASTNode methodDecl= compilationUnit.findDeclaringNode(overridden.getKey());
398 if (methodDecl == null)
400 declaration= (MethodDeclaration) methodDecl;
401 // TODO(SH): decide whether we want to propose overwriting existing annotations in super
402 // if (hasNullAnnotation(declaration)) // if overridden has explicit declaration don't propose to change it
404 String message= Messages.format(NullFixMessages.QuickFixes_change_overridden_return_nullness, new String[] { overridden.getName(), annotationNameLabel });
405 return new ReturnAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, allowRemove, message);
408 private static CompilationUnit findCUForMethod(CompilationUnit compilationUnit, ICompilationUnit cu, IMethodBinding methodBinding) {
409 ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getMethodDeclaration());
410 if (methodDecl == null) {
411 // is methodDecl defined in another CU?
412 ITypeBinding declaringTypeDecl= methodBinding.getDeclaringClass().getTypeDeclaration();
413 if (declaringTypeDecl.isFromSource()) {
414 ICompilationUnit targetCU= null;
416 targetCU= ASTResolving.findCompilationUnitForBinding(cu, compilationUnit, declaringTypeDecl);
417 } catch (JavaModelException e) { /* can't do better */
419 if (targetCU != null) {
420 return ASTResolving.createQuickFixAST(targetCU, null);
425 return compilationUnit;
428 /* The relevant declaring node of a return statement is the enclosing method. */
429 private static ASTNode getDeclaringNode(ASTNode selectedNode) {
430 return ASTNodes.getParent(selectedNode, ASTNode.METHOD_DECLARATION);