| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |