All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.common.util.TypeVisualizer Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.43.0
Show newest version
package org.checkerframework.common.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
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.DefaultAnnotationFormatter;
import org.checkerframework.framework.util.ExecUtil;
import org.checkerframework.javacutil.BugInCF;
import org.plumelib.util.StringsPlume;

/**
 * TypeVisualizer prints AnnotatedTypeMirrors as a directed graph where each node is a type and an
 * arrow is a reference. Arrows are labeled with the relationship that reference represents (e.g. an
 * arrow marked "extends" starts from a type variable or wildcard type and points to the upper bound
 * of that type).
 *
 * 

Currently, to use TypeVisualizer just insert an if statement somewhere that targets the type * you would like to print: e.g. * *

{@code
 * if (type.getKind() == TypeKind.EXECUTABLE && type.toString().contains("methodToPrint")) {
 *     TypeVisualizer.drawToPng("/Users/jburke/Documents/tmp/method.png", type);
 * }
 * }
* * Be sure to remove such statements before committing your changes. */ public class TypeVisualizer { /** * Creates a dot file at dest that contains a digraph for the structure of {@code type}. * * @param dest the destination dot file * @param type the type to be written */ public static void drawToDot(final File dest, final AnnotatedTypeMirror type) { final Drawing drawer = new Drawing("Type", type); drawer.draw(dest); } /** * Creates a dot file at dest that contains a digraph for the structure of {@code type}. * * @param dest the destination dot file, this string will be directly passed to new File(dest) * @param type the type to be written */ public static void drawToDot(final String dest, final AnnotatedTypeMirror type) { drawToDot(new File(dest), type); } /** * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that * file into a png at the location dest. This method will fail if a temp file can't be created. * * @param dest the destination png file * @param type the type to be written */ public static void drawToPng(final File dest, final AnnotatedTypeMirror type) { try { final File dotFile = File.createTempFile(dest.getName(), ".dot"); drawToDot(dotFile, type); execDotToPng(dotFile, dest); } catch (Exception exc) { throw new RuntimeException(exc); } } /** * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that * file into a png at the location dest. This method will fail if a temp file can't be created. * * @param dest the destination png file, this string will be directly passed to new File(dest) * @param type the type to be written */ public static void drawToPng(final String dest, final AnnotatedTypeMirror type) { drawToPng(new File(dest), type); } /** * Converts the given dot file to a png file at the specified location. This method calls the * program "dot" from Runtime.exec and will fail if "dot" is not on your class path. * * @param dotFile the dot file to convert * @param pngFile the destination of the resultant png file */ public static void execDotToPng(final File dotFile, final File pngFile) { String[] cmd = new String[] {"dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath()}; System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile); System.out.flush(); ExecUtil.execute(cmd, System.out, System.err); } /** * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to * a dot file at {@code directory/varName}. * * @return true if the type variable was printed, otherwise false */ public static boolean printTypevarToDotIfMatches( final AnnotatedTypeVariable typeVariable, final List typeVarNames, final String directory) { return printTypevarIfMatches(typeVariable, typeVarNames, directory, false); } /** * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to * a png file at {@code directory/varName.png}. * * @return true if the type variable was printed, otherwise false */ public static boolean printTypevarToPngIfMatches( final AnnotatedTypeVariable typeVariable, final List typeVarNames, final String directory) { return printTypevarIfMatches(typeVariable, typeVarNames, directory, true); } private static boolean printTypevarIfMatches( final AnnotatedTypeVariable typeVariable, final List typeVarNames, final String directory, final boolean png) { final String dirPath = directory.endsWith(File.separator) ? directory : directory + File.separator; String varName = typeVariable.getUnderlyingType().asElement().toString(); if (typeVarNames.contains(varName)) { if (png) { TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable); } else { TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable); } return true; } return false; } /** * This class exists because there is no LinkedIdentityHashMap. * *

Node is just a wrapper around type mirror that replaces .equals with referential equality. * This is done to preserve the order types were traversed so that printing will occur in a * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just * create a wrapper that performed referential equality on types and use a LinkedHashMap. */ private static class Node { /** The delegate; that is, the wrapped value. */ private final @InternedDistinct AnnotatedTypeMirror type; /** * Create a new Node that wraps the given type. * * @param type the type that the newly-constructed Node represents */ private Node(final @FindDistinct AnnotatedTypeMirror type) { this.type = type; } @Override public int hashCode() { return type.hashCode(); } @Override public boolean equals(@Nullable Object obj) { if (obj == null) { return false; } if (obj instanceof Node) { return ((Node) obj).type == this.type; } return false; } } /** * Drawing visits a type and writes a dot file to the location specified. It contains data * structures to hold the intermediate dot information before printing. */ private static class Drawing { /** A map from Node (type) to a dot string declaring that node. */ private final Map nodes = new LinkedHashMap<>(); /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */ private final List lines = new ArrayList<>(); private final String graphName; /** The type being drawn. */ private final AnnotatedTypeMirror type; /** Used to identify nodes uniquely. This field is monotonically increasing. */ private int nextId = 0; public Drawing(final String graphName, final AnnotatedTypeMirror type) { this.graphName = graphName; this.type = type; } public void draw(final File file) { addNodes(type); addConnections(); write(file); } private void addNodes(final AnnotatedTypeMirror type) { new NodeDrawer().visit(type); } private void addConnections() { final ConnectionDrawer connectionDrawer = new ConnectionDrawer(); for (final Node node : nodes.keySet()) { connectionDrawer.visit(node.type); } } private void write(final File file) { try { BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(file)); writer.write("digraph " + graphName + "{"); writer.newLine(); for (final String line : lines) { writer.write(line + ";"); writer.newLine(); } writer.write("}"); writer.flush(); } catch (IOException e) { throw new BugInCF(e, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); } finally { if (writer != null) { writer.close(); } } } catch (IOException exc) { throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); } } /** * Connection drawer is used to add the connections between all the nodes created by the * NodeDrawer. It is not a scanner and is called on every node in the nodes map. */ private class ConnectionDrawer implements AnnotatedTypeVisitor { @Override public Void visit(AnnotatedTypeMirror type) { type.accept(this, null); return null; } @Override public Void visit(AnnotatedTypeMirror type, Void aVoid) { return visit(type); } @Override public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { final List typeArgs = type.getTypeArguments(); for (int i = 0; i < typeArgs.size(); i++) { lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i)); } return null; } @Override public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { final List bounds = type.getBounds(); for (int i = 0; i < bounds.size(); i++) { lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&")); } return null; } @Override public Void visitUnion(AnnotatedUnionType type, Void aVoid) { final List alternatives = type.getAlternatives(); for (int i = 0; i < alternatives.size(); i++) { lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|")); } return null; } @Override public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { ExecutableElement methodElem = type.getElement(); lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns")); final List typeVarElems = methodElem.getTypeParameters(); final List typeVars = type.getTypeVariables(); for (int i = 0; i < typeVars.size(); i++) { final String typeVarName = typeVarElems.get(i).getSimpleName().toString(); lines.add(connect(type, typeVars.get(i)) + " " + makeMethodTypeArgLabel(typeVarName)); } if (type.getReceiverType() != null) { lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver")); } final List paramElems = methodElem.getParameters(); final List params = type.getParameterTypes(); for (int i = 0; i < params.size(); i++) { final String paramName = paramElems.get(i).getSimpleName().toString(); lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName)); } final List thrown = type.getThrownTypes(); for (int i = 0; i < thrown.size(); i++) { lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i)); } return null; } @Override public Void visitArray(AnnotatedArrayType type, Void aVoid) { lines.add(connect(type, type.getComponentType())); return null; } @Override public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends")); lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super")); return null; } @Override public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { return null; } @Override public Void visitNoType(AnnotatedNoType type, Void aVoid) { return null; } @Override public Void visitNull(AnnotatedNullType type, Void aVoid) { return null; } @Override public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends")); lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super")); return null; } private String connect(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) { return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to)); } private String makeLabel(final String text) { return "[label=\"" + text + "\"]"; } private String makeTypeArgLabel(final int argIndex) { return makeLabel("<" + argIndex + ">"); } private String makeMethodTypeArgLabel(final String paramName) { return makeLabel("<" + paramName + ">"); } private String makeParamLabel(final String paramName) { return makeLabel(paramName); } private String makeThrownLabel(final int index) { return makeLabel("throws: " + index); } } /** * Scans types and adds a mapping from type to dot node declaration representing that type in * the enclosing drawing. */ private class NodeDrawer implements AnnotatedTypeVisitor { private final DefaultAnnotationFormatter annoFormatter = new DefaultAnnotationFormatter(); public NodeDrawer() {} private void visitAll(final List types) { for (final AnnotatedTypeMirror type : types) { visit(type); } } @Override public Void visit(AnnotatedTypeMirror type) { if (type != null) { type.accept(this, null); } return null; } @Override public Void visit(AnnotatedTypeMirror type, Void aVoid) { return visit(type); } @Override public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode( type, getAnnoStr(type) + " " + type.getUnderlyingType().asElement().getSimpleName() + (type.getTypeArguments().isEmpty() ? "" : "<...>"), "shape=box"); visitAll(type.getTypeArguments()); } return null; } @Override public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon"); visitAll(type.getBounds()); } return null; } @Override public Void visitUnion(AnnotatedUnionType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon"); visitAll(type.getAlternatives()); } return null; } @Override public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2"); visit(type.getReturnType()); visitAll(type.getTypeVariables()); visit(type.getReceiverType()); visitAll(type.getParameterTypes()); visitAll(type.getThrownTypes()); } else { throw new BugInCF("Executable types should never be recursive%ntype=%s", type); } return null; } @Override public Void visitArray(AnnotatedArrayType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + "[]"); visit(type.getComponentType()); } return null; } @Override public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode( type, getAnnoStr(type) + " " + type.getUnderlyingType().asElement().getSimpleName(), "shape=invtrapezium"); visit(type.getUpperBound()); visit(type.getLowerBound()); } return null; } @Override public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + " " + type.getKind()); } return null; } @Override public Void visitNoType(AnnotatedNoType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + " None"); } return null; } @Override public Void visitNull(AnnotatedNullType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + " NullType"); } return null; } @Override public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { if (checkOrAdd(type)) { addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium"); visit(type.getExtendsBound()); visit(type.getSuperBound()); } return null; } /** * Returns a string representation of the annotations on a type. * * @param atm an annotated type * @return a string representation of the annotations on {@code atm} */ public String getAnnoStr(final AnnotatedTypeMirror atm) { StringJoiner sj = new StringJoiner(" "); for (final AnnotationMirror anno : atm.getAnnotations()) { // TODO: More comprehensive escaping sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\")); } return sj.toString(); } public boolean checkOrAdd(final AnnotatedTypeMirror atm) { final Node node = new Node(atm); if (nodes.containsKey(node)) { return false; } nodes.put(node, String.valueOf(nextId++)); return true; } public String makeLabeledNode(final AnnotatedTypeMirror type, final String label) { return makeLabeledNode(type, label, null); } public String makeLabeledNode( final AnnotatedTypeMirror type, final String label, final String attributes) { final String attr = (attributes != null) ? ", " + attributes : ""; return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]"; } public void addLabeledNode(final AnnotatedTypeMirror type, final String label) { lines.add(makeLabeledNode(type, label)); } public void addLabeledNode( final AnnotatedTypeMirror type, final String label, final String attributes) { lines.add(makeLabeledNode(type, label, attributes)); } public String makeMethodLabel(final AnnotatedExecutableType methodType) { final ExecutableElement methodElem = methodType.getElement(); final StringBuilder builder = new StringBuilder(); builder.append(methodElem.getReturnType().toString()); builder.append(" <"); builder.append(StringsPlume.join(", ", methodElem.getTypeParameters())); builder.append("> "); builder.append(methodElem.getSimpleName().toString()); builder.append("("); builder.append(StringsPlume.join(",", methodElem.getParameters())); builder.append(")"); return builder.toString(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy