blob: 765e2448837cff38f514b80d72d01477e0f692bb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2017 GK Software AG and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.swt.graphics.Image;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.fix.NullAnnotationsFix;
import org.eclipse.jdt.internal.corext.fix.NullAnnotationsRewriteOperations.ChangeKind;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.fix.NullAnnotationsCleanUp;
import org.eclipse.jdt.internal.ui.text.correction.proposals.CreatePackageInfoWithDefaultNullnessProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.ExtractToNullCheckedLocalProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.MakeLocalVariableNonNullProposal;
/**
* Quick Fixes for null-annotation related problems.
*/
public class NullAnnotationsCorrectionProcessor {
// pre: changeKind != OVERRIDDEN
public static void addReturnAndArgumentTypeProposal(IInvocationContext context, IProblemLocation problem, ChangeKind changeKind,
Collection<ICommandAccess> proposals) {
CompilationUnit astRoot= context.getASTRoot();
ASTNode selectedNode= problem.getCoveringNode(astRoot);
boolean isArgumentProblem= NullAnnotationsFix.isComplainingAboutArgument(selectedNode);
if (isArgumentProblem || NullAnnotationsFix.isComplainingAboutReturn(selectedNode))
addNullAnnotationInSignatureProposal(context, problem, proposals, changeKind, isArgumentProblem);
}
public static void addNullAnnotationInSignatureProposal(IInvocationContext context, IProblemLocation problem,
Collection<ICommandAccess> proposals, ChangeKind changeKind, boolean isArgumentProblem) {
NullAnnotationsFix fix= NullAnnotationsFix.createNullAnnotationInSignatureFix(context.getASTRoot(), problem,
changeKind, isArgumentProblem);
if (fix != null) {
Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
Map<String, String> options= new Hashtable<>();
if (fix.getCu() != context.getASTRoot()) {
// workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..)
final CompilationUnit cu= fix.getCu();
final IInvocationContext originalContext= context;
context= new IInvocationContext() {
@Override
public int getSelectionOffset() {
return originalContext.getSelectionOffset();
}
@Override
public int getSelectionLength() {
return originalContext.getSelectionLength();
}
@Override
public ASTNode getCoveringNode() {
return originalContext.getCoveringNode();
}
@Override
public ASTNode getCoveredNode() {
return originalContext.getCoveredNode();
}
@Override
public ICompilationUnit getCompilationUnit() {
return (ICompilationUnit) cu.getJavaElement();
}
@Override
public CompilationUnit getASTRoot() {
return cu;
}
};
}
int relevance= (changeKind == ChangeKind.OVERRIDDEN) ? IProposalRelevance.CHANGE_NULLNESS_ANNOTATION_IN_OVERRIDDEN_METHOD : IProposalRelevance.CHANGE_NULLNESS_ANNOTATION; //raise local change above change in overridden method
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), relevance, image, context);
proposals.add(proposal);
}
}
public static void addRemoveRedundantAnnotationProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
NullAnnotationsFix fix= NullAnnotationsFix.createRemoveRedundantNullAnnotationsFix(context.getASTRoot(), problem);
if (fix == null)
return;
Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
Map<String, String> options= new Hashtable<>();
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), IProposalRelevance.REMOVE_REDUNDANT_NULLNESS_ANNOTATION, image, context);
proposals.add(proposal);
}
public static void addAddMissingDefaultNullnessProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) throws CoreException {
final CompilationUnit astRoot= context.getASTRoot();
if (astRoot.getJavaElement().getElementName().equals(JavaModelUtil.PACKAGE_INFO_JAVA)) {
NullAnnotationsFix fix= NullAnnotationsFix.createAddMissingDefaultNullnessAnnotationsFix(astRoot, problem);
Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
Map<String, String> options= new Hashtable<>();
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), IProposalRelevance.ADD_MISSING_NULLNESS_ANNOTATION, image,
context);
proposals.add(proposal);
} else {
final IPackageFragment pack= (IPackageFragment) astRoot.getJavaElement().getParent();
String nonNullByDefaultAnnotationname= NullAnnotationsFix.getNonNullByDefaultAnnotationName(pack, true);
String label= Messages.format(CorrectionMessages.NullAnnotationsCorrectionProcessor_create_packageInfo_with_defaultnullness, new String[] { nonNullByDefaultAnnotationname });
proposals.add(CreatePackageInfoWithDefaultNullnessProposal.createFor(problem.getProblemId(), label, pack));
}
}
/**
* Fix for {@link IProblem#NullableFieldReference}
* @param context context
* @param problem problem to be fixed
* @param proposals accumulator for computed proposals
*/
public static void addExtractCheckedLocalProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
CompilationUnit compilationUnit = context.getASTRoot();
ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
SimpleName name= findProblemFieldName(selectedNode, problem.getProblemId());
if (name == null)
return;
ASTNode method= ASTNodes.getParent(selectedNode, MethodDeclaration.class);
if (method == null)
method= ASTNodes.getParent(selectedNode, Initializer.class);
if (method == null)
return;
proposals.add(new ExtractToNullCheckedLocalProposal(cu, compilationUnit, name, method));
}
private static SimpleName findProblemFieldName(ASTNode selectedNode, int problemID) {
// if a field access occurs in an compatibility situation (assignment/return/argument)
// with expected type declared @NonNull we first need to find the SimpleName inside:
if (selectedNode instanceof FieldAccess)
selectedNode= ((FieldAccess) selectedNode).getName();
else if (selectedNode instanceof QualifiedName)
selectedNode= ((QualifiedName) selectedNode).getName();
if (selectedNode instanceof SimpleName) {
SimpleName name= (SimpleName) selectedNode;
if (problemID == IProblem.NullableFieldReference)
return name;
// not field dereference, but compatibility issue - is value a field reference?
IBinding binding= name.resolveBinding();
if ((binding instanceof IVariableBinding) && ((IVariableBinding) binding).isField())
return name;
}
return null;
}
public static void addLocalVariableAnnotationProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
ICompilationUnit cu= context.getCompilationUnit();
CompilationUnit astRoot= context.getASTRoot();
String nonNullAnnotationName= NullAnnotationsFix.getNonNullAnnotationName(astRoot.getJavaElement(), false);
if (nonNullAnnotationName == null) {
return;
}
ASTNode selectedNode= problem.getCoveredNode(astRoot);
if (!(selectedNode instanceof Expression)) {
return;
}
Expression nodeToCast= (Expression) selectedNode;
IBinding callerBinding= Bindings.resolveExpressionBinding(nodeToCast, false);
if (callerBinding instanceof IVariableBinding) {
IVariableBinding variableBinding= (IVariableBinding) callerBinding;
if (variableBinding.isField()) {
return;
}
ITypeBinding type= variableBinding.getType();
if (type == null || type.isArray()) {
return;
}
proposals.add(new MakeLocalVariableNonNullProposal(cu, variableBinding, astRoot, IProposalRelevance.CHANGE_NULLNESS_ANNOTATION, nonNullAnnotationName));
}
}
private NullAnnotationsCorrectionProcessor() {
}
}