blob: eb9e6ebce12f555a47a1672de96af946f9b544ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2017 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
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
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.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.VariableDeclaration;
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.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.internal.ui.JavaPlugin;
public class NullAnnotationsRewriteOperations {
static final String TYPE_USE_NAME= ElementType.class.getName()+'.'+ElementType.TYPE_USE.name();
// reduced copy of org.eclipse.jdt.annotation.DefaultLocation:
enum DefaultLocation {
PARAMETER,
RETURN_TYPE
}
public enum ChangeKind {
LOCAL, // do the normal thing locally in the current method
INVERSE, // insert the inverse annotation in the local method
OVERRIDDEN, // change the overridden method
TARGET // change the target method of a method call
}
static abstract class SignatureAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
// initialized from the Builder:
protected CompilationUnit fUnit;
protected String fAnnotationToAdd;
protected String fAnnotationToRemove;
protected boolean fAllowRemove;
protected boolean fUseNullTypeAnnotations;
protected boolean fRequireExplicitAnnotation;
// assigned within constructors:
protected String fKey;
protected String fMessage;
// assigned after the constructor:
boolean fRemoveIfNonNullByDefault;
String fNonNullByDefaultName;
protected SignatureAnnotationRewriteOperation(Builder builder) {
fUnit= builder.fUnit;
fAnnotationToAdd= builder.fAnnotationToAdd;
fAnnotationToRemove= builder.fAnnotationToRemove;
fAllowRemove= builder.fAllowRemove;
fUseNullTypeAnnotations= builder.fUseNullTypeAnnotations;
fRequireExplicitAnnotation= builder.requiresExplicitAnnotation();
}
/* A globally unique key that identifies the position being annotated (for avoiding double annotations). */
public String getKey() {
return fKey;
}
public CompilationUnit getCompilationUnit() {
return fUnit;
}
boolean checkExisting(List<IExtendedModifier> existingModifiers, ListRewrite listRewrite, TextEditGroup editGroup) {
for (Object mod : existingModifiers) {
if (mod instanceof MarkerAnnotation) {
MarkerAnnotation annotation= (MarkerAnnotation) mod;
String existingName= annotation.getTypeName().getFullyQualifiedName();
int lastDot= fAnnotationToRemove.lastIndexOf('.');
if (existingName.equals(fAnnotationToRemove) || (lastDot != -1 && fAnnotationToRemove.substring(lastDot + 1).equals(existingName))) {
if (!fAllowRemove)
return false; // veto this change
listRewrite.remove(annotation, editGroup);
return true;
}
// paranoia: check if by accident the annotation is already present (shouldn't happen):
lastDot= fAnnotationToAdd.lastIndexOf('.');
if (existingName.equals(fAnnotationToAdd) || (lastDot != -1 && fAnnotationToAdd.substring(lastDot + 1).equals(existingName))) {
return false; // already present
}
}
}
return true;
}
/* Is the given element affected by a @NonNullByDefault v1.x? */
boolean hasNonNullDefault(IBinding enclosingElement) {
if (!fRemoveIfNonNullByDefault) return false;
IAnnotationBinding[] annotations = enclosingElement.getAnnotations();
for (int i= 0; i < annotations.length; i++) {
IAnnotationBinding annot = annotations[i];
if (annot.getAnnotationType().getQualifiedName().equals(fNonNullByDefaultName)) {
IMemberValuePairBinding[] pairs= annot.getDeclaredMemberValuePairs();
if (pairs.length > 0) {
// is default cancelled by "false" or "value=false" ?
for (int j= 0; j < pairs.length; j++)
if (pairs[j].getKey() == null || pairs[j].getKey().equals("value")) //$NON-NLS-1$
return (pairs[j].getValue() != Boolean.FALSE);
}
return true;
}
}
if (enclosingElement instanceof IMethodBinding) {
return hasNonNullDefault(((IMethodBinding)enclosingElement).getDeclaringClass());
} else if (enclosingElement instanceof ITypeBinding) {
ITypeBinding typeBinding= (ITypeBinding)enclosingElement;
if (typeBinding.isLocal())
return hasNonNullDefault(typeBinding.getDeclaringMethod());
else if (typeBinding.isMember())
return hasNonNullDefault(typeBinding.getDeclaringClass());
else
return hasNonNullDefault(typeBinding.getPackage());
}
return false;
}
/* Is the given element affected by a 308-style @NonNullByDefault? */
boolean hasNonNullDefault308(IBinding enclosingElement, int parameterRank, DefaultLocation defaultLocation, boolean recursiveCall) {
if (!fRemoveIfNonNullByDefault) return false;
if (!recursiveCall) {
ITypeBinding affectedType= null;
if (enclosingElement instanceof IMethodBinding) {
switch (defaultLocation) {
case RETURN_TYPE:
affectedType= ((IMethodBinding) enclosingElement).getReturnType();
break;
case PARAMETER:
affectedType= ((IMethodBinding) enclosingElement).getParameterTypes()[parameterRank];
break;
default:
// no other locations supported yet (fields?)
}
} else if (enclosingElement instanceof IVariableBinding) {
affectedType= ((IVariableBinding) enclosingElement).getType();
}
if (affectedType != null) {
if (affectedType.isTypeVariable() || affectedType.isWildcardType()) {
return false; // not affected by @NonNullByDefault
}
}
}
IAnnotationBinding[] annotations= enclosingElement.getAnnotations();
for (int i= 0; i < annotations.length; i++) {
IAnnotationBinding annot= annotations[i];
ITypeBinding annotationType= annot.getAnnotationType();
if (annotationType != null && annotationType.getQualifiedName().equals(fNonNullByDefaultName)) {
IMemberValuePairBinding[] pairs= annot.getDeclaredMemberValuePairs();
if (pairs.length > 0) {
// does the default's set of locations contain `defaultLocation`?
for (int j= 0; j < pairs.length; j++)
if (pairs[j].getKey() == null || pairs[j].getKey().equals("value")) { //$NON-NLS-1$
return matchesLocation(pairs[j].getValue(), defaultLocation);
}
}
return true;
}
}
if (enclosingElement instanceof IVariableBinding) {
IVariableBinding variable= (IVariableBinding) enclosingElement;
enclosingElement= variable.isParameter() ? variable.getDeclaringMethod() : variable.getDeclaringClass();
return hasNonNullDefault308(enclosingElement, -1, defaultLocation, true);
} else if (enclosingElement instanceof IMethodBinding) {
return hasNonNullDefault308(((IMethodBinding)enclosingElement).getDeclaringClass(), -1, defaultLocation, true);
} else if (enclosingElement instanceof ITypeBinding) {
ITypeBinding typeBinding= (ITypeBinding)enclosingElement;
if (typeBinding.isLocal())
return hasNonNullDefault308(typeBinding.getDeclaringMethod(), -1, defaultLocation, true);
else if (typeBinding.isMember())
return hasNonNullDefault308(typeBinding.getDeclaringClass(), -1, defaultLocation, true);
else
return hasNonNullDefault308(typeBinding.getPackage(), -1, defaultLocation, true);
}
return false;
}
private boolean matchesLocation(Object value, DefaultLocation location) {
if (value instanceof Object[]) {
Object[] values= (Object[]) value;
for (int i= 0; i < values.length; i++) {
if (matchesLocation(values[i], location))
return true;
}
} else if (value instanceof IVariableBinding) {
String name= ((IVariableBinding) value).getName();
return location.name().equals(name);
}
return false;
}
public String getMessage() {
return fMessage;
}
}
/**
* Rewrite operation that inserts an annotation into a method signature.
*
* Crafted after the lead of Java50Fix.AnnotationRewriteOperation
*/
static class ReturnAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
private final MethodDeclaration fBodyDeclaration;
ReturnAnnotationRewriteOperation(MethodDeclaration method, String message, Builder builder) {
super(builder);
fKey= method.resolveBinding().getKey() + "<return>"; //$NON-NLS-1$
fBodyDeclaration= method;
fMessage= message;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
AST ast= cuRewrite.getRoot().getAST();
ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fBodyDeclaration, fBodyDeclaration.getModifiersProperty());
TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
if (!checkExisting(fBodyDeclaration.modifiers(), listRewrite, group))
return;
if (!fRequireExplicitAnnotation) {
if (fUseNullTypeAnnotations
? hasNonNullDefault308(fBodyDeclaration.resolveBinding(), /*parameterRank*/-1, DefaultLocation.RETURN_TYPE, false)
: hasNonNullDefault(fBodyDeclaration.resolveBinding()))
return; // should be safe, as in this case checkExisting() should've already produced a change (remove existing annotation).
}
Annotation newAnnotation= ast.newMarkerAnnotation();
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
String resolvableName= importRewrite.addImport(fAnnotationToAdd);
newAnnotation.setTypeName(ast.newName(resolvableName));
listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the return type
}
}
static class ParameterAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
static class IndexedParameter {
int index;
String name;
IndexedParameter(int index, String name) {
this.index= index;
this.name= name;
}
}
private SingleVariableDeclaration fArgument;
private int fParameterRank;
// for lambda parameter (can return null):
static ParameterAnnotationRewriteOperation create(LambdaExpression lambda, IndexedParameter parameter, String message, Builder builder) {
IMethodBinding lambdaMethodBinding= lambda.resolveMethodBinding();
List<?> parameters= lambda.parameters();
if (parameters.size() > parameter.index) {
Object param= parameters.get(parameter.index);
if (!(param instanceof SingleVariableDeclaration)) {
return null; // type elided lambda
}
return new ParameterAnnotationRewriteOperation(lambdaMethodBinding, parameters, parameter.index, message, builder);
}
// shouldn't happen, we've checked that paramName indeed denotes a parameter.
throw new RuntimeException("Argument " + parameter.name + " not found in method " + lambda.toString()); //$NON-NLS-1$ //$NON-NLS-2$
}
// for method parameter:
ParameterAnnotationRewriteOperation(MethodDeclaration method, int paramIdx, String message, Builder builder) {
this(method.resolveBinding(), method.parameters(), paramIdx, message, builder);
}
private ParameterAnnotationRewriteOperation(IMethodBinding methodBinding, List<?> parameters, int paramIdx, String message, Builder builder) {
super(builder);
fKey= methodBinding.getKey();
fArgument= (SingleVariableDeclaration) parameters.get(paramIdx);
fParameterRank= paramIdx;
fKey+= fArgument.getName().getIdentifier();
fMessage= message;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException {
AST ast= cuRewrite.getRoot().getAST();
ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fArgument, SingleVariableDeclaration.MODIFIERS2_PROPERTY);
TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
if (!checkExisting(fArgument.modifiers(), listRewrite, group))
return;
if (!fRequireExplicitAnnotation) {
if (fUseNullTypeAnnotations
? hasNonNullDefault308(fArgument.resolveBinding(), fParameterRank, DefaultLocation.PARAMETER, false)
: hasNonNullDefault(fArgument.resolveBinding()))
return; // should be safe, as in this case checkExisting() should've already produced a change (remove existing annotation).
}
Annotation newAnnotation= ast.newMarkerAnnotation();
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
String resolvableName= importRewrite.addImport(fAnnotationToAdd);
newAnnotation.setTypeName(ast.newName(resolvableName));
listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the type
}
}
static class RemoveRedundantAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
private IProblemLocation fProblem;
private CompilationUnit fCompilationUnit;
public RemoveRedundantAnnotationRewriteOperation(CompilationUnit compilationUnit, IProblemLocation problem) {
fCompilationUnit= compilationUnit;
fProblem= problem;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException {
TextEditGroup group= createTextEditGroup(FixMessages.NullAnnotationsRewriteOperations_remove_redundant_nullness_annotation, cuRewrite);
ASTRewrite astRewrite= cuRewrite.getASTRewrite();
CompilationUnit astRoot= fCompilationUnit;
ASTNode selectedNode= fProblem.getCoveringNode(astRoot);
if (fProblem.getProblemId() == IProblem.RedundantNullAnnotation) {
List<IExtendedModifier> modifiers;
if (selectedNode instanceof SingleVariableDeclaration) {
SingleVariableDeclaration singleVariableDeclaration= (SingleVariableDeclaration) selectedNode;
modifiers= singleVariableDeclaration.modifiers();
} else if (selectedNode instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration= (FieldDeclaration) selectedNode;
modifiers= fieldDeclaration.modifiers();
} else if (selectedNode instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration= (MethodDeclaration) selectedNode;
modifiers= methodDeclaration.modifiers();
} else {
return;
}
for (Iterator<IExtendedModifier> iterator= modifiers.iterator(); iterator.hasNext();) {
IExtendedModifier modifier= iterator.next();
if (modifier instanceof MarkerAnnotation) {
MarkerAnnotation annotation= (MarkerAnnotation) modifier;
IAnnotationBinding annotationBinding= annotation.resolveAnnotationBinding();
String name= annotationBinding.getName();
if (name.equals(NullAnnotationsFix.getNonNullAnnotationName(fCompilationUnit.getJavaElement(), true))) {
astRewrite.remove(annotation, group);
}
}
}
} else {
if (!(selectedNode instanceof Annotation))
return;
Annotation annotation= (Annotation) selectedNode;
IAnnotationBinding annotationBinding= annotation.resolveAnnotationBinding();
String name= annotationBinding.getName();
if (name.equals(NullAnnotationsFix.getNonNullByDefaultAnnotationName(fCompilationUnit.getJavaElement(), true))) {
astRewrite.remove(annotation, group);
}
}
}
}
public static class Builder {
IProblemLocation fProblem;
ChangeKind fChangeKind;
CompilationUnit fUnit;
String fAnnotationToAdd;
String fAnnotationToRemove;
boolean fAllowRemove;
boolean fAffectsParameter;
boolean fUseNullTypeAnnotations;
public Builder(IProblemLocation problem, CompilationUnit unit, String annotationToAdd, String annotationToRemove,
boolean allowRemove, boolean affectsParameter, ChangeKind changeKind)
{
fChangeKind= changeKind;
fUnit= unit;
fAnnotationToAdd= annotationToAdd;
fAnnotationToRemove= annotationToRemove;
fAllowRemove= allowRemove;
fAffectsParameter= affectsParameter;
fProblem= problem;
fUseNullTypeAnnotations= usesNullTypeAnnotations(unit.getJavaElement(), annotationToAdd);
}
private boolean usesNullTypeAnnotations(IJavaElement cu, String annotationName) {
IJavaProject project= (IJavaProject) cu.getAncestor(IJavaElement.JAVA_PROJECT);
if (!JavaModelUtil.is18OrHigher(project)) {
return false;
}
try {
IType annotationType= project.findType(annotationName);
if (annotationType == null) {
return false;
}
IAnnotation[] annotations= annotationType.getAnnotations();
for (int i= 0; i < annotations.length; i++) {
if (annotations[i].getElementName().equals(Target.class.getName())) {
for (IMemberValuePair valuePair : annotations[i].getMemberValuePairs()) {
if (TYPE_USE_NAME.equals(valuePair.getValue())) {
return true;
}
}
return false;
}
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
return false;
}
public boolean requiresExplicitAnnotation() {
switch (fProblem.getProblemId()) {
case IProblem.ConflictingInheritedNullAnnotations:
case IProblem.ConflictingNullAnnotations:
return fChangeKind != ChangeKind.OVERRIDDEN;
default:
return false;
}
}
public void swapAnnotations() {
String tmp= fAnnotationToAdd;
fAnnotationToAdd= fAnnotationToRemove;
fAnnotationToRemove= tmp;
}
public boolean is50OrHigher() {
CompilationUnit compilationUnit= fUnit;
ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
return JavaModelUtil.is50OrHigher(cu.getJavaProject());
}
public ASTNode getCoveringNode() {
return fProblem.getCoveringNode(fUnit);
}
// Entry for QuickFixes:
public SignatureAnnotationRewriteOperation createAddAnnotationOperation(Set<String> handledPositions, boolean thisUnitOnly, ChangeKind changeKind) {
// precondition:
// thisUnitOnly => changeKind == LOCAL
SignatureAnnotationRewriteOperation result;
if (changeKind == ChangeKind.OVERRIDDEN)
result= createAddAnnotationToOverriddenOperation();
else
result= createAddAnnotationOperation(changeKind == ChangeKind.TARGET, thisUnitOnly);
if (handledPositions != null && result != null) {
if (handledPositions.contains(result.getKey()))
return null;
handledPositions.add(result.getKey());
}
return result;
}
private SignatureAnnotationRewriteOperation createAddAnnotationOperation(boolean changeTargetMethod, boolean thisUnitOnly) {
if (!is50OrHigher())
return null;
ASTNode selectedNode= getCoveringNode();
if (selectedNode == null)
return null;
ICompilationUnit cu= (ICompilationUnit) fUnit.getJavaElement();
ASTNode declaringNode= getDeclaringNode(selectedNode);
switch (fProblem.getProblemId()) {
case IProblem.IllegalDefinitionToNonNullParameter:
// case IllegalRedefinitionToNonNullParameter:
// these affect another method
break;
case IProblem.IllegalReturnNullityRedefinition:
if (declaringNode == null)
declaringNode= selectedNode;
break; // do propose changes even if we already have an annotation
default:
// if this method has annotations, don't change'em
if (!fAllowRemove && NullAnnotationsFix.hasExplicitNullAnnotation(cu, fProblem.getOffset()))
return null;
}
String annotationNameLabel= fAnnotationToAdd;
int lastDot= annotationNameLabel.lastIndexOf('.');
if (lastDot != -1)
annotationNameLabel= annotationNameLabel.substring(lastDot + 1);
annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
if (changeTargetMethod) {
MethodInvocation methodInvocation= null;
if (fAffectsParameter) {
if (selectedNode.getParent() instanceof MethodInvocation)
methodInvocation= (MethodInvocation) selectedNode.getParent();
} else {
if (selectedNode instanceof MethodInvocation)
methodInvocation= (MethodInvocation) selectedNode;
}
if (methodInvocation != null) {
// DefiniteNullToNonNullParameter || PotentialNullToNonNullParameter
int paramIdx= methodInvocation.arguments().indexOf(selectedNode);
IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
MethodDeclaration methodDecl= findMethodDeclarationInUnit(cu, methodBinding, thisUnitOnly);
if (methodDecl == null)
return null;
if (fAffectsParameter) {
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_target_method_parameter_nullness,
new Object[] {methodInvocation.getName(), annotationNameLabel});
return new ParameterAnnotationRewriteOperation(methodDecl, paramIdx, message, this);
} else {
MethodDeclaration declaration = methodDecl;
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_return_nullness,
new String[] { declaration.getName().getIdentifier(), annotationNameLabel });
return new ReturnAnnotationRewriteOperation(declaration, message, this);
}
}
} else if (declaringNode instanceof MethodDeclaration || declaringNode instanceof LambdaExpression) {
// complaint is in signature of this method / lambda
switch (fProblem.getProblemId()) {
case IProblem.ParameterLackingNonNullAnnotation:
case IProblem.ParameterLackingNullableAnnotation:
case IProblem.IllegalDefinitionToNonNullParameter:
case IProblem.IllegalRedefinitionToNonNullParameter:
// problems regarding the argument declaration:
ParameterAnnotationRewriteOperation.IndexedParameter parameter= findParameterDeclaration(selectedNode);
if (parameter != null) {
switch (declaringNode.getNodeType()) {
case ASTNode.METHOD_DECLARATION:
MethodDeclaration method= (MethodDeclaration) declaringNode;
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_parameter_nullness,
new Object[] {parameter.name, annotationNameLabel});
return new ParameterAnnotationRewriteOperation(method, parameter.index, message, this);
case ASTNode.LAMBDA_EXPRESSION:
LambdaExpression lambda = (LambdaExpression) declaringNode;
// TODO: specific message for lambda
message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_parameter_nullness,
new Object[] {parameter.name, annotationNameLabel});
return ParameterAnnotationRewriteOperation.create(lambda, parameter, message, this);
default:
return null;
}
}
break;
case IProblem.SpecdNonNullLocalVariableComparisonYieldsFalse:
case IProblem.RedundantNullCheckOnSpecdNonNullLocalVariable:
case IProblem.RequiredNonNullButProvidedNull:
case IProblem.RequiredNonNullButProvidedPotentialNull:
case IProblem.RequiredNonNullButProvidedSpecdNullable:
case IProblem.RequiredNonNullButProvidedUnknown:
case IProblem.ConflictingNullAnnotations:
case IProblem.ConflictingInheritedNullAnnotations:
if (fAffectsParameter) {
// statement suggests changing parameters:
if (selectedNode instanceof SimpleName) {
parameter= findReferencedParameter(selectedNode);
if (parameter != null) {
switch (declaringNode.getNodeType()) {
case ASTNode.METHOD_DECLARATION:
MethodDeclaration declaration= (MethodDeclaration) declaringNode;
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_parameter_nullness,
new Object[] { parameter.name, annotationNameLabel });
return new ParameterAnnotationRewriteOperation(declaration, parameter.index, message, this);
case ASTNode.LAMBDA_EXPRESSION:
LambdaExpression lambda= (LambdaExpression) declaringNode;
// TODO: appropriate message for lambda
message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_parameter_nullness,
new Object[] { parameter.name, annotationNameLabel });
return ParameterAnnotationRewriteOperation.create(lambda, parameter, message, this);
default:
return null;
}
}
}
break;
}
//$FALL-THROUGH$
case IProblem.IllegalReturnNullityRedefinition:
if (declaringNode.getNodeType()== ASTNode.METHOD_DECLARATION) {
MethodDeclaration declaration= (MethodDeclaration) declaringNode;
String name= declaration.getName().getIdentifier();
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_method_return_nullness, new String[] { name, annotationNameLabel });
return new ReturnAnnotationRewriteOperation(declaration, message, this);
}
break;
default:
return null;
}
}
return null;
}
private SignatureAnnotationRewriteOperation createAddAnnotationToOverriddenOperation() {
if (!is50OrHigher())
return null;
ASTNode selectedNode= getCoveringNode();
if (selectedNode == null)
return null;
ICompilationUnit cu= (ICompilationUnit) fUnit.getJavaElement();
ASTNode declaringNode= getDeclaringNode(selectedNode);
switch (fProblem.getProblemId()) {
case IProblem.IllegalDefinitionToNonNullParameter:
case IProblem.IllegalRedefinitionToNonNullParameter:
break;
case IProblem.IllegalReturnNullityRedefinition:
case IProblem.ConflictingNullAnnotations:
if (declaringNode == null)
declaringNode= selectedNode;
break;
default:
return null;
}
String annotationNameLabel= fAnnotationToAdd;
int lastDot= annotationNameLabel.lastIndexOf('.');
if (lastDot != -1)
annotationNameLabel= annotationNameLabel.substring(lastDot + 1);
annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
if (declaringNode instanceof MethodDeclaration) {
// complaint is in signature of this method
MethodDeclaration declaration= (MethodDeclaration) declaringNode;
switch (fProblem.getProblemId()) {
case IProblem.IllegalReturnNullityRedefinition:
if (!hasNullAnnotation(declaration)) {
return null; // don't adjust super if local has no explicit annotation (?)
}
//$FALL-THROUGH$
case IProblem.IllegalDefinitionToNonNullParameter:
case IProblem.IllegalRedefinitionToNonNullParameter:
case IProblem.ConflictingNullAnnotations:
if (fAffectsParameter) {
return createChangeOverriddenParameterOperation(cu, declaration, selectedNode, annotationNameLabel);
} else {
return createChangeOverriddenReturnOperation(cu, declaration, annotationNameLabel);
}
default:
return null;
}
}
return null;
}
private SignatureAnnotationRewriteOperation createChangeOverriddenParameterOperation(ICompilationUnit cu, MethodDeclaration declaration, ASTNode selectedNode,
String annotationNameLabel) {
IMethodBinding methodDeclBinding= declaration.resolveBinding();
if (methodDeclBinding == null)
return null;
IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
if (overridden == null)
return null;
MethodDeclaration overriddenDeclaration= findMethodDeclarationInUnit(cu, overridden, false);
if (overriddenDeclaration == null)
return null;
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_overridden_parameter_nullness, new String[] { overridden.getName(), annotationNameLabel });
ParameterAnnotationRewriteOperation.IndexedParameter parameter= findParameterDeclaration(selectedNode); // parameter.name is determined from the current method, but this name will not be used here
if (parameter == null)
return null;
return new ParameterAnnotationRewriteOperation(overriddenDeclaration, parameter.index, message, this);
}
private ParameterAnnotationRewriteOperation.IndexedParameter findParameterDeclaration(ASTNode selectedNode) {
VariableDeclaration argDecl= (selectedNode instanceof VariableDeclaration) ? (VariableDeclaration) selectedNode : (VariableDeclaration) ASTNodes.getParent(selectedNode,
VariableDeclaration.class);
if (argDecl != null) {
StructuralPropertyDescriptor locationInParent= argDecl.getLocationInParent();
if (!locationInParent.isChildListProperty())
return null;
List<?> containingList= (List<?>) argDecl.getParent().getStructuralProperty(locationInParent);
return new ParameterAnnotationRewriteOperation.IndexedParameter(containingList.indexOf(argDecl), argDecl.getName().getIdentifier());
}
return null;
}
private ParameterAnnotationRewriteOperation.IndexedParameter findReferencedParameter(ASTNode selectedNode) {
if (selectedNode.getNodeType() == ASTNode.SIMPLE_NAME) {
IBinding binding= ((SimpleName) selectedNode).resolveBinding();
if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter()) {
ASTNode current= selectedNode.getParent();
while (current != null) {
List<?> parameters= null;
switch (current.getNodeType()) {
case ASTNode.METHOD_DECLARATION:
parameters= ((MethodDeclaration) current).parameters();
break;
case ASTNode.LAMBDA_EXPRESSION:
parameters= ((LambdaExpression) current).parameters();
break;
default:
/* continue traversing outwards */
}
if (parameters != null) {
for (int i= 0; i < parameters.size(); i++) {
VariableDeclaration parameter= (VariableDeclaration) parameters.get(i);
if (parameter.resolveBinding() == binding)
return new ParameterAnnotationRewriteOperation.IndexedParameter(i, binding.getName());
}
}
current= current.getParent();
}
}
}
return null;
}
private boolean hasNullAnnotation(MethodDeclaration decl) {
List<IExtendedModifier> modifiers= decl.modifiers();
String nonnull= NullAnnotationsFix.getNonNullAnnotationName(decl.resolveBinding().getJavaElement(), false);
String nullable= NullAnnotationsFix.getNullableAnnotationName(decl.resolveBinding().getJavaElement(), false);
for (Object mod : modifiers) {
if (mod instanceof Annotation) {
Name annotationName= ((Annotation) mod).getTypeName();
String fullyQualifiedName= annotationName.getFullyQualifiedName();
if (annotationName.isSimpleName() ? nonnull.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nonnull))
return true;
if (annotationName.isSimpleName() ? nullable.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nullable))
return true;
}
}
return false;
}
private SignatureAnnotationRewriteOperation createChangeOverriddenReturnOperation(ICompilationUnit cu, MethodDeclaration declaration, String annotationNameLabel) {
IMethodBinding methodDeclBinding= declaration.resolveBinding();
if (methodDeclBinding == null)
return null;
IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
if (overridden == null)
return null;
declaration= findMethodDeclarationInUnit(cu, overridden, false);
if (declaration == null)
return null;
// TODO(SH): decide whether we want to propose overwriting existing annotations in super
// if (hasNullAnnotation(declaration)) // if overridden has explicit declaration don't propose to change it
// return null;
String message= Messages.format(FixMessages.NullAnnotationsRewriteOperations_change_overridden_return_nullness, new String[] { overridden.getName(), annotationNameLabel });
return new ReturnAnnotationRewriteOperation(declaration, message, this);
}
private MethodDeclaration findMethodDeclarationInUnit(ICompilationUnit cu, IMethodBinding method, boolean sameUnitOnly) {
method= method.getMethodDeclaration();
CompilationUnit compilationUnit= findCUForMethod(fUnit, cu, method);
if (compilationUnit == null)
return null;
if (sameUnitOnly && !compilationUnit.getJavaElement().equals(cu))
return null;
ASTNode methodDecl= compilationUnit.findDeclaringNode(method.getKey());
if (methodDecl == null)
return null;
fUnit= compilationUnit;
return (MethodDeclaration) methodDecl;
}
private CompilationUnit findCUForMethod(CompilationUnit compilationUnit, ICompilationUnit cu, IMethodBinding methodBinding) {
ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getMethodDeclaration());
if (methodDecl == null) {
// is methodDecl defined in another CU?
ITypeBinding declaringTypeDecl= methodBinding.getDeclaringClass().getTypeDeclaration();
if (declaringTypeDecl.isFromSource()) {
ICompilationUnit targetCU= null;
try {
targetCU= ASTResolving.findCompilationUnitForBinding(cu, compilationUnit, declaringTypeDecl);
} catch (JavaModelException e) { /* can't do better */
}
if (targetCU != null) {
return ASTResolving.createQuickFixAST(targetCU, null);
}
}
return null;
}
return compilationUnit;
}
/* The relevant declaring node of a return statement is the enclosing method or lambda. */
private static ASTNode getDeclaringNode(ASTNode selectedNode) {
while (selectedNode != null) {
switch (selectedNode.getNodeType()) {
case ASTNode.METHOD_DECLARATION:
case ASTNode.LAMBDA_EXPRESSION:
return selectedNode;
default:
selectedNode= selectedNode.getParent();
}
}
return null;
}
}
}