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()); } } }