blob: e777ef75aa9ba494b74cd71b7229db87945b9483 [file] [log] [blame]
package org.checkerframework.framework.type;
import com.sun.tools.javac.code.Type;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeKind;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
import org.checkerframework.framework.util.AnnotationFormatter;
import org.checkerframework.framework.util.DefaultAnnotationFormatter;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* An AnnotatedTypeFormatter used by default by all AnnotatedTypeFactory (and therefore all
* annotated types).
*
* @see org.checkerframework.framework.type.AnnotatedTypeFormatter
* @see org.checkerframework.framework.type.AnnotatedTypeMirror#toString
*/
public class DefaultAnnotatedTypeFormatter implements AnnotatedTypeFormatter {
protected final FormattingVisitor formattingVisitor;
/**
* Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by
* default.
*/
public DefaultAnnotatedTypeFormatter() {
this(new DefaultAnnotationFormatter(), true, false);
}
/**
* @param printVerboseGenerics for type parameters, their uses, and wildcards, print more
* information
* @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print
* invisible annotations
*/
public DefaultAnnotatedTypeFormatter(
boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) {
this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos);
}
/**
* @param formatter an object that converts annotation mirrors to strings
* @param printVerboseGenerics for type parameters, their uses, and wildcards, print more
* information
* @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print
* invisible annotations
*/
public DefaultAnnotatedTypeFormatter(
AnnotationFormatter formatter,
boolean printVerboseGenerics,
boolean defaultPrintInvisibleAnnos) {
this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos));
}
/**
* Used by subclasses and other constructors to specify the underlying implementation of this
* DefaultAnnotatedTypeFormatter.
*/
protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) {
this.formattingVisitor = visitor;
}
@Override
public String format(final AnnotatedTypeMirror type) {
formattingVisitor.resetPrintVerboseSettings();
return formattingVisitor.visit(type);
}
@Override
public String format(final AnnotatedTypeMirror type, final boolean printVerbose) {
formattingVisitor.setVerboseSettings(printVerbose);
return formattingVisitor.visit(type);
}
/** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */
protected static class FormattingVisitor
implements AnnotatedTypeVisitor<String, Set<AnnotatedTypeMirror>> {
/** The object responsible for converting annotations to strings. */
protected final AnnotationFormatter annoFormatter;
/**
* Represents whether or not invisible annotations should be printed if the client of this class
* does not use the printInvisibleAnnos parameter.
*/
protected final boolean defaultInvisiblesSetting;
/**
* For a given call to format, this setting specifies whether or not to printInvisibles. If a
* user did not specify a printInvisible parameter in the call to format then this value will
* equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object
*/
protected boolean currentPrintInvisibleSetting;
/** Default value of currentPrintVerboseGenerics. */
protected final boolean defaultPrintVerboseGenerics;
/**
* Prints type variables in a less ambiguous manner using [] to delimit them. Always prints both
* bounds even if they lower bound is an AnnotatedNull type.
*/
protected boolean currentPrintVerboseGenerics;
public FormattingVisitor(
AnnotationFormatter annoFormatter,
boolean printVerboseGenerics,
boolean defaultInvisiblesSetting) {
this.annoFormatter = annoFormatter;
this.defaultPrintVerboseGenerics = printVerboseGenerics;
this.currentPrintVerboseGenerics = printVerboseGenerics;
this.defaultInvisiblesSetting = defaultInvisiblesSetting;
this.currentPrintInvisibleSetting = false;
}
/** Set the current verbose settings to use while printing. */
protected void setVerboseSettings(boolean printVerbose) {
this.currentPrintInvisibleSetting = printVerbose;
this.currentPrintVerboseGenerics = printVerbose;
}
/** Set verbose settings to the default. */
protected void resetPrintVerboseSettings() {
this.currentPrintInvisibleSetting = defaultInvisiblesSetting;
this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics;
}
/**
* Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with
* their annotations followed by " Void"
*/
@SideEffectFree
protected void printBound(
final String keyWord,
final AnnotatedTypeMirror field,
final Set<AnnotatedTypeMirror> visiting,
final StringBuilder sb) {
if (!currentPrintVerboseGenerics && (field == null || field.getKind() == TypeKind.NULL)) {
return;
}
sb.append(" ");
sb.append(keyWord);
sb.append(" ");
if (field == null) {
sb.append("<null>");
} else if (field.getKind() != TypeKind.NULL) {
sb.append(visit(field, visiting));
} else {
sb.append(
annoFormatter.formatAnnotationString(
field.getAnnotations(), currentPrintInvisibleSetting));
sb.append("Void");
}
}
@SideEffectFree
@Override
public String visit(AnnotatedTypeMirror type) {
return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>()));
}
@Override
public String visit(AnnotatedTypeMirror type, Set<AnnotatedTypeMirror> annotatedTypeVariables) {
return type.accept(this, annotatedTypeVariables);
}
@Override
public String visitDeclared(AnnotatedDeclaredType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
if (type.isDeclaration() && currentPrintInvisibleSetting) {
sb.append("/*DECL*/ ");
}
if (type.getEnclosingType() != null) {
sb.append(this.visit(type.getEnclosingType(), visiting));
sb.append('.');
}
final Element typeElt = type.getUnderlyingType().asElement();
String smpl = typeElt.getSimpleName().toString();
if (smpl.isEmpty()) {
// For anonymous classes smpl is empty - toString
// of the element is more useful.
smpl = typeElt.toString();
}
sb.append(
annoFormatter.formatAnnotationString(
type.getAnnotations(), currentPrintInvisibleSetting));
sb.append(smpl);
if (type.typeArgs != null) {
// getTypeArguments sets the field if it does not already exist.
final List<AnnotatedTypeMirror> typeArgs = type.typeArgs;
if (!typeArgs.isEmpty()) {
StringJoiner sj = new StringJoiner(", ", "<", ">");
for (AnnotatedTypeMirror typeArg : typeArgs) {
sj.add(visit(typeArg, visiting));
}
sb.append(sj);
}
}
return sb.toString();
}
@Override
public String visitIntersection(
AnnotatedIntersectionType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (AnnotatedTypeMirror bound : type.getBounds()) {
if (!isFirst) {
sb.append(" & ");
}
sb.append(visit(bound, visiting));
isFirst = false;
}
return sb.toString();
}
@Override
public String visitUnion(AnnotatedUnionType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (AnnotatedDeclaredType adt : type.getAlternatives()) {
if (!isFirst) {
sb.append(" | ");
}
sb.append(visit(adt, visiting));
isFirst = false;
}
return sb.toString();
}
@Override
public String visitExecutable(AnnotatedExecutableType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
if (!type.getTypeVariables().isEmpty()) {
StringJoiner sj = new StringJoiner(", ", "<", "> ");
for (AnnotatedTypeVariable atv : type.getTypeVariables()) {
sj.add(visit(atv, visiting));
}
sb.append(sj.toString());
}
if (type.getReturnType() != null) {
sb.append(visit(type.getReturnType(), visiting));
} else {
sb.append("<UNKNOWNRETURN>");
}
sb.append(' ');
if (type.getElement() != null) {
sb.append(type.getElement().getSimpleName());
} else {
sb.append("METHOD");
}
sb.append('(');
AnnotatedDeclaredType rcv = type.getReceiverType();
if (rcv != null) {
sb.append(visit(rcv, visiting));
sb.append(" this");
}
if (!type.getParameterTypes().isEmpty()) {
int p = 0;
for (AnnotatedTypeMirror atm : type.getParameterTypes()) {
if (rcv != null || p > 0) {
sb.append(", ");
}
sb.append(visit(atm, visiting));
// Output some parameter names to make it look more like a method.
// TODO: go to the element and look up real parameter names, maybe.
sb.append(" p");
sb.append(p++);
}
}
sb.append(')');
if (!type.getThrownTypes().isEmpty()) {
sb.append(" throws ");
for (AnnotatedTypeMirror atm : type.getThrownTypes()) {
sb.append(visit(atm, visiting));
}
}
return sb.toString();
}
@Override
public String visitArray(AnnotatedArrayType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
AnnotatedArrayType array = type;
AnnotatedTypeMirror component;
while (true) {
component = array.getComponentType();
if (!array.getAnnotations().isEmpty()) {
sb.append(' ');
sb.append(
annoFormatter.formatAnnotationString(
array.getAnnotations(), currentPrintInvisibleSetting));
}
sb.append("[]");
if (!(component instanceof AnnotatedArrayType)) {
sb.insert(0, visit(component, visiting));
break;
}
array = (AnnotatedArrayType) component;
}
return sb.toString();
}
@Override
public String visitTypeVariable(AnnotatedTypeVariable type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
if (TypesUtils.isCaptured(type.underlyingType)) {
String underlyingType = type.underlyingType.toString();
// underlyingType has this form: "capture#826 of ? extends java.lang.Object".
// We output only the "capture#826" part.
// NOTE: The number is the hash code of the captured type, so it's nondeterministic, but it
// is still important to print it in order to tell the difference between two captured
// types.
sb.append(underlyingType, 0, underlyingType.indexOf(" of "));
} else {
sb.append(type.underlyingType);
}
if (!visiting.contains(type)) {
if (type.isDeclaration() && currentPrintInvisibleSetting) {
sb.append("/*DECL*/ ");
}
try {
visiting.add(type);
if (currentPrintVerboseGenerics) {
sb.append("[");
}
printBound("extends", type.getUpperBoundField(), visiting, sb);
printBound("super", type.getLowerBoundField(), visiting, sb);
if (currentPrintVerboseGenerics) {
sb.append("]");
}
} finally {
visiting.remove(type);
}
}
return sb.toString();
}
@SideEffectFree
@Override
public String visitPrimitive(AnnotatedPrimitiveType type, Set<AnnotatedTypeMirror> visiting) {
return formatFlatType(type);
}
@SideEffectFree
@Override
public String visitNoType(AnnotatedNoType type, Set<AnnotatedTypeMirror> visiting) {
return formatFlatType(type);
}
@SideEffectFree
@Override
public String visitNull(AnnotatedNullType type, Set<AnnotatedTypeMirror> visiting) {
return annoFormatter.formatAnnotationString(
type.getAnnotations(), currentPrintInvisibleSetting)
+ "NullType";
}
@Override
public String visitWildcard(AnnotatedWildcardType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
if (type.isUninferredTypeArgument()) {
sb.append("/*INFERENCE FAILED for:*/ ");
}
sb.append(
annoFormatter.formatAnnotationString(
type.getAnnotationsField(), currentPrintInvisibleSetting));
sb.append("?");
if (!visiting.contains(type)) {
try {
visiting.add(type);
if (currentPrintVerboseGenerics) {
sb.append("[");
}
printBound("extends", type.getExtendsBoundField(), visiting, sb);
printBound("super", type.getSuperBoundField(), visiting, sb);
if (currentPrintVerboseGenerics) {
sb.append("]");
}
} finally {
visiting.remove(type);
}
}
return sb.toString();
}
@SideEffectFree
protected String formatFlatType(final AnnotatedTypeMirror flatType) {
return annoFormatter.formatAnnotationString(
flatType.getAnnotations(), currentPrintInvisibleSetting)
+ TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType());
}
}
}