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 */
23package org.graalvm.compiler.debug;
24
25import java.io.IOException;
26import java.io.OutputStream;
27import 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 */
49public 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}
480