1 /*
   2  * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.debug;
  26 
  27 import java.io.IOException;
  28 import java.io.OutputStream;
  29 import java.io.PrintStream;
  30 
  31 /**
  32  * A utility for printing compiler debug and informational output to an output stream.
  33  *
  34  * A {@link LogStream} instance maintains an internal buffer that is flushed to the underlying
  35  * output stream every time one of the {@code println} methods is invoked, or a newline character (
  36  * {@code '\n'}) is written.
  37  *
  38  * All of the {@code print} and {@code println} methods return the {code LogStream} instance on
  39  * which they were invoked. This allows chaining of these calls to mitigate use of String
  40  * concatenation by the caller.
  41  *
  42  * A {@code LogStream} maintains a current {@linkplain #indentationLevel() indentation} level. Each
  43  * line of output written to this stream has {@code n} spaces prefixed to it where {@code n} is the
  44  * value that would be returned by {@link #indentationLevel()} when the first character of a new
  45  * line is written.
  46  *
  47  * A {@code LogStream} maintains a current {@linkplain #position() position} for the current line
  48  * being written. This position can be advanced to a specified position by
  49  * {@linkplain #fillTo(int, char) filling} this stream with a given character.
  50  */
  51 public class LogStream {
  52 
  53     /**
  54      * Null output stream that simply swallows any output sent to it.
  55      */
  56     public static final LogStream SINK = new LogStream();
  57 
  58     private static final PrintStream SINK_PS = new PrintStream(new OutputStream() {
  59 
  60         @Override
  61         public void write(int b) throws IOException {
  62         }
  63     });
  64 
  65     private LogStream() {
  66         this.ps = null;
  67         this.lineBuffer = null;
  68     }
  69 
  70     /**
  71      * The output stream to which this log stream writes.
  72      */
  73     private final PrintStream ps;
  74 
  75     private final StringBuilder lineBuffer;
  76     private int indentationLevel;
  77     private char indentation = ' ';
  78     private boolean indentationDisabled;
  79 
  80     public final PrintStream out() {
  81         if (ps == null) {
  82             return SINK_PS;
  83         }
  84         return ps;
  85     }
  86 
  87     /**
  88      * The system dependent line separator.
  89      */
  90     public static final String LINE_SEPARATOR = System.lineSeparator();
  91 
  92     /**
  93      * Creates a new log stream.
  94      *
  95      * @param os the underlying output stream to which prints are sent
  96      */
  97     public LogStream(OutputStream os) {
  98         ps = os instanceof PrintStream ? (PrintStream) os : new PrintStream(os);
  99         lineBuffer = new StringBuilder(100);
 100     }
 101 
 102     /**
 103      * Creates a new log stream that shares the same {@linkplain #ps output stream} as a given
 104      * {@link LogStream}.
 105      *
 106      * @param log a LogStream whose output stream is shared with this one
 107      */
 108     public LogStream(LogStream log) {
 109         ps = log.ps;
 110         lineBuffer = new StringBuilder(100);
 111     }
 112 
 113     /**
 114      * Prepends {@link #indentation} to the current output line until its write position is equal to
 115      * the current {@linkplain #indentationLevel()} level.
 116      */
 117     private void indent() {
 118         if (ps != null) {
 119             if (!indentationDisabled && indentationLevel != 0) {
 120                 while (lineBuffer.length() < indentationLevel) {
 121                     lineBuffer.append(indentation);
 122                 }
 123             }
 124         }
 125     }
 126 
 127     private LogStream flushLine(boolean withNewline) {
 128         if (ps != null) {
 129             if (withNewline) {
 130                 lineBuffer.append(LINE_SEPARATOR);
 131             }
 132             ps.print(lineBuffer.toString());
 133             ps.flush();
 134             lineBuffer.setLength(0);
 135         }
 136         return this;
 137     }
 138 
 139     /**
 140      * Flushes the stream. This is done by terminating the current line if it is not at position 0
 141      * and then flushing the underlying output stream.
 142      */
 143     public void flush() {
 144         if (ps != null) {
 145             if (lineBuffer.length() != 0) {
 146                 flushLine(false);
 147             }
 148             ps.flush();
 149         }
 150     }
 151 
 152     /**
 153      * Gets the current column position of this log stream.
 154      *
 155      * @return the current column position of this log stream
 156      */
 157     public int position() {
 158         return lineBuffer == null ? 0 : lineBuffer.length();
 159 
 160     }
 161 
 162     /**
 163      * Gets the current indentation level for this log stream.
 164      *
 165      * @return the current indentation level for this log stream.
 166      */
 167     public int indentationLevel() {
 168         return indentationLevel;
 169     }
 170 
 171     /**
 172      * Adjusts the current indentation level of this log stream.
 173      *
 174      * @param delta
 175      */
 176     public void adjustIndentation(int delta) {
 177         if (delta < 0) {
 178             indentationLevel = Math.max(0, indentationLevel + delta);
 179         } else {
 180             indentationLevel += delta;
 181         }
 182     }
 183 
 184     /**
 185      * Gets the current indentation character of this log stream.
 186      */
 187     public char indentation() {
 188         return indentation;
 189     }
 190 
 191     public void disableIndentation() {
 192         indentationDisabled = true;
 193     }
 194 
 195     public void enableIndentation() {
 196         indentationDisabled = false;
 197     }
 198 
 199     /**
 200      * Sets the character used for indentation.
 201      */
 202     public void setIndentation(char c) {
 203         indentation = c;
 204     }
 205 
 206     /**
 207      * Advances this stream's {@linkplain #position() position} to a given position by repeatedly
 208      * appending a given character as necessary.
 209      *
 210      * @param position the position to which this stream's position will be advanced
 211      * @param filler the character used to pad the stream
 212      */
 213     public LogStream fillTo(int position, char filler) {
 214         if (ps != null) {
 215             indent();
 216             while (lineBuffer.length() < position) {
 217                 lineBuffer.append(filler);
 218             }
 219         }
 220         return this;
 221     }
 222 
 223     /**
 224      * Writes a boolean value to this stream as {@code "true"} or {@code "false"}.
 225      *
 226      * @param b the value to be printed
 227      * @return this {@link LogStream} instance
 228      */
 229     public LogStream print(boolean b) {
 230         if (ps != null) {
 231             indent();
 232             lineBuffer.append(b);
 233         }
 234         return this;
 235     }
 236 
 237     /**
 238      * Writes a boolean value to this stream followed by a {@linkplain #LINE_SEPARATOR line
 239      * separator}.
 240      *
 241      * @param b the value to be printed
 242      * @return this {@link LogStream} instance
 243      */
 244     public LogStream println(boolean b) {
 245         if (ps != null) {
 246             indent();
 247             lineBuffer.append(b);
 248             return flushLine(true);
 249         }
 250         return this;
 251     }
 252 
 253     /**
 254      * Writes a character value to this stream.
 255      *
 256      * @param c the value to be printed
 257      * @return this {@link LogStream} instance
 258      */
 259     public LogStream print(char c) {
 260         if (ps != null) {
 261             indent();
 262             lineBuffer.append(c);
 263             if (c == '\n') {
 264                 if (lineBuffer.indexOf(LINE_SEPARATOR, lineBuffer.length() - LINE_SEPARATOR.length()) != -1) {
 265                     flushLine(false);
 266                 }
 267             }
 268         }
 269         return this;
 270     }
 271 
 272     /**
 273      * Writes a character value to this stream followed by a {@linkplain #LINE_SEPARATOR line
 274      * separator}.
 275      *
 276      * @param c the value to be printed
 277      * @return this {@link LogStream} instance
 278      */
 279     public LogStream println(char c) {
 280         if (ps != null) {
 281             indent();
 282             lineBuffer.append(c);
 283             flushLine(true);
 284         }
 285         return this;
 286     }
 287 
 288     /**
 289      * Prints an int value.
 290      *
 291      * @param i the value to be printed
 292      * @return this {@link LogStream} instance
 293      */
 294     public LogStream print(int i) {
 295         if (ps != null) {
 296             indent();
 297             lineBuffer.append(i);
 298         }
 299         return this;
 300     }
 301 
 302     /**
 303      * Writes an int value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}.
 304      *
 305      * @param i the value to be printed
 306      * @return this {@link LogStream} instance
 307      */
 308     public LogStream println(int i) {
 309         if (ps != null) {
 310             indent();
 311             lineBuffer.append(i);
 312             return flushLine(true);
 313         }
 314         return this;
 315     }
 316 
 317     /**
 318      * Writes a float value to this stream.
 319      *
 320      * @param f the value to be printed
 321      * @return this {@link LogStream} instance
 322      */
 323     public LogStream print(float f) {
 324         if (ps != null) {
 325             indent();
 326             lineBuffer.append(f);
 327         }
 328         return this;
 329     }
 330 
 331     /**
 332      * Writes a float value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}
 333      * .
 334      *
 335      * @param f the value to be printed
 336      * @return this {@link LogStream} instance
 337      */
 338     public LogStream println(float f) {
 339         if (ps != null) {
 340             indent();
 341             lineBuffer.append(f);
 342             return flushLine(true);
 343         }
 344         return this;
 345     }
 346 
 347     /**
 348      * Writes a long value to this stream.
 349      *
 350      * @param l the value to be printed
 351      * @return this {@link LogStream} instance
 352      */
 353     public LogStream print(long l) {
 354         if (ps != null) {
 355             indent();
 356             lineBuffer.append(l);
 357         }
 358         return this;
 359     }
 360 
 361     /**
 362      * Writes a long value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}.
 363      *
 364      * @param l the value to be printed
 365      * @return this {@link LogStream} instance
 366      */
 367     public LogStream println(long l) {
 368         if (ps != null) {
 369             indent();
 370             lineBuffer.append(l);
 371             return flushLine(true);
 372         }
 373         return this;
 374     }
 375 
 376     /**
 377      * Writes a double value to this stream.
 378      *
 379      * @param d the value to be printed
 380      * @return this {@link LogStream} instance
 381      */
 382     public LogStream print(double d) {
 383         if (ps != null) {
 384             indent();
 385             lineBuffer.append(d);
 386         }
 387         return this;
 388     }
 389 
 390     /**
 391      * Writes a double value to this stream followed by a {@linkplain #LINE_SEPARATOR line
 392      * separator}.
 393      *
 394      * @param d the value to be printed
 395      * @return this {@link LogStream} instance
 396      */
 397     public LogStream println(double d) {
 398         if (ps != null) {
 399             indent();
 400             lineBuffer.append(d);
 401             return flushLine(true);
 402         }
 403         return this;
 404     }
 405 
 406     /**
 407      * Writes a {@code String} value to this stream. This method ensures that the
 408      * {@linkplain #position() position} of this stream is updated correctly with respect to any
 409      * {@linkplain #LINE_SEPARATOR line separators} present in {@code s}.
 410      *
 411      * @param s the value to be printed
 412      * @return this {@link LogStream} instance
 413      */
 414     public LogStream print(String s) {
 415         if (ps != null) {
 416             if (s == null) {
 417                 indent();
 418                 lineBuffer.append(s);
 419                 return this;
 420             }
 421 
 422             int index = 0;
 423             int next = s.indexOf(LINE_SEPARATOR, index);
 424             while (index < s.length()) {
 425                 indent();
 426                 if (next > index || next == 0) {
 427                     lineBuffer.append(s.substring(index, next));
 428                     flushLine(true);
 429                     index = next + LINE_SEPARATOR.length();
 430                     next = s.indexOf(LINE_SEPARATOR, index);
 431                 } else {
 432                     lineBuffer.append(s.substring(index));
 433                     break;
 434                 }
 435             }
 436         }
 437         return this;
 438     }
 439 
 440     /**
 441      * Writes a {@code String} value to this stream followed by a {@linkplain #LINE_SEPARATOR line
 442      * separator}.
 443      *
 444      * @param s the value to be printed
 445      * @return this {@link LogStream} instance
 446      */
 447     public LogStream println(String s) {
 448         if (ps != null) {
 449             print(s);
 450             flushLine(true);
 451         }
 452         return this;
 453     }
 454 
 455     /**
 456      * Writes a formatted string to this stream.
 457      *
 458      * @param format a format string as described in {@link String#format(String, Object...)}
 459      * @param args the arguments to be formatted
 460      * @return this {@link LogStream} instance
 461      */
 462     public LogStream printf(String format, Object... args) {
 463         if (ps != null) {
 464             print(String.format(format, args));
 465         }
 466         return this;
 467     }
 468 
 469     /**
 470      * Writes a {@linkplain #LINE_SEPARATOR line separator} to this stream.
 471      *
 472      * @return this {@code LogStream} instance
 473      */
 474     public LogStream println() {
 475         if (ps != null) {
 476             indent();
 477             flushLine(true);
 478         }
 479         return this;
 480     }
 481 }