blob: b27b8452853ec5c96d835606246e6332ba040b2a [file] [log] [blame]
/*******************************************************************************
* 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('.', '/');
}
}
}