package org.checkerframework.framework.type;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
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.framework.util.PluginUtil;
import org.checkerframework.javacutil.TypeAnnotationUtils;
/**
* 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 keyWord followed by 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<AnnotatedTypeMirror, Boolean>()));
}
@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()) {
sb.append("<");
boolean isFirst = true;
for (AnnotatedTypeMirror typeArg : typeArgs) {
if (!isFirst) {
sb.append(", ");
}
sb.append(visit(typeArg, visiting));
isFirst = false;
}
sb.append(">");
}
}
return sb.toString();
}
@Override
public String visitIntersection(
AnnotatedIntersectionType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (AnnotatedDeclaredType adt : type.directSuperTypes()) {
if (!isFirst) sb.append(" & ");
sb.append(visit(adt, 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()) {
sb.append('<');
List<String> typeVars = new ArrayList<>(type.getTypeVariables().size());
for (AnnotatedTypeVariable atv : type.getTypeVariables()) {
typeVars.add(visit(atv, visiting));
}
sb.append(PluginUtil.join(", ", typeVars));
sb.append("> ");
}
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().size() > 0) {
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();
sb.append(type.actualType);
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)
+ " null";
}
@Override
public String visitWildcard(AnnotatedWildcardType type, Set<AnnotatedTypeMirror> visiting) {
StringBuilder sb = new StringBuilder();
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());
}
}
}