001/*
002 * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.debug;
024
025import java.io.*;
026
027/**
028 * A utility for printing compiler debug and informational output to an output stream.
029 *
030 * A {@link LogStream} instance maintains an internal buffer that is flushed to the underlying
031 * output stream every time one of the {@code println} methods is invoked, or a newline character (
032 * {@code '\n'}) is written.
033 *
034 * All of the {@code print} and {@code println} methods return the {code LogStream} instance on
035 * which they were invoked. This allows chaining of these calls to mitigate use of String
036 * concatenation by the caller.
037 *
038 * A {@code LogStream} maintains a current {@linkplain #indentationLevel() indentation} level. Each
039 * line of output written to this stream has {@code n} spaces prefixed to it where {@code n} is the
040 * value that would be returned by {@link #indentationLevel()} when the first character of a new
041 * line is written.
042 *
043 * A {@code LogStream} maintains a current {@linkplain #position() position} for the current line
044 * being written. This position can be advanced to a specified position by
045 * {@linkplain #fillTo(int, char) filling} this stream with a given character.
046 */
047public class LogStream {
048
049    /**
050     * Null output stream that simply swallows any output sent to it.
051     */
052    public static final LogStream SINK = new LogStream();
053
054    private static final PrintStream SINK_PS = new PrintStream(new OutputStream() {
055
056        @Override
057        public void write(int b) throws IOException {
058        }
059    });
060
061    private LogStream() {
062        this.ps = null;
063        this.lineBuffer = null;
064    }
065
066    /**
067     * The output stream to which this log stream writes.
068     */
069    private final PrintStream ps;
070
071    private final StringBuilder lineBuffer;
072    private int indentationLevel;
073    private char indentation = ' ';
074    private boolean indentationDisabled;
075
076    public final PrintStream out() {
077        if (ps == null) {
078            return SINK_PS;
079        }
080        return ps;
081    }
082
083    /**
084     * The system dependent line separator.
085     */
086    public static final String LINE_SEPARATOR = System.getProperty("line.separator");
087
088    /**
089     * Creates a new log stream.
090     *
091     * @param os the underlying output stream to which prints are sent
092     */
093    public LogStream(OutputStream os) {
094        ps = os instanceof PrintStream ? (PrintStream) os : new PrintStream(os);
095        lineBuffer = new StringBuilder(100);
096    }
097
098    /**
099     * Creates a new log stream that shares the same {@linkplain #ps output stream} as a given
100     * {@link LogStream}.
101     *
102     * @param log a LogStream whose output stream is shared with this one
103     */
104    public LogStream(LogStream log) {
105        ps = log.ps;
106        lineBuffer = new StringBuilder(100);
107    }
108
109    /**
110     * Prepends {@link #indentation} to the current output line until its write position is equal to
111     * the current {@linkplain #indentationLevel()} level.
112     */
113    private void indent() {
114        if (ps != null) {
115            if (!indentationDisabled && indentationLevel != 0) {
116                while (lineBuffer.length() < indentationLevel) {
117                    lineBuffer.append(indentation);
118                }
119            }
120        }
121    }
122
123    private LogStream flushLine(boolean withNewline) {
124        if (ps != null) {
125            if (withNewline) {
126                lineBuffer.append(LINE_SEPARATOR);
127            }
128            ps.print(lineBuffer.toString());
129            ps.flush();
130            lineBuffer.setLength(0);
131        }
132        return this;
133    }
134
135    /**
136     * Flushes the stream. This is done by terminating the current line if it is not at position 0
137     * and then flushing the underlying output stream.
138     */
139    public void flush() {
140        if (ps != null) {
141            if (lineBuffer.length() != 0) {
142                flushLine(false);
143            }
144            ps.flush();
145        }
146    }
147
148    /**
149     * Gets the current column position of this log stream.
150     *
151     * @return the current column position of this log stream
152     */
153    public int position() {
154        return lineBuffer == null ? 0 : lineBuffer.length();
155
156    }
157
158    /**
159     * Gets the current indentation level for this log stream.
160     *
161     * @return the current indentation level for this log stream.
162     */
163    public int indentationLevel() {
164        return indentationLevel;
165    }
166
167    /**
168     * Adjusts the current indentation level of this log stream.
169     *
170     * @param delta
171     */
172    public void adjustIndentation(int delta) {
173        if (delta < 0) {
174            indentationLevel = Math.max(0, indentationLevel + delta);
175        } else {
176            indentationLevel += delta;
177        }
178    }
179
180    /**
181     * Gets the current indentation character of this log stream.
182     */
183    public char indentation() {
184        return indentation;
185    }
186
187    public void disableIndentation() {
188        indentationDisabled = true;
189    }
190
191    public void enableIndentation() {
192        indentationDisabled = false;
193    }
194
195    /**
196     * Sets the character used for indentation.
197     */
198    public void setIndentation(char c) {
199        indentation = c;
200    }
201
202    /**
203     * Advances this stream's {@linkplain #position() position} to a given position by repeatedly
204     * appending a given character as necessary.
205     *
206     * @param position the position to which this stream's position will be advanced
207     * @param filler the character used to pad the stream
208     */
209    public LogStream fillTo(int position, char filler) {
210        if (ps != null) {
211            indent();
212            while (lineBuffer.length() < position) {
213                lineBuffer.append(filler);
214            }
215        }
216        return this;
217    }
218
219    /**
220     * Writes a boolean value to this stream as {@code "true"} or {@code "false"}.
221     *
222     * @param b the value to be printed
223     * @return this {@link LogStream} instance
224     */
225    public LogStream print(boolean b) {
226        if (ps != null) {
227            indent();
228            lineBuffer.append(b);
229        }
230        return this;
231    }
232
233    /**
234     * Writes a boolean value to this stream followed by a {@linkplain #LINE_SEPARATOR line
235     * separator}.
236     *
237     * @param b the value to be printed
238     * @return this {@link LogStream} instance
239     */
240    public LogStream println(boolean b) {
241        if (ps != null) {
242            indent();
243            lineBuffer.append(b);
244            return flushLine(true);
245        }
246        return this;
247    }
248
249    /**
250     * Writes a character value to this stream.
251     *
252     * @param c the value to be printed
253     * @return this {@link LogStream} instance
254     */
255    public LogStream print(char c) {
256        if (ps != null) {
257            indent();
258            lineBuffer.append(c);
259            if (c == '\n') {
260                if (lineBuffer.indexOf(LINE_SEPARATOR, lineBuffer.length() - LINE_SEPARATOR.length()) != -1) {
261                    flushLine(false);
262                }
263            }
264        }
265        return this;
266    }
267
268    /**
269     * Writes a character value to this stream followed by a {@linkplain #LINE_SEPARATOR line
270     * separator}.
271     *
272     * @param c the value to be printed
273     * @return this {@link LogStream} instance
274     */
275    public LogStream println(char c) {
276        if (ps != null) {
277            indent();
278            lineBuffer.append(c);
279            flushLine(true);
280        }
281        return this;
282    }
283
284    /**
285     * Prints an int value.
286     *
287     * @param i the value to be printed
288     * @return this {@link LogStream} instance
289     */
290    public LogStream print(int i) {
291        if (ps != null) {
292            indent();
293            lineBuffer.append(i);
294        }
295        return this;
296    }
297
298    /**
299     * Writes an int value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}.
300     *
301     * @param i the value to be printed
302     * @return this {@link LogStream} instance
303     */
304    public LogStream println(int i) {
305        if (ps != null) {
306            indent();
307            lineBuffer.append(i);
308            return flushLine(true);
309        }
310        return this;
311    }
312
313    /**
314     * Writes a float value to this stream.
315     *
316     * @param f the value to be printed
317     * @return this {@link LogStream} instance
318     */
319    public LogStream print(float f) {
320        if (ps != null) {
321            indent();
322            lineBuffer.append(f);
323        }
324        return this;
325    }
326
327    /**
328     * Writes a float value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}
329     * .
330     *
331     * @param f the value to be printed
332     * @return this {@link LogStream} instance
333     */
334    public LogStream println(float f) {
335        if (ps != null) {
336            indent();
337            lineBuffer.append(f);
338            return flushLine(true);
339        }
340        return this;
341    }
342
343    /**
344     * Writes a long value to this stream.
345     *
346     * @param l the value to be printed
347     * @return this {@link LogStream} instance
348     */
349    public LogStream print(long l) {
350        if (ps != null) {
351            indent();
352            lineBuffer.append(l);
353        }
354        return this;
355    }
356
357    /**
358     * Writes a long value to this stream followed by a {@linkplain #LINE_SEPARATOR line separator}.
359     *
360     * @param l the value to be printed
361     * @return this {@link LogStream} instance
362     */
363    public LogStream println(long l) {
364        if (ps != null) {
365            indent();
366            lineBuffer.append(l);
367            return flushLine(true);
368        }
369        return this;
370    }
371
372    /**
373     * Writes a double value to this stream.
374     *
375     * @param d the value to be printed
376     * @return this {@link LogStream} instance
377     */
378    public LogStream print(double d) {
379        if (ps != null) {
380            indent();
381            lineBuffer.append(d);
382        }
383        return this;
384    }
385
386    /**
387     * Writes a double value to this stream followed by a {@linkplain #LINE_SEPARATOR line
388     * separator}.
389     *
390     * @param d the value to be printed
391     * @return this {@link LogStream} instance
392     */
393    public LogStream println(double d) {
394        if (ps != null) {
395            indent();
396            lineBuffer.append(d);
397            return flushLine(true);
398        }
399        return this;
400    }
401
402    /**
403     * Writes a {@code String} value to this stream. This method ensures that the
404     * {@linkplain #position() position} of this stream is updated correctly with respect to any
405     * {@linkplain #LINE_SEPARATOR line separators} present in {@code s}.
406     *
407     * @param s the value to be printed
408     * @return this {@link LogStream} instance
409     */
410    public LogStream print(String s) {
411        if (ps != null) {
412            if (s == null) {
413                indent();
414                lineBuffer.append(s);
415                return this;
416            }
417
418            int index = 0;
419            int next = s.indexOf(LINE_SEPARATOR, index);
420            while (index < s.length()) {
421                indent();
422                if (next > index) {
423                    lineBuffer.append(s.substring(index, next));
424                    flushLine(true);
425                    index = next + LINE_SEPARATOR.length();
426                    next = s.indexOf(LINE_SEPARATOR, index);
427                } else {
428                    lineBuffer.append(s.substring(index));
429                    break;
430                }
431            }
432        }
433        return this;
434    }
435
436    /**
437     * Writes a {@code String} value to this stream followed by a {@linkplain #LINE_SEPARATOR line
438     * separator}.
439     *
440     * @param s the value to be printed
441     * @return this {@link LogStream} instance
442     */
443    public LogStream println(String s) {
444        if (ps != null) {
445            print(s);
446            flushLine(true);
447        }
448        return this;
449    }
450
451    /**
452     * Writes a formatted string to this stream.
453     *
454     * @param format a format string as described in {@link String#format(String, Object...)}
455     * @param args the arguments to be formatted
456     * @return this {@link LogStream} instance
457     */
458    public LogStream printf(String format, Object... args) {
459        if (ps != null) {
460            print(String.format(format, args));
461        }
462        return this;
463    }
464
465    /**
466     * Writes a {@linkplain #LINE_SEPARATOR line separator} to this stream.
467     *
468     * @return this {@code LogStream} instance
469     */
470    public LogStream println() {
471        if (ps != null) {
472            indent();
473            flushLine(true);
474        }
475        return this;
476    }
477}