Java tutorial
/******************************************************************************* * Copyright (c) 2011 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 - initial API and implementation *******************************************************************************/ package org.eclipse.objectteams.internal.jdt.nullity.quickfix; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.RequiredNonNullButProvidedNull; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.RequiredNonNullButProvidedPotentialNull; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.RequiredNonNullButProvidedUnknown; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.IllegalDefinitionToNonNullParameter; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.IllegalRedefinitionToNonNullParameter; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.IllegalReturnNullityRedefinition; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.ParameterLackingNonNullAnnotation; import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem.ParameterLackingNullableAnnotation; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; 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.IBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation; import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal; import org.eclipse.jdt.ui.cleanup.ICleanUpFix; import org.eclipse.jdt.ui.text.java.IInvocationContext; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.IProblemLocation; import org.eclipse.objectteams.internal.jdt.nullity.NullCompilerOptions; import org.eclipse.swt.graphics.Image; /** * Quickfixes for null-annotation related problems. * * @author stephan */ @SuppressWarnings("restriction") public class QuickFixes implements org.eclipse.jdt.ui.text.java.IQuickFixProcessor { /** Small adaptation just to make available the 'compilationUnit' passed at instantiation time. */ static class MyCURewriteOperationsFix extends CompilationUnitRewriteOperationsFix { CompilationUnit cu; public MyCURewriteOperationsFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] operations) { super(name, compilationUnit, operations); this.cu = compilationUnit; } } public boolean hasCorrections(ICompilationUnit cu, int problemId) { switch (problemId) { case RequiredNonNullButProvidedNull: case RequiredNonNullButProvidedPotentialNull: case RequiredNonNullButProvidedUnknown: case IllegalReturnNullityRedefinition: case IllegalRedefinitionToNonNullParameter: case IllegalDefinitionToNonNullParameter: case ParameterLackingNonNullAnnotation: case ParameterLackingNullableAnnotation: case IProblem.NonNullLocalVariableComparisonYieldsFalse: case IProblem.RedundantNullCheckOnNonNullLocalVariable: return true; default: return false; } } /* (non-Javadoc) * @see IAssistProcessor#getCorrections(org.eclipse.jdt.internal.ui.text.correction.IAssistContext, org.eclipse.jdt.internal.ui.text.correction.IProblemLocation[]) */ public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations) throws CoreException { if (locations == null || locations.length == 0) { return null; } HashSet<Integer> handledProblems = new HashSet<Integer>(locations.length); ArrayList<IJavaCompletionProposal> resultingCollections = new ArrayList<IJavaCompletionProposal>(); for (int i = 0; i < locations.length; i++) { IProblemLocation curr = locations[i]; Integer id = new Integer(curr.getProblemId()); if (handledProblems.add(id)) { process(context, curr, resultingCollections); } } return (IJavaCompletionProposal[]) resultingCollections .toArray(new IJavaCompletionProposal[resultingCollections.size()]); } void process(IInvocationContext context, IProblemLocation problem, Collection<IJavaCompletionProposal> proposals) { int id = problem.getProblemId(); if (id == 0) { // no proposals for none-problem locations return; } CompilationUnit astRoot = context.getASTRoot(); ASTNode selectedNode = problem.getCoveringNode(astRoot); switch (id) { case IllegalReturnNullityRedefinition: case IllegalDefinitionToNonNullParameter: case IllegalRedefinitionToNonNullParameter: addNullAnnotationInSignatureProposal(context, problem, proposals, false); addNullAnnotationInSignatureProposal(context, problem, proposals, true); break; case RequiredNonNullButProvidedNull: case RequiredNonNullButProvidedPotentialNull: case RequiredNonNullButProvidedUnknown: case ParameterLackingNonNullAnnotation: case ParameterLackingNullableAnnotation: case IProblem.NonNullLocalVariableComparisonYieldsFalse: case IProblem.RedundantNullCheckOnNonNullLocalVariable: if (isComplainingAboutArgument(selectedNode) || isComplainingAboutReturn(selectedNode)) addNullAnnotationInSignatureProposal(context, problem, proposals, false); break; } } @SuppressWarnings("unchecked") void addNullAnnotationInSignatureProposal(IInvocationContext context, IProblemLocation problem, @SuppressWarnings("rawtypes") Collection proposals, boolean modifyOverridden) { MyCURewriteOperationsFix fix = createNullAnnotationInSignatureFix(context.getASTRoot(), problem, modifyOverridden); if (fix != null) { Image image = JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE); Map<String, String> options = new Hashtable<String, String>(); if (fix.cu != context.getASTRoot()) { // workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..) final CompilationUnit cu = fix.cu; final IInvocationContext originalContext = context; context = new IInvocationContext() { public int getSelectionOffset() { return originalContext.getSelectionOffset(); } public int getSelectionLength() { return originalContext.getSelectionLength(); } public ASTNode getCoveringNode() { return originalContext.getCoveringNode(); } public ASTNode getCoveredNode() { return originalContext.getCoveredNode(); } public ICompilationUnit getCompilationUnit() { return (ICompilationUnit) cu.getJavaElement(); } public CompilationUnit getASTRoot() { return cu; } }; } int relevance = modifyOverridden ? 15 : 20; // TODO(SH): insert suitable values, just raise local change above change in overridden method FixCorrectionProposal proposal = new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, this, problem.getProblemId()), relevance, image, context); proposals.add(proposal); } } boolean isComplainingAboutArgument(ASTNode selectedNode) { if (!(selectedNode instanceof SimpleName)) { return false; } SimpleName nameNode = (SimpleName) selectedNode; IBinding binding = nameNode.resolveBinding(); if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter()) return true; VariableDeclaration argDecl = (VariableDeclaration) ASTNodes.getParent(selectedNode, VariableDeclaration.class); if (argDecl != null) binding = argDecl.resolveBinding(); if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter()) return true; return false; } boolean isComplainingAboutReturn(ASTNode selectedNode) { return selectedNode.getParent().getNodeType() == ASTNode.RETURN_STATEMENT; } MyCURewriteOperationsFix createNullAnnotationInSignatureFix(CompilationUnit compilationUnit, IProblemLocation problem, boolean modifyOverridden) { String nullableAnnotationName = getNullableAnnotationName(compilationUnit.getJavaElement(), false); String nonNullAnnotationName = getNonNullAnnotationName(compilationUnit.getJavaElement(), false); String annotationToAdd = nullableAnnotationName; String annotationToRemove = nonNullAnnotationName; switch (problem.getProblemId()) { case IllegalDefinitionToNonNullParameter: case IllegalRedefinitionToNonNullParameter: // case ParameterLackingNullableAnnotation: // never proposed with modifyOverridden if (modifyOverridden) { annotationToAdd = nonNullAnnotationName; annotationToRemove = nullableAnnotationName; } break; case ParameterLackingNonNullAnnotation: case IllegalReturnNullityRedefinition: if (!modifyOverridden) { annotationToAdd = nonNullAnnotationName; annotationToRemove = nullableAnnotationName; } break; // all others propose to add @Nullable } // when performing one change at a time we can actually modify another CU than the current one: RewriteOperations.SignatureAnnotationRewriteOperation operation = RewriteOperations .createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, null, false/*thisUnitOnly*/, true/*allowRemove*/, modifyOverridden); if (operation == null) return null; return new MyCURewriteOperationsFix(operation.getMessage(), operation.getCompilationUnit(), // note that this uses the findings from createAddAnnotationOperation(..) new RewriteOperations.SignatureAnnotationRewriteOperation[] { operation }); } // Entry for NullAnnotationsCleanup: public ICleanUpFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] locations, int problemID) { ICompilationUnit cu = (ICompilationUnit) compilationUnit.getJavaElement(); if (!JavaModelUtil.is50OrHigher(cu.getJavaProject())) return null; List<CompilationUnitRewriteOperation> operations = new ArrayList<CompilationUnitRewriteOperation>(); if (locations == null) { org.eclipse.jdt.core.compiler.IProblem[] problems = compilationUnit.getProblems(); locations = new IProblemLocation[problems.length]; for (int i = 0; i < problems.length; i++) { if (problems[i].getID() == problemID) locations[i] = new ProblemLocation(problems[i]); } } createAddNullAnnotationOperations(compilationUnit, locations, operations); if (operations.size() == 0) return null; CompilationUnitRewriteOperation[] operationsArray = operations .toArray(new CompilationUnitRewriteOperation[operations.size()]); return new MyCURewriteOperationsFix(FixMessages.QuickFixes_add_annotation_change_name, compilationUnit, operationsArray); } @SuppressWarnings({ "rawtypes", "unchecked" }) void createAddNullAnnotationOperations(CompilationUnit compilationUnit, IProblemLocation[] locations, List result) { String nullableAnnotationName = getNullableAnnotationName(compilationUnit.getJavaElement(), false); String nonNullAnnotationName = getNonNullAnnotationName(compilationUnit.getJavaElement(), false); Set<String> handledPositions = new HashSet<String>(); for (int i = 0; i < locations.length; i++) { IProblemLocation problem = locations[i]; if (problem == null) continue; // problem was filtered out by createCleanUp() String annotationToAdd = nullableAnnotationName; String annotationToRemove = nonNullAnnotationName; switch (problem.getProblemId()) { case IllegalDefinitionToNonNullParameter: case IllegalRedefinitionToNonNullParameter: case ParameterLackingNonNullAnnotation: case IllegalReturnNullityRedefinition: annotationToAdd = nonNullAnnotationName; annotationToRemove = nullableAnnotationName; // all others propose to add @Nullable } // when performing multiple changes we can only modify the one CU that the CleanUp infrastructure provides to the operation. CompilationUnitRewriteOperation fix = RewriteOperations.createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions, true/*thisUnitOnly*/, false/*allowRemove*/, false/*modifyOverridden*/); if (fix != null) result.add(fix); } } public static boolean isMissingNullAnnotationProblem(int id) { return id == RequiredNonNullButProvidedNull || id == RequiredNonNullButProvidedPotentialNull || id == IllegalReturnNullityRedefinition || mayIndicateParameterNullcheck(id); } public static boolean mayIndicateParameterNullcheck(int problemId) { return problemId == IProblem.NonNullLocalVariableComparisonYieldsFalse || problemId == IProblem.RedundantNullCheckOnNonNullLocalVariable; } public static boolean hasExplicitNullAnnotation(ICompilationUnit compilationUnit, int offset) { // FIXME(SH): check for existing annotations disabled due to lack of precision: // should distinguish what is actually annotated (return? param? which?) // try { // IJavaElement problemElement = compilationUnit.getElementAt(offset); // if (problemElement.getElementType() == IJavaElement.METHOD) { // IMethod method = (IMethod) problemElement; // String nullable = getNullableAnnotationName(compilationUnit, true); // String nonnull = getNonNullAnnotationName(compilationUnit, true); // for (IAnnotation annotation : method.getAnnotations()) { // if ( annotation.getElementName().equals(nonnull) // || annotation.getElementName().equals(nullable)) // return true; // } // } // } catch (JavaModelException jme) { // /* nop */ // } return false; } public static String getNullableAnnotationName(IJavaElement javaElement, boolean makeSimple) { String qualifiedName = javaElement.getJavaProject() .getOption(NullCompilerOptions.OPTION_NullableAnnotationName, true); int lastDot; if (makeSimple && qualifiedName != null && (lastDot = qualifiedName.lastIndexOf('.')) != -1) return qualifiedName.substring(lastDot + 1); return qualifiedName; } public static String getNonNullAnnotationName(IJavaElement javaElement, boolean makeSimple) { String qualifiedName = javaElement.getJavaProject() .getOption(NullCompilerOptions.OPTION_NonNullAnnotationName, true); int lastDot; if (makeSimple && qualifiedName != null && (lastDot = qualifiedName.lastIndexOf('.')) != -1) return qualifiedName.substring(lastDot + 1); return qualifiedName; } }