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