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 }