1 /*
2  * Copyright (c) 2009, 2018, 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 
LogStream()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 
out()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.getProperty("line.separator");
91 
92     /**
93      * Creates a new log stream.
94      *
95      * @param os the underlying output stream to which prints are sent
96      */
LogStream(OutputStream os)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      */
LogStream(LogStream log)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      */
indent()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 
flushLine(boolean withNewline)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      */
flush()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      */
position()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      */
indentationLevel()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      */
adjustIndentation(int delta)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      */
indentation()187     public char indentation() {
188         return indentation;
189     }
190 
disableIndentation()191     public void disableIndentation() {
192         indentationDisabled = true;
193     }
194 
enableIndentation()195     public void enableIndentation() {
196         indentationDisabled = false;
197     }
198 
199     /**
200      * Sets the character used for indentation.
201      */
setIndentation(char c)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      */
fillTo(int position, char filler)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      */
print(boolean b)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      */
println(boolean b)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      */
print(char c)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      */
println(char c)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      */
print(int i)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      */
println(int i)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      */
print(float f)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      */
println(float f)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      */
print(long l)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      */
println(long l)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      */
print(double d)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      */
println(double d)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      */
print(String s)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      */
println(String s)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      */
printf(String format, Object... args)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      */
println()474     public LogStream println() {
475         if (ps != null) {
476             indent();
477             flushLine(true);
478         }
479         return this;
480     }
481 }
482