/* * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.debug; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; /** * A utility for printing compiler debug and informational output to an output stream. * * A {@link LogStream} instance maintains an internal buffer that is flushed to the underlying * output stream every time one of the {@code println} methods is invoked, or a newline character ( * {@code '\n'}) is written. * * All of the {@code print} and {@code println} methods return the {code LogStream} instance on * which they were invoked. This allows chaining of these calls to mitigate use of String * concatenation by the caller. * * A {@code LogStream} maintains a current {@linkplain #indentationLevel() indentation} level. Each * line of output written to this stream has {@code n} spaces prefixed to it where {@code n} is the * value that would be returned by {@link #indentationLevel()} when the first character of a new * line is written. * * A {@code LogStream} maintains a current {@linkplain #position() position} for the current line * being written. This position can be advanced to a specified position by * {@linkplain #fillTo(int, char) filling} this stream with a given character. */ public class LogStream { /** * Null output stream that simply swallows any output sent to it. */ public static final LogStream SINK = new LogStream(); private static final PrintStream SINK_PS = new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { } }); private LogStream() { this.ps = null; this.lineBuffer = null; } /** * The output stream to which this log stream writes. */ private final PrintStream ps; private final StringBuilder lineBuffer; private int indentationLevel; private char indentation = ' '; private boolean indentationDisabled; public final PrintStream out() { if (ps == null) { return SINK_PS; } return ps; } /** * The system dependent line separator. */ public static final String LINE_SEPARATOR = System.getProperty("line.separator"); /** * Creates a new log stream. * * @param os the underlying output stream to which prints are sent */ public LogStream(OutputStream os) { ps = os instanceof PrintStream ? (PrintStream) os : new PrintStream(os); lineBuffer = new StringBuilder(100); } /** * Creates a new log stream that shares the same {@linkplain #ps output stream} as a given * {@link LogStream}. * * @param log a LogStream whose output stream is shared with this one */ public LogStream(LogStream log) { ps = log.ps; lineBuffer = new StringBuilder(100); } /** * Prepends {@link #indentation} to the current output line until its write position is equal to * the current {@linkplain #indentationLevel()} level. */ private void indent() { if (ps != null) { if (!indentationDisabled && indentationLevel != 0) { while (lineBuffer.length() < indentationLevel) { lineBuffer.append(indentation); } } } } private LogStream flushLine(boolean withNewline) { if (ps != null) { if (withNewline) { lineBuffer.append(LINE_SEPARATOR); } ps.print(lineBuffer.toString()); ps.flush(); lineBuffer.setLength(0); } return this; } /** * Flushes the stream. This is done by terminating the current line if it is not at position 0 * and then flushing the underlying output stream. */ public void flush() { if (ps != null) { if (lineBuffer.length() != 0) { flushLine(false); } ps.flush(); } } /** * Gets the current column position of this log stream. * * @return the current column position of this log stream */ public int position() { return lineBuffer == null ? 0 : lineBuffer.length(); } /** * Gets the current indentation level for this log stream. * * @return the current indentation level for this log stream. */ public int indentationLevel() { return indentationLevel; } /** * Adjusts the current indentation level of this log stream. * * @param delta */ public void adjustIndentation(int delta) { if (delta < 0) { indentationLevel = Math.max(0, indentationLevel + delta); } else { indentationLevel += delta; } } /** * Gets the current indentation character of this log stream. */ public char indentation() { return indentation; } public void disableIndentation() { indentationDisabled = true; } public void enableIndentation() { indentationDisabled = false; } /** * Sets the character used for indentation. */ public void setIndentation(char c) { indentation = c; } /** * Advances this stream's {@linkplain #position() position} to a given position by repeatedly * appending a given character as necessary. * * @param position the position to which this stream's position will be advanced * @param filler the character used to pad the stream */ public LogStream fillTo(int position, char filler) { if (ps != null) { indent(); while (lineBuffer.length() < position) { lineBuffer.append(filler); } } return this; } /** * Writes a boolean value to this stream as {@code "true"} or {@code "false"}. * * @param b the value to be printed * @return this {@link LogStream} instance */ public LogStream print(boolean b) { if (ps != null) { indent(); lineBuffer.append(b); } return this; } /** * Writes a boolean value to this stream followed by a {@linkplain #LINE_SEPARATOR line * separator}. * * @param b the value to be printed * @return this {@link LogStream} instance */ public LogStream println(boolean b) { if (ps != null) { indent(); lineBuffer.append(b); return flushLine(true); } return this; } /** * Writes a character value to this stream. * * @param c the value to be printed * @return this {@link LogStream} instance */ public LogStream print(char c) { if (ps != null) { indent(); lineBuffer.append(c); if (c == '\n') { if (lineBuffer.indexOf(LINE_SEPARATOR, lineBuffer.length() - LINE_SEPARATOR.length()) != -1) { flushLine(false); } } } return this; } /** * Writes a character value to this stream followed by a {@linkplain #LINE_SEPARATOR line * separator}. * * @param c the value to be printed * @return this {@link LogStream} instance */ public LogStream println(char c) { if (ps != null) { indent(); lineBuffer.append(c); flushLine(true); } return this; } /** * Prints an int value. * * @param i the value to be printed * @return this {@link LogStream} instance */ public LogStream print(int i) { if (ps != null) { indent(); lineBuffer.append(i); } return this; } /** * Writes an int value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}. * * @param i the value to be printed * @return this {@link LogStream} instance */ public LogStream println(int i) { if (ps != null) { indent(); lineBuffer.append(i); return flushLine(true); } return this; } /** * Writes a float value to this stream. * * @param f the value to be printed * @return this {@link LogStream} instance */ public LogStream print(float f) { if (ps != null) { indent(); lineBuffer.append(f); } return this; } /** * Writes a float value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator} * . * * @param f the value to be printed * @return this {@link LogStream} instance */ public LogStream println(float f) { if (ps != null) { indent(); lineBuffer.append(f); return flushLine(true); } return this; } /** * Writes a long value to this stream. * * @param l the value to be printed * @return this {@link LogStream} instance */ public LogStream print(long l) { if (ps != null) { indent(); lineBuffer.append(l); } return this; } /** * Writes a long value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}. * * @param l the value to be printed * @return this {@link LogStream} instance */ public LogStream println(long l) { if (ps != null) { indent(); lineBuffer.append(l); return flushLine(true); } return this; } /** * Writes a double value to this stream. * * @param d the value to be printed * @return this {@link LogStream} instance */ public LogStream print(double d) { if (ps != null) { indent(); lineBuffer.append(d); } return this; } /** * Writes a double value to this stream followed by a {@linkplain #LINE_SEPARATOR line * separator}. * * @param d the value to be printed * @return this {@link LogStream} instance */ public LogStream println(double d) { if (ps != null) { indent(); lineBuffer.append(d); return flushLine(true); } return this; } /** * Writes a {@code String} value to this stream. This method ensures that the * {@linkplain #position() position} of this stream is updated correctly with respect to any * {@linkplain #LINE_SEPARATOR line separators} present in {@code s}. * * @param s the value to be printed * @return this {@link LogStream} instance */ public LogStream print(String s) { if (ps != null) { if (s == null) { indent(); lineBuffer.append(s); return this; } int index = 0; int next = s.indexOf(LINE_SEPARATOR, index); while (index < s.length()) { indent(); if (next > index || next == 0) { lineBuffer.append(s.substring(index, next)); flushLine(true); index = next + LINE_SEPARATOR.length(); next = s.indexOf(LINE_SEPARATOR, index); } else { lineBuffer.append(s.substring(index)); break; } } } return this; } /** * Writes a {@code String} value to this stream followed by a {@linkplain #LINE_SEPARATOR line * separator}. * * @param s the value to be printed * @return this {@link LogStream} instance */ public LogStream println(String s) { if (ps != null) { print(s); flushLine(true); } return this; } /** * Writes a formatted string to this stream. * * @param format a format string as described in {@link String#format(String, Object...)} * @param args the arguments to be formatted * @return this {@link LogStream} instance */ public LogStream printf(String format, Object... args) { if (ps != null) { print(String.format(format, args)); } return this; } /** * Writes a {@linkplain #LINE_SEPARATOR line separator} to this stream. * * @return this {@code LogStream} instance */ public LogStream println() { if (ps != null) { indent(); flushLine(true); } return this; } }