| /******************************************************************************* |
| * Copyright (c) 2015 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.jdt.internal.corext.fix; |
| |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NONNULL; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NO_ANNOTATION; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NULLABLE; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMember; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodParameterType; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodReturnType; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.extractGenericSignature; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.extractGenericTypeSignature; |
| import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.getAnnotationFile; |
| import static org.eclipse.jdt.internal.ui.text.spelling.WordCorrectionProposal.getHtmlRepresentation; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| |
| import org.eclipse.jdt.core.IClasspathAttribute; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.ArrayType; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.Dimension; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| 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.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.ParameterizedType; |
| import org.eclipse.jdt.core.dom.PrimitiveType; |
| import org.eclipse.jdt.core.dom.SimpleType; |
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.TypeParameter; |
| import org.eclipse.jdt.core.dom.VariableDeclaration; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.WildcardType; |
| import org.eclipse.jdt.core.util.ExternalAnnotationUtil; |
| import org.eclipse.jdt.core.util.ExternalAnnotationUtil.MergeStrategy; |
| |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; |
| import org.eclipse.jdt.ui.text.java.correction.ICommandAccess; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.JavaUIStatus; |
| import org.eclipse.jdt.internal.ui.text.correction.ExternalNullAnnotationQuickAssistProcessor; |
| import org.eclipse.jdt.internal.ui.text.correction.IProposalRelevance; |
| |
| /** |
| * Proposals for null annotations that modify external annotations, rather than Java source files. |
| * |
| * @see <a href="https://bugs.eclipse.org/458200">[null] "Annotate" proposals for adding external |
| * null annotations to library classes</a> |
| * @since 3.11 |
| */ |
| public class ExternalNullAnnotationChangeProposals { |
| |
| static final String CONSTRUCTOR_SELECTOR= "<init>"; //$NON-NLS-1$ |
| |
| static abstract class SignatureAnnotationChangeProposal implements IJavaCompletionProposal, ICommandAccess { |
| |
| protected String fLabel; |
| |
| protected ICompilationUnit fCU; // cu where the assist was invoked |
| |
| protected String fAffectedTypeName; |
| |
| protected IFile fAnnotationFile; |
| |
| protected String fSelector; |
| |
| protected String fSignature; |
| |
| protected String fCurrentAnnotated; |
| |
| protected String fAnnotatedSignature; |
| |
| protected MergeStrategy fMergeStrategy; |
| |
| protected String[] fDryRun; // result from a dry-run signature update; structure: { prefix, old-type, new-type, postfix } |
| |
| |
| /* return true if the operation is available. */ |
| protected boolean initializeOperation(ICompilationUnit cu, ITypeBinding declaringClass, String selector, |
| String plainSignature, String annotatedSignature, String label, MergeStrategy mergeStrategy) { |
| IJavaProject project= (IJavaProject) cu.getAncestor(IJavaElement.JAVA_PROJECT); |
| IFile file= null; |
| try { |
| file= getAnnotationFile(project, declaringClass, new NullProgressMonitor()); |
| } catch (CoreException e) { |
| return false; |
| } |
| if (file == null) |
| return false; |
| |
| fCU= cu; |
| fAffectedTypeName= declaringClass.getErasure().getBinaryName().replace('.', '/'); |
| fAnnotationFile= file; |
| fSelector= selector; |
| fAnnotatedSignature= annotatedSignature; |
| fSignature= plainSignature; |
| |
| fLabel= label; |
| fMergeStrategy= mergeStrategy; |
| |
| fCurrentAnnotated= ExternalAnnotationUtil.getAnnotatedSignature(fAffectedTypeName, file, fSelector, fSignature); |
| if (fCurrentAnnotated == null) |
| fCurrentAnnotated= fSignature; |
| dryRun(); |
| return fDryRun != null && !fDryRun[1].equals(fDryRun[2]); |
| } |
| |
| /** |
| * Perform a dry-run annotation update, to check if we have any update, indeed. If |
| * successful, the result should be available in {@link #fDryRun}. |
| */ |
| protected abstract void dryRun(); |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return null; // nothing to reveal in the current editor. |
| } |
| |
| @Override |
| public String getDisplayString() { |
| return fLabel; |
| } |
| |
| @Override |
| public Image getImage() { |
| return JavaPluginImages.get(JavaPluginImages.IMG_OBJS_ANNOTATION); |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return null; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| doAnnotateMember(new NullProgressMonitor()); |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| } catch (IOException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| @Override |
| public int getRelevance() { |
| return IProposalRelevance.CHANGE_METHOD; |
| } |
| |
| @Override |
| public String getCommandId() { |
| return ExternalNullAnnotationQuickAssistProcessor.ANNOTATE_MEMBER_ID; |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| StringBuffer buffer= new StringBuffer(); |
| buffer.append("<dl>"); //$NON-NLS-1$ |
| buffer.append("<dt>").append(getHtmlRepresentation(fSelector)).append("</dt>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append("<dd>").append(getHtmlRepresentation(fSignature)).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append("<dd>").append(getFullAnnotatedSignatureHTML()).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append("</dl>"); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| |
| protected String getFullAnnotatedSignatureHTML() { |
| String[] parts= fDryRun; |
| |
| // search the difference: |
| int pos= 0; |
| while (pos < parts[1].length() && pos < parts[2].length()) { |
| if (parts[1].charAt(pos) != parts[2].charAt(pos)) |
| break; |
| pos++; |
| } |
| |
| // prefix up-to the difference: |
| StringBuilder buf= new StringBuilder(); |
| buf.append(getHtmlRepresentation(parts[0])); |
| buf.append(getHtmlRepresentation(parts[2].substring(0, pos))); |
| |
| // highlight the difference: |
| switch (parts[2].charAt(pos)) { |
| case NULLABLE: |
| case NONNULL: |
| // added annotation in parts[2]: bold: |
| buf.append("<b>").append(parts[2].charAt(pos)).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| break; |
| default: |
| // removed annotation in parts[1]: strike: |
| buf.append("<del>").append(parts[1].charAt(pos)).append("</del>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| pos--; // char in parts[2] is not yet consumed |
| } |
| |
| // everything else: |
| buf.append(getHtmlRepresentation(parts[2].substring(pos + 1))); |
| buf.append(getHtmlRepresentation(parts[3])); |
| return buf.toString(); |
| } |
| |
| protected abstract void doAnnotateMember(IProgressMonitor monitor) throws CoreException, UnsupportedEncodingException, IOException; |
| } |
| |
| static class ReturnAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { |
| |
| @Override |
| protected void dryRun() { |
| fDryRun= ExternalAnnotationUtil.annotateReturnType(fCurrentAnnotated, fAnnotatedSignature, fMergeStrategy); |
| } |
| |
| @Override |
| protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException { |
| annotateMethodReturnType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fMergeStrategy, monitor); |
| } |
| } |
| |
| static class ParameterAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { |
| |
| int fParamIdx; |
| |
| ParameterAnnotationRewriteProposal(int paramIdx) { |
| fParamIdx= paramIdx; |
| } |
| |
| @Override |
| protected void dryRun() { |
| fDryRun= ExternalAnnotationUtil.annotateParameterType(fCurrentAnnotated, fAnnotatedSignature, fParamIdx, fMergeStrategy); |
| } |
| |
| @Override |
| protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException { |
| annotateMethodParameterType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fParamIdx, fMergeStrategy, monitor); |
| } |
| } |
| |
| static class FieldAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { |
| |
| @Override |
| protected void dryRun() { |
| fDryRun= ExternalAnnotationUtil.annotateType(fCurrentAnnotated, fAnnotatedSignature, fMergeStrategy); |
| } |
| |
| @Override |
| protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, UnsupportedEncodingException, IOException { |
| annotateMember(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fMergeStrategy, monitor); |
| } |
| } |
| |
| static class MissingBindingException extends RuntimeException { |
| private static final long serialVersionUID= 1L; |
| ASTNode fNode; |
| MissingBindingException(ASTNode/*Type or TypeParameter or MethodDeclaration*/ node) { |
| fNode= node; |
| } |
| @Override |
| public String getMessage() { |
| // check if compilation may have been aborted due to classpath trouble / unreportable problem: |
| ASTNode cu= ASTNodes.getParent(fNode, ASTNode.COMPILATION_UNIT); |
| if (cu instanceof CompilationUnit) { |
| for (IProblem problem : ((CompilationUnit) cu).getProblems()) { |
| if (problem.getID() == IProblem.IsClassPathCorrect || problem.getOriginatingFileName() == null) |
| return problem.getMessage(); |
| } |
| } |
| switch (fNode.getNodeType()) { |
| case ASTNode.METHOD_DECLARATION: |
| return "Could not resolve method "+fNode.toString(); //$NON-NLS-1$ |
| case ASTNode.VARIABLE_DECLARATION_FRAGMENT: |
| return "Could not resolve field "+fNode.toString(); //$NON-NLS-1$ |
| default: |
| return "Could not resolve type "+fNode.toString(); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| static ITypeBinding resolveBinding(TypeParameter type) { |
| ITypeBinding binding= type.resolveBinding(); |
| if (binding == null || binding.isRecovered()) throw new MissingBindingException(type); |
| return binding; |
| } |
| |
| static ITypeBinding resolveBinding(Type type) { |
| ITypeBinding binding= type.resolveBinding(); |
| if (binding == null || binding.isRecovered()) throw new MissingBindingException(type); |
| return binding; |
| } |
| |
| static IMethodBinding resolveBinding(MethodDeclaration method) { |
| IMethodBinding binding= method.resolveBinding(); |
| if (binding == null || binding.isRecovered()) throw new MissingBindingException(method); |
| return binding; |
| } |
| |
| static IVariableBinding resolveBinding(VariableDeclaration variable) { |
| IVariableBinding binding= variable.resolveBinding(); |
| if (binding == null || binding.isRecovered()) throw new MissingBindingException(variable); |
| return binding; |
| } |
| |
| /* Quick assist on class file, propose changes on any type detail. */ |
| public static void collectExternalAnnotationProposals(ICompilationUnit cu, ASTNode coveringNode, int offset, ArrayList<IJavaCompletionProposal> resultingCollection) { |
| |
| IJavaProject javaProject= cu.getJavaProject(); |
| if (JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, true))) |
| return; |
| |
| if (!hasAnnotationPathInWorkspace(javaProject, cu)) // refuse to update files outside the workspace |
| return; |
| |
| ASTNode inner= null; // the innermost type or type parameter node (to be annotated, unless annotating a dimension) |
| ASTNode outer= null; // will become the outermost type or type parameter node (to be traversed) |
| SingleVariableDeclaration variable= null; // when annotating extra dimension or varars this is where we get that additional info from |
| boolean annotateVarargs= false; |
| int extraDims= 0; // total number of extra dimensions |
| int outerExtraDims= 0; // number of outer extra dimension preceding the annotation position |
| |
| if (coveringNode instanceof Dimension && coveringNode.getLocationInParent() == SingleVariableDeclaration.EXTRA_DIMENSIONS2_PROPERTY) { |
| // annotating extra dimensions, remember dimension counts |
| variable= (SingleVariableDeclaration) coveringNode.getParent(); |
| outer= variable.getType(); |
| inner= variable.getType(); |
| List<?> extraDimensions= variable.extraDimensions(); |
| extraDims= extraDimensions.size(); |
| outerExtraDims= extraDimensions.indexOf(coveringNode); |
| } else if (coveringNode instanceof SingleVariableDeclaration) { |
| // annotating varargs ellipsis? |
| variable= (SingleVariableDeclaration) coveringNode; |
| outer= variable.getType(); |
| inner= variable.getType(); |
| if (variable.isVarargs()) { |
| Type type= variable.getType(); |
| if (offset < type.getStartPosition()+type.getLength()) |
| return; |
| if (offset+3 > variable.getName().getStartPosition()) |
| return; |
| annotateVarargs= true; |
| } else { |
| return; |
| } |
| } else { |
| // annotating 'normal' type? |
| while (true) { |
| if (coveringNode instanceof Type || coveringNode instanceof TypeParameter) { |
| inner= coveringNode; |
| break; |
| } |
| coveringNode= coveringNode.getParent(); |
| if (coveringNode == null) |
| return; |
| } |
| if (inner == null || inner.getNodeType() == ASTNode.PRIMITIVE_TYPE) |
| return; // cannot be annotated |
| outer= inner; |
| ASTNode next; |
| while (((next= outer.getParent()) instanceof Type) || (next instanceof TypeParameter)) |
| outer= next; |
| } |
| |
| // prepare three renderers for three proposals: |
| ASTNode typeToAnnotate = (!annotateVarargs && extraDims == 0) ? inner : null; |
| TypeRenderer rendererNonNull= new TypeRenderer(typeToAnnotate, offset, NONNULL); |
| TypeRenderer rendererNullable= new TypeRenderer(typeToAnnotate, offset, NULLABLE); |
| TypeRenderer rendererRemove= new TypeRenderer(typeToAnnotate, offset, NO_ANNOTATION); |
| |
| if (variable != null) { |
| // prepend dimensions which are not covered by type traversal below |
| if (variable.isVarargs()) { |
| rendererNonNull.addDimension(annotateVarargs); |
| rendererNullable.addDimension(annotateVarargs); |
| rendererRemove.addDimension(annotateVarargs); |
| } |
| for (int i= 0; i < extraDims; i++) { |
| rendererNonNull.addDimension(i == outerExtraDims); |
| rendererNullable.addDimension(i == outerExtraDims); |
| rendererRemove.addDimension(i == outerExtraDims); |
| } |
| } |
| boolean useJava8= JavaModelUtil.is18OrHigher(javaProject.getOption(JavaCore.COMPILER_SOURCE, true)); |
| if (!useJava8 && (outer != inner || outerExtraDims > 0)) { // below 1.8 we can only annotate the top type (not type parameter) |
| // still need to handle ParameterizedType (outer) with SimpleType (inner) |
| if (!(outer.getNodeType() == ASTNode.PARAMETERIZED_TYPE && inner.getParent() == outer)) |
| return; |
| } |
| try { |
| if (outer instanceof Type) { |
| if (extraDims == 0 && !annotateVarargs) { |
| ITypeBinding typeBinding= resolveBinding((Type) outer); |
| if (typeBinding.isPrimitive()) |
| return; |
| } |
| outer.accept(rendererNonNull); |
| outer.accept(rendererNullable); |
| outer.accept(rendererRemove); |
| } else { // type parameter |
| List<?> siblingList= (List<?>) outer.getParent().getStructuralProperty(outer.getLocationInParent()); |
| rendererNonNull.visitTypeParameters(siblingList); |
| rendererNullable.visitTypeParameters(siblingList); |
| rendererRemove.visitTypeParameters(siblingList); |
| } |
| |
| StructuralPropertyDescriptor locationInParent= outer.getLocationInParent(); |
| ProposalCreator creator= null; |
| if (locationInParent == MethodDeclaration.RETURN_TYPE2_PROPERTY) { |
| MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class); |
| creator= new ReturnProposalCreator(cu, resolveBinding(method)); |
| } else if (locationInParent == SingleVariableDeclaration.TYPE_PROPERTY) { |
| ASTNode param= outer.getParent(); |
| if (param.getLocationInParent() == MethodDeclaration.PARAMETERS_PROPERTY) { |
| MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class); |
| int paramIdx= method.parameters().indexOf(param); |
| if (paramIdx != -1) |
| creator= new ParameterProposalCreator(cu, resolveBinding(method), paramIdx); |
| } |
| } else if (locationInParent == FieldDeclaration.TYPE_PROPERTY) { |
| FieldDeclaration field= (FieldDeclaration) ASTNodes.getParent(coveringNode, FieldDeclaration.class); |
| if (field.fragments().size() > 0) { |
| VariableDeclarationFragment fragment= (VariableDeclarationFragment) field.fragments().get(0); |
| creator= new FieldProposalCreator(cu, resolveBinding(fragment)); |
| } |
| } |
| if (creator != null) { |
| createProposalsForType(cu, inner, extraDims, outerExtraDims, annotateVarargs, offset, |
| rendererNonNull, rendererNullable, rendererRemove, creator, resultingCollection); |
| } |
| } catch (MissingBindingException mbe) { |
| JavaPlugin.log(JavaUIStatus.createError(IStatus.ERROR, "Error during computation of Annotate proposals: "+mbe.getMessage(), mbe)); //$NON-NLS-1$ |
| return; |
| } |
| } |
| |
| static boolean hasAnnotationPathInWorkspace(IJavaProject javaProject, ICompilationUnit cu) { |
| IPackageFragmentRoot root= (IPackageFragmentRoot) cu.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); |
| if (root != null) { |
| try { |
| IClasspathEntry resolvedClasspathEntry= root.getResolvedClasspathEntry(); |
| for (IClasspathAttribute cpa : resolvedClasspathEntry.getExtraAttributes()) { |
| if (IClasspathAttribute.EXTERNAL_ANNOTATION_PATH.equals(cpa.getName())) { |
| Path annotationPath= new Path(cpa.getValue()); |
| IProject project= javaProject.getProject(); |
| if (project.exists(annotationPath)) |
| return true; |
| IWorkspaceRoot wsRoot= project.getWorkspace().getRoot(); |
| return wsRoot.exists(annotationPath); |
| } |
| } |
| } catch (JavaModelException jme) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| private static abstract class ProposalCreator { |
| |
| ICompilationUnit fCU; |
| |
| ITypeBinding fDeclaringClass; |
| |
| String fSelector; |
| |
| String fSignature; |
| |
| MergeStrategy fMergeStrategy= MergeStrategy.OVERWRITE_ANNOTATIONS; |
| |
| ProposalCreator(ICompilationUnit cu, ITypeBinding declaringClass, String selector, String signature) { |
| fCU= cu; |
| fDeclaringClass= declaringClass; |
| fSelector= selector; |
| fSignature= signature; |
| } |
| |
| SignatureAnnotationChangeProposal create(String annotatedSignature, String label) { |
| SignatureAnnotationChangeProposal operation= doCreate(annotatedSignature, label); |
| if (!operation.initializeOperation(fCU, fDeclaringClass, fSelector, fSignature, annotatedSignature, label, fMergeStrategy)) |
| return null; |
| return operation; |
| } |
| |
| abstract SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label); |
| } |
| |
| private static class ReturnProposalCreator extends ProposalCreator { |
| |
| ReturnProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding) { |
| super(cu, methodBinding.getDeclaringClass(), methodBinding.getName(), extractGenericSignature(methodBinding)); |
| } |
| |
| @Override |
| SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { |
| return new ReturnAnnotationRewriteProposal(); |
| } |
| } |
| |
| private static class ParameterProposalCreator extends ProposalCreator { |
| int fParamIdx; |
| |
| ParameterProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding, int paramIdx) { |
| super(cu, methodBinding.getDeclaringClass(), |
| methodBinding.isConstructor() ? CONSTRUCTOR_SELECTOR : methodBinding.getName(), |
| extractGenericSignature(methodBinding)); |
| fParamIdx= paramIdx; |
| } |
| |
| @Override |
| SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { |
| return new ParameterAnnotationRewriteProposal(fParamIdx); |
| } |
| } |
| |
| private static class FieldProposalCreator extends ProposalCreator { |
| |
| FieldProposalCreator(ICompilationUnit cu, IVariableBinding fieldBinding) { |
| super(cu, fieldBinding.getDeclaringClass(), fieldBinding.getName(), extractGenericTypeSignature(fieldBinding.getType())); |
| } |
| |
| @Override |
| SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { |
| return new FieldAnnotationRewriteProposal(); |
| } |
| } |
| |
| /* Create one proposal from each of the three given renderers. */ |
| static void createProposalsForType(ICompilationUnit cu, ASTNode type, int dims, |
| int outerDims, boolean annotateVarargs, int offset, |
| TypeRenderer rendererNonNull, TypeRenderer rendererNullable, TypeRenderer rendererRemove, ProposalCreator creator, ArrayList<IJavaCompletionProposal> resultingCollection) { |
| SignatureAnnotationChangeProposal operation; |
| String label; |
| // propose adding @NonNull: |
| label= getAddAnnotationLabel(NullAnnotationsFix.getNonNullAnnotationName(cu, true), type, dims, outerDims, annotateVarargs, offset); |
| operation= creator.create(rendererNonNull.getResult(), label); |
| if (operation != null) |
| resultingCollection.add(operation); |
| |
| // propose adding @Nullable: |
| label= getAddAnnotationLabel(NullAnnotationsFix.getNullableAnnotationName(cu, true), type, dims, outerDims, annotateVarargs, offset); |
| operation= creator.create(rendererNullable.getResult(), label); |
| if (operation != null) |
| resultingCollection.add(operation); |
| |
| // propose removing annotation: |
| label= Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_remove_nullness_annotation, |
| new String[] { type2String(type, offset) }); |
| operation= creator.create(rendererRemove.getResult(), label); |
| if (operation != null) |
| resultingCollection.add(operation); |
| } |
| |
| static String getAddAnnotationLabel(String annotationName, ASTNode type, int dims, int outerDims, boolean annotateVarargs, int offset) { |
| StringBuilder left= null; |
| StringBuilder dimsRight= null; |
| if (type.getNodeType() == ASTNode.ARRAY_TYPE) { |
| // find the insertion point using the text offset: |
| ArrayType arrayType= (ArrayType) type; |
| left= new StringBuilder(arrayType.getElementType().toString()); |
| dimsRight= new StringBuilder(); |
| @SuppressWarnings("rawtypes") |
| List dimensions= arrayType.dimensions(); |
| for (int i= 0; i < dimensions.size(); i++) { |
| Dimension dimension= (Dimension) dimensions.get(i); |
| if (dimension.getStartPosition() + dimension.getLength() <= offset) |
| left.append("[]"); //$NON-NLS-1$ |
| else |
| dimsRight.append("[]"); //$NON-NLS-1$ |
| } |
| } else if (dims > 0) { |
| // find then insertion point using the dimension counts: |
| left= new StringBuilder(type.toString()); |
| dimsRight= new StringBuilder(); |
| for (int i= 0; i < dims; i++) { |
| if (i < outerDims) |
| left.append("[]"); //$NON-NLS-1$ |
| else |
| dimsRight.append("[]"); //$NON-NLS-1$ |
| } |
| } else if (annotateVarargs) { |
| left = new StringBuilder(type.toString()); |
| dimsRight = new StringBuilder(); |
| } |
| if (left != null && dimsRight != null) { |
| if (annotateVarargs) |
| dimsRight.append("..."); //$NON-NLS-1$ |
| // need to assemble special format with annotation attached to the selected dimension: |
| return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_array_annotation, |
| new String[] { left.toString(), annotationName, dimsRight.toString() }); |
| } |
| return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_annotation, |
| new String[] { annotationName, type.toString() }); |
| } |
| |
| static String type2String(ASTNode type, int offset) { |
| if (type.getNodeType() == ASTNode.ARRAY_TYPE) { |
| ArrayType arrayType= (ArrayType) type; |
| StringBuilder buf= new StringBuilder(arrayType.getElementType().toString()); |
| @SuppressWarnings("rawtypes") |
| List dimensions= arrayType.dimensions(); |
| for (int i= 0; i < dimensions.size(); i++) { |
| Dimension dimension= (Dimension) dimensions.get(i); |
| if (dimension.getStartPosition() + dimension.getLength() > offset) |
| buf.append("[]"); //$NON-NLS-1$ |
| } |
| return buf.toString(); |
| } |
| return type.toString(); |
| } |
| |
| /** |
| * A visitor that renders an AST snippet representing a type or type parameter. For rendering |
| * the Eclipse External Annotation format is used, i.e., class file signatures with additions |
| * for null annotations. |
| * <p> |
| * In particular a given null annotation is inserted for the given focusType. |
| * </p> |
| */ |
| static class TypeRenderer extends ASTVisitor { |
| |
| StringBuffer fBuffer; |
| |
| ASTNode fFocusType; // Type or TypeParameter |
| |
| int fOffset; |
| |
| char fAnnotation; |
| |
| public TypeRenderer(ASTNode focusType, int offset, char annotation) { |
| fBuffer= new StringBuffer(); |
| fFocusType= focusType; |
| fOffset= offset; |
| fAnnotation= annotation; |
| } |
| |
| public void addDimension(boolean annotate) { |
| fBuffer.append('['); |
| if (annotate) |
| fBuffer.append(fAnnotation); |
| } |
| public String getResult() { |
| return fBuffer.toString(); |
| } |
| |
| /* Renders a type parameter list in angle brackets. */ |
| public void visitTypeParameters(@SuppressWarnings("rawtypes") List parameters) { |
| fBuffer.append('<'); |
| for (Object p : parameters) |
| ((TypeParameter) p).accept(this); |
| fBuffer.append('>'); |
| } |
| |
| @Override |
| public boolean visit(ParameterizedType type) { |
| fBuffer.append('L'); |
| if (type == fFocusType || type.getType() == fFocusType) |
| fBuffer.append(fAnnotation); |
| fBuffer.append(binaryName(resolveBinding(type))); |
| fBuffer.append('<'); |
| for (Object arg : type.typeArguments()) |
| ((Type) arg).accept(this); |
| fBuffer.append('>'); |
| fBuffer.append(';'); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(WildcardType wildcard) { |
| Type bound= wildcard.getBound(); |
| if (bound == null) { |
| fBuffer.append('*'); |
| } else if (wildcard.isUpperBound()) { |
| fBuffer.append('+'); |
| } else { |
| fBuffer.append('-'); |
| } |
| if (wildcard == fFocusType) |
| fBuffer.append(fAnnotation); |
| if (bound != null) |
| bound.accept(this); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(ArrayType array) { |
| @SuppressWarnings("rawtypes") |
| List dimensions= array.dimensions(); |
| boolean annotated= false; |
| for (int i= 0; i < dimensions.size(); i++) { |
| fBuffer.append('['); |
| Dimension dimension= (Dimension) dimensions.get(i); |
| if (!annotated && array == fFocusType && dimension.getStartPosition() + dimension.getLength() > fOffset) { |
| fBuffer.append(fAnnotation); |
| annotated= true; |
| } |
| } |
| array.getElementType().accept(this); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(TypeParameter parameter) { |
| if (parameter == fFocusType) |
| fBuffer.append(fAnnotation); |
| fBuffer.append(parameter.getName().getIdentifier()); |
| Type classBound= null; |
| for (Object bound : parameter.typeBounds()) { |
| Type typeBound= (Type) bound; |
| if (resolveBinding(typeBound).isClass()) { |
| classBound= typeBound; |
| break; |
| } |
| } |
| if (classBound != null) { |
| fBuffer.append(':'); |
| classBound.accept(this); |
| } else { |
| ITypeBinding typeBinding= resolveBinding(parameter); |
| fBuffer.append(":L").append(binaryName(typeBinding.getSuperclass())).append(';'); //$NON-NLS-1$ |
| } |
| for (Object bound : parameter.typeBounds()) { |
| if (bound == classBound) |
| continue; |
| Type typeBound= (Type) bound; |
| fBuffer.append(':'); |
| typeBound.accept(this); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(SimpleType type) { |
| ITypeBinding typeBinding= resolveBinding(type); |
| if (typeBinding.isTypeVariable()) { |
| fBuffer.append('T'); |
| if (fFocusType == type) |
| fBuffer.append(fAnnotation); |
| fBuffer.append(typeBinding.getName()).append(';'); |
| } else { |
| fBuffer.append('L'); |
| if (fFocusType == type) |
| fBuffer.append(fAnnotation); |
| fBuffer.append(binaryName(typeBinding)).append(';'); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(PrimitiveType node) { |
| // not a legal focus type, but could be array element type |
| fBuffer.append(resolveBinding(node).getBinaryName()); |
| return false; |
| } |
| |
| String binaryName(ITypeBinding type) { |
| return type.getBinaryName().replace('.', '/'); |
| } |
| } |
| } |