org.hibernate.hql.internal.ast.util.ASTPrinter.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.hql.internal.ast.util.ASTPrinter.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.hql.internal.ast.util;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.hql.internal.ast.tree.DisplayableNode;
import org.hibernate.internal.util.StringHelper;

import antlr.collections.AST;

/**
 * Utility for generating pretty "ASCII art" representations of syntax trees.
 *
 * This class is threadsafe: reuse instances as needed.
 *
 * @author Joshua Davis
 * @author Steve Ebersole
 */
public class ASTPrinter {

    // This is a map: array index is the ANTLR Token ID, array value is the name of that token.
    // There might be gaps in the array (null values) but it's generally quite compact.
    private final String[] tokenTypeNameCache;

    /**
     * Constructs a printer. Package protected: use the constants from {TokenPrinters}
     * @see TokenPrinters
     *
     * @param tokenTypeConstants The token types to use during printing; typically the {vocabulary}TokenTypes.java
     * interface generated by ANTLR.
     */
    ASTPrinter(Class tokenTypeConstants) {
        this.tokenTypeNameCache = ASTUtil.generateTokenNameCache(tokenTypeConstants);
    }

    /**
     * Renders the AST into 'ASCII art' form and returns that string representation.
     *
     * @param ast The AST to display.
     * @param header The header for the display.
     *
     * @return The AST in 'ASCII art' form, as a string.
     */
    public String showAsString(AST ast, String header) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);
        ps.println(header);
        showAst(ast, ps);
        ps.flush();
        return new String(baos.toByteArray());
    }

    /**
     * Prints the AST in 'ASCII art' form to the specified print stream.
     *
     * @param ast The AST to print.
     * @param out The print stream to which the AST should be printed.
     */
    public void showAst(AST ast, PrintStream out) {
        showAst(ast, new PrintWriter(out));
    }

    /**
     * Prints the AST in 'ASCII art' tree form to the specified print writer.
     *
     * @param ast The AST to print.
     * @param pw The print writer to which the AST should be written.
     */
    public void showAst(AST ast, PrintWriter pw) {
        ArrayList<AST> parents = new ArrayList<AST>();
        showAst(parents, pw, ast);
        pw.flush();
    }

    /**
     * Returns the token type name for the given token type.
     *
     * @param type The token type.
     *
     * @return String - The token type name from the token type constant class,
     *         or just the integer as a string if none exists.
     */
    public String getTokenTypeName(int type) {
        String value = tokenTypeNameCache[type];
        if (value == null) {
            value = Integer.toString(type);
        }
        return value;
    }

    private void showAst(ArrayList<AST> parents, PrintWriter pw, AST ast) {
        if (ast == null) {
            pw.println("AST is null!");
            return;
        }

        indentLine(parents, pw);

        if (ast.getNextSibling() == null) {
            pw.print(" \\-");
        } else {
            pw.print(" +-");
        }

        showNode(pw, ast);
        showNodeProperties(parents, pw, ast);

        ArrayList<AST> newParents = new ArrayList<AST>(parents);
        newParents.add(ast);
        for (AST child = ast.getFirstChild(); child != null; child = child.getNextSibling()) {
            showAst(newParents, pw, child);
        }
        newParents.clear();
    }

    private void indentLine(List<AST> parents, PrintWriter pw) {
        for (AST parent : parents) {
            if (parent.getNextSibling() == null) {
                pw.print("   ");
            } else {
                pw.print(" | ");
            }
        }
    }

    private void showNode(PrintWriter pw, AST ast) {
        String s = nodeToString(ast);
        pw.println(s);
    }

    public String nodeToString(AST ast) {
        if (ast == null) {
            return "{node:null}";
        }
        StringBuilder buf = new StringBuilder();
        buf.append("[").append(getTokenTypeName(ast.getType())).append("] ");
        buf.append(StringHelper.unqualify(ast.getClass().getName())).append(": ");

        buf.append("'");
        String text = ast.getText();
        if (text == null) {
            text = "{text:null}";
        }
        appendEscapedMultibyteChars(text, buf);
        buf.append("'");
        if (ast instanceof DisplayableNode) {
            DisplayableNode displayableNode = (DisplayableNode) ast;
            // Add a space before the display text.
            buf.append(" ").append(displayableNode.getDisplayText());
        }
        return buf.toString();
    }

    private void showNodeProperties(ArrayList<AST> parents, PrintWriter pw, AST ast) {
        Map<String, Object> nodeProperties = createNodeProperties(ast);
        ArrayList<AST> parentsAndNode = new ArrayList<>(parents);
        parentsAndNode.add(ast);
        for (String propertyName : nodeProperties.keySet()) {
            indentLine(parentsAndNode, pw);
            pw.println(propertyToString(propertyName, nodeProperties.get(propertyName), ast));
        }
    }

    public LinkedHashMap<String, Object> createNodeProperties(AST ast) {
        return new LinkedHashMap<>();
    }

    public String propertyToString(String label, Object value, AST ast) {
        return String.format("%s: %s", label, value);
    }

    public static void appendEscapedMultibyteChars(String text, StringBuilder buf) {
        char[] chars = text.toCharArray();
        for (char aChar : chars) {
            if (aChar > 256) {
                buf.append("\\u");
                buf.append(Integer.toHexString(aChar));
            } else {
                buf.append(aChar);
            }
        }
    }

    public static String escapeMultibyteChars(String text) {
        StringBuilder buf = new StringBuilder();
        appendEscapedMultibyteChars(text, buf);
        return buf.toString();
    }
}