001/* 
002 * Copyright (C) 2016 Hobrasoft s.r.o.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Affero General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Affero General Public License for more details.
013 *
014 * You should have received a copy of the GNU Affero General Public License
015 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
016 */
017package cz.hobrasoft.pdfmu;
018
019import java.io.Flushable;
020import java.io.IOException;
021import java.io.PrintStream;
022import org.apache.commons.lang3.StringUtils;
023
024/**
025 * Prints indented messages to a {@link PrintStream}
026 *
027 * <p>
028 * The indentation level starts at 0 and can be changed using
029 * {@link #indentMore} and {@link #indentLess}.
030 *
031 * <p>
032 * Example of usage:
033 * <pre>
034 * {@code
035 * TextOutput to = new TextOutput(System.out);
036 * to.println("Hello!");
037 * to.indentMore("My favorite fruits:");
038 * to.println("Banana");
039 * to.println("Apple");
040 * to.indentLess();
041 * to.flush();
042 * }
043 * </pre>
044 *
045 * @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
046 */
047public class TextOutput implements Flushable {
048
049    private final PrintStream ps;
050    private int indentLevel = 0;
051    private static final String indentString = "  ";
052
053    /**
054     * Creates a {@link TextOutput} bound to a {@link PrintStream}
055     *
056     * @param ps the {@link PrintStream} to print messages to
057     */
058    public TextOutput(PrintStream ps) {
059        // ps may be null
060        this.ps = ps;
061    }
062
063    /**
064     * Creates a {@link TextOutput} that discards all the messages
065     */
066    public TextOutput() {
067        this(null);
068    }
069
070    /**
071     * Prints a message on a separate line
072     *
073     * <p>
074     * The message is indented by the current indentation level (starting at 0)
075     * and printed using the specified {@link PrintStream}.
076     *
077     * <p>
078     * Mimics {@link PrintStream#println(String x)}.
079     *
080     * @param x message to print
081     */
082    public void println(String x) {
083        if (ps != null) {
084            assert indentLevel >= 0;
085            String prefix = StringUtils.repeat(indentString, indentLevel);
086            ps.println(String.format("%s%s", prefix, x));
087        }
088    }
089
090    /**
091     * Increments the indentation level
092     *
093     * <p>
094     * If the code between {@link #indentMore} and {@link #indentLess} may throw
095     * a (checked) exception, surround the code with a
096     * {@code try}-{@code finally} block like this to maintain consistency:
097     * <pre>
098     * {@code
099     * TextOutput to;
100     * to.indentMore();
101     * try {
102     *   // Do stuff, possibly throwing an exception
103     * // Possibly catch the exception
104     * } finally {
105     *   to.indentLess();
106     * }
107     * }
108     * </pre>
109     */
110    public void indentMore() {
111        ++indentLevel;
112    }
113
114    /**
115     * Prints a message and increments the indentation level
116     *
117     * @param message message to print
118     */
119    public void indentMore(String message) {
120        println(message);
121        indentMore();
122    }
123
124    /**
125     * Decrements the indentation level
126     */
127    public void indentLess() {
128        if (indentLevel > 0) {
129            --indentLevel;
130        }
131        assert indentLevel >= 0;
132    }
133
134    /**
135     * Flushes the underlying {@link PrintStream}
136     *
137     * @throws IOException if the underlying {@link PrintStream#flush} throws an
138     * {@link IOException}
139     */
140    @Override
141    public void flush() throws IOException {
142        if (ps != null) {
143            ps.flush();
144        }
145    }
146}