TwoColumnOutput.java revision 373ff22ec69bb6e93646994347b6d80502be1588
1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.util;
33
34import java.io.*;
35
36/**
37 * Class that takes a combined output destination and provides two
38 * output writers, one of which ends up writing to the left column and
39 * one which goes on the right.
40 */
41public final class TwoColumnOutput {
42    /** non-null; underlying writer for final output */
43    private final Writer out;
44
45    /** > 0; the left column width */
46    private final int leftWidth;
47
48    /** non-null; pending left column output */
49    private final StringBuffer leftBuf;
50
51    /** non-null; pending right column output */
52    private final StringBuffer rightBuf;
53
54    /** non-null; left column writer */
55    private final WrappedIndentingWriter leftColumn;
56
57    /** non-null; right column writer */
58    private final WrappedIndentingWriter rightColumn;
59
60    /**
61     * Turns the given two strings (with widths) and spacer into a formatted
62     * two-column string.
63     *
64     * @param s1 non-null; first string
65     * @param width1 > 0; width of the first column
66     * @param spacer non-null; spacer string
67     * @param s2 non-null; second string
68     * @param width2 > 0; width of the second column
69     * @return non-null; an appropriately-formatted string
70     */
71    public static String toString(String s1, int width1, String spacer,
72                                  String s2, int width2) {
73        int len1 = s1.length();
74        int len2 = s2.length();
75
76        StringWriter sw = new StringWriter((len1 + len2) * 3);
77        TwoColumnOutput twoOut =
78            new TwoColumnOutput(sw, width1, width2, spacer);
79
80        try {
81            twoOut.getLeft().write(s1);
82            twoOut.getRight().write(s2);
83        } catch (IOException ex) {
84            throw new RuntimeException("shouldn't happen", ex);
85        }
86
87        twoOut.flush();
88        return sw.toString();
89    }
90
91    /**
92     * Constructs an instance.
93     *
94     * @param out non-null; writer to send final output to
95     * @param leftWidth > 0; width of the left column, in characters
96     * @param rightWidth > 0; width of the right column, in characters
97     * @param spacer non-null; spacer string to sit between the two columns
98     */
99    public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
100                           String spacer) {
101        if (out == null) {
102            throw new NullPointerException("out == null");
103        }
104
105        if (leftWidth < 1) {
106            throw new IllegalArgumentException("leftWidth < 1");
107        }
108
109        if (rightWidth < 1) {
110            throw new IllegalArgumentException("rightWidth < 1");
111        }
112
113        if (spacer == null) {
114            throw new NullPointerException("spacer == null");
115        }
116
117        StringWriter leftWriter = new StringWriter(1000);
118        StringWriter rightWriter = new StringWriter(1000);
119
120        this.out = out;
121        this.leftWidth = leftWidth;
122        this.leftBuf = leftWriter.getBuffer();
123        this.rightBuf = rightWriter.getBuffer();
124        this.leftColumn = new WrappedIndentingWriter(leftWriter, leftWidth);
125        this.rightColumn =
126            new WrappedIndentingWriter(rightWriter, rightWidth, spacer);
127    }
128
129    /**
130     * Constructs an instance.
131     *
132     * @param out non-null; stream to send final output to
133     * @param leftWidth &gt;= 1; width of the left column, in characters
134     * @param rightWidth &gt;= 1; width of the right column, in characters
135     * @param spacer non-null; spacer string to sit between the two columns
136     */
137    public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
138                           String spacer) {
139        this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
140    }
141
142    /**
143     * Gets the writer to use to write to the left column.
144     *
145     * @return non-null; the left column writer
146     */
147    public Writer getLeft() {
148        return leftColumn;
149    }
150
151    /**
152     * Gets the writer to use to write to the right column.
153     *
154     * @return non-null; the right column writer
155     */
156    public Writer getRight() {
157        return rightColumn;
158    }
159
160    /**
161     * Flushes the output. If there are more lines of pending output in one
162     * column, then the other column will get filled with blank lines.
163     */
164    public void flush() {
165        try {
166            appendNewlineIfNecessary(leftBuf, leftColumn);
167            appendNewlineIfNecessary(rightBuf, rightColumn);
168            outputFullLines();
169            flushLeft();
170            flushRight();
171        } catch (IOException ex) {
172            throw new RuntimeException(ex);
173        }
174    }
175
176    /**
177     * Outputs to the final destination as many full line pairs as
178     * there are in the pending output, removing those lines from
179     * their respective buffers. This method terminates when at
180     * least one of the two column buffers is empty.
181     */
182    private void outputFullLines() throws IOException {
183        for (;;) {
184            int leftLen = leftBuf.indexOf("\n");
185            if (leftLen < 0) {
186                return;
187            }
188
189            int rightLen = rightBuf.indexOf("\n");
190            if (rightLen < 0) {
191                return;
192            }
193
194            if (leftLen != 0) {
195                out.write(leftBuf.substring(0, leftLen));
196            }
197
198            if (rightLen != 0) {
199                writeSpaces(out, leftWidth - leftLen);
200                out.write(rightBuf.substring(0, rightLen));
201            }
202
203            out.write('\n');
204
205            leftBuf.delete(0, leftLen + 1);
206            rightBuf.delete(0, rightLen + 1);
207        }
208    }
209
210    /**
211     * Flushes the left column buffer, printing it and clearing the buffer.
212     * If the buffer is already empty, this does nothing.
213     */
214    private void flushLeft() throws IOException {
215        appendNewlineIfNecessary(leftBuf, leftColumn);
216
217        while (leftBuf.length() != 0) {
218            rightColumn.write('\n');
219            outputFullLines();
220        }
221    }
222
223    /**
224     * Flushes the right column buffer, printing it and clearing the buffer.
225     * If the buffer is already empty, this does nothing.
226     */
227    private void flushRight() throws IOException {
228        appendNewlineIfNecessary(rightBuf, rightColumn);
229
230        while (rightBuf.length() != 0) {
231            leftColumn.write('\n');
232            outputFullLines();
233        }
234    }
235
236    /**
237     * Appends a newline to the given buffer via the given writer, but
238     * only if it isn't empty and doesn't already end with one.
239     *
240     * @param buf non-null; the buffer in question
241     * @param out non-null; the writer to use
242     */
243    private static void appendNewlineIfNecessary(StringBuffer buf,
244                                                 Writer out)
245            throws IOException {
246        int len = buf.length();
247
248        if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
249            out.write('\n');
250        }
251    }
252
253    /**
254     * Writes the given number of spaces to the given writer.
255     *
256     * @param out non-null; where to write
257     * @param amt &gt;= 0; the number of spaces to write
258     */
259    private static void writeSpaces(Writer out, int amt) throws IOException {
260        while (amt > 0) {
261            out.write(' ');
262            amt--;
263        }
264    }
265}
266