1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * As per the Apache license requirements, this file has been modified
19 * from its original state.
20 *
21 * Such modifications are Copyright (C) 2010 Ben Gruver, and are released
22 * under the original license
23 */
24
25package org.jf.dexlib.Util;
26
27import java.io.*;
28
29/**
30 * Class that takes a combined output destination and provides two
31 * output writers, one of which ends up writing to the left column and
32 * one which goes on the right.
33 */
34public final class TwoColumnOutput {
35    /** non-null; underlying writer for final output */
36    private final Writer out;
37
38    /** > 0; the left column width */
39    private final int leftWidth;
40
41    /** non-null; pending left column output */
42    private final StringBuffer leftBuf;
43
44    /** non-null; pending right column output */
45    private final StringBuffer rightBuf;
46
47    /** non-null; left column writer */
48    private final IndentingWriter leftColumn;
49
50    /** non-null; right column writer */
51    private final IndentingWriter rightColumn;
52
53    /**
54     * Turns the given two strings (with widths) and spacer into a formatted
55     * two-column string.
56     *
57     * @param s1 non-null; first string
58     * @param width1 > 0; width of the first column
59     * @param spacer non-null; spacer string
60     * @param s2 non-null; second string
61     * @param width2 > 0; width of the second column
62     * @return non-null; an appropriately-formatted string
63     */
64    public static String toString(String s1, int width1, String spacer,
65                                  String s2, int width2) {
66        int len1 = s1.length();
67        int len2 = s2.length();
68
69        StringWriter sw = new StringWriter((len1 + len2) * 3);
70        TwoColumnOutput twoOut =
71            new TwoColumnOutput(sw, width1, width2, spacer);
72
73        try {
74            twoOut.getLeft().write(s1);
75            twoOut.getRight().write(s2);
76        } catch (IOException ex) {
77            throw new RuntimeException("shouldn't happen", ex);
78        }
79
80        twoOut.flush();
81        return sw.toString();
82    }
83
84    /**
85     * Constructs an instance.
86     *
87     * @param out non-null; writer to send final output to
88     * @param leftWidth > 0; width of the left column, in characters
89     * @param rightWidth > 0; width of the right column, in characters
90     * @param spacer non-null; spacer string to sit between the two columns
91     */
92    public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
93                           String spacer) {
94        if (out == null) {
95            throw new NullPointerException("out == null");
96        }
97
98        if (leftWidth < 1) {
99            throw new IllegalArgumentException("leftWidth < 1");
100        }
101
102        if (rightWidth < 1) {
103            throw new IllegalArgumentException("rightWidth < 1");
104        }
105
106        if (spacer == null) {
107            throw new NullPointerException("spacer == null");
108        }
109
110        StringWriter leftWriter = new StringWriter(1000);
111        StringWriter rightWriter = new StringWriter(1000);
112
113        this.out = out;
114        this.leftWidth = leftWidth;
115        this.leftBuf = leftWriter.getBuffer();
116        this.rightBuf = rightWriter.getBuffer();
117        this.leftColumn = new IndentingWriter(leftWriter, leftWidth);
118        this.rightColumn =
119            new IndentingWriter(rightWriter, rightWidth, spacer);
120    }
121
122    /**
123     * Constructs an instance.
124     *
125     * @param out non-null; stream to send final output to
126     * @param leftWidth &gt;= 1; width of the left column, in characters
127     * @param rightWidth &gt;= 1; width of the right column, in characters
128     * @param spacer non-null; spacer string to sit between the two columns
129     */
130    public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
131                           String spacer) {
132        this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
133    }
134
135    /**
136     * Gets the writer to use to write to the left column.
137     *
138     * @return non-null; the left column writer
139     */
140    public Writer getLeft() {
141        return leftColumn;
142    }
143
144    /**
145     * Gets the writer to use to write to the right column.
146     *
147     * @return non-null; the right column writer
148     */
149    public Writer getRight() {
150        return rightColumn;
151    }
152
153    /**
154     * Flushes the output. If there are more lines of pending output in one
155     * column, then the other column will get filled with blank lines.
156     */
157    public void flush() {
158        try {
159            appendNewlineIfNecessary(leftBuf, leftColumn);
160            appendNewlineIfNecessary(rightBuf, rightColumn);
161            outputFullLines();
162            flushLeft();
163            flushRight();
164        } catch (IOException ex) {
165            throw new RuntimeException(ex);
166        }
167    }
168
169    /**
170     * Outputs to the final destination as many full line pairs as
171     * there are in the pending output, removing those lines from
172     * their respective buffers. This method terminates when at
173     * least one of the two column buffers is empty.
174     */
175    private void outputFullLines() throws IOException {
176        for (;;) {
177            int leftLen = leftBuf.indexOf("\n");
178            if (leftLen < 0) {
179                return;
180            }
181
182            int rightLen = rightBuf.indexOf("\n");
183            if (rightLen < 0) {
184                return;
185            }
186
187            if (leftLen != 0) {
188                out.write(leftBuf.substring(0, leftLen));
189            }
190
191            if (rightLen != 0) {
192                writeSpaces(out, leftWidth - leftLen);
193                out.write(rightBuf.substring(0, rightLen));
194            }
195
196            out.write('\n');
197
198            leftBuf.delete(0, leftLen + 1);
199            rightBuf.delete(0, rightLen + 1);
200        }
201    }
202
203    /**
204     * Flushes the left column buffer, printing it and clearing the buffer.
205     * If the buffer is already empty, this does nothing.
206     */
207    private void flushLeft() throws IOException {
208        appendNewlineIfNecessary(leftBuf, leftColumn);
209
210        while (leftBuf.length() != 0) {
211            rightColumn.write('\n');
212            outputFullLines();
213        }
214    }
215
216    /**
217     * Flushes the right column buffer, printing it and clearing the buffer.
218     * If the buffer is already empty, this does nothing.
219     */
220    private void flushRight() throws IOException {
221        appendNewlineIfNecessary(rightBuf, rightColumn);
222
223        while (rightBuf.length() != 0) {
224            leftColumn.write('\n');
225            outputFullLines();
226        }
227    }
228
229    /**
230     * Appends a newline to the given buffer via the given writer, but
231     * only if it isn't empty and doesn't already end with one.
232     *
233     * @param buf non-null; the buffer in question
234     * @param out non-null; the writer to use
235     */
236    private static void appendNewlineIfNecessary(StringBuffer buf,
237                                                 Writer out)
238            throws IOException {
239        int len = buf.length();
240
241        if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
242            out.write('\n');
243        }
244    }
245
246    /**
247     * Writes the given number of spaces to the given writer.
248     *
249     * @param out non-null; where to write
250     * @param amt &gt;= 0; the number of spaces to write
251     */
252    private static void writeSpaces(Writer out, int amt) throws IOException {
253        while (amt > 0) {
254            out.write(' ');
255            amt--;
256        }
257    }
258}
259