summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/util/TwoColumnOutput.java
blob: ed2ab9f47bcbf5d120138a4bee8e8ab0650afe83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.util;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;

/**
 * Class that takes a combined output destination and provides two
 * output writers, one of which ends up writing to the left column and
 * one which goes on the right.
 */
public final class TwoColumnOutput {
    /** {@code non-null;} underlying writer for final output */
    private final Writer out;

    /** {@code > 0;} the left column width */
    private final int leftWidth;

    /** {@code non-null;} pending left column output */
    private final StringBuffer leftBuf;

    /** {@code non-null;} pending right column output */
    private final StringBuffer rightBuf;

    /** {@code non-null;} left column writer */
    private final IndentingWriter leftColumn;

    /** {@code non-null;} right column writer */
    private final IndentingWriter rightColumn;

    /**
     * Turns the given two strings (with widths) and spacer into a formatted
     * two-column string.
     *
     * @param s1 {@code non-null;} first string
     * @param width1 {@code > 0;} width of the first column
     * @param spacer {@code non-null;} spacer string
     * @param s2 {@code non-null;} second string
     * @param width2 {@code > 0;} width of the second column
     * @return {@code non-null;} an appropriately-formatted string
     */
    public static String toString(String s1, int width1, String spacer,
                                  String s2, int width2) {
        int len1 = s1.length();
        int len2 = s2.length();

        StringWriter sw = new StringWriter((len1 + len2) * 3);
        TwoColumnOutput twoOut =
            new TwoColumnOutput(sw, width1, width2, spacer);

        try {
            twoOut.getLeft().write(s1);
            twoOut.getRight().write(s2);
        } catch (IOException ex) {
            throw new RuntimeException("shouldn't happen", ex);
        }

        twoOut.flush();
        return sw.toString();
    }

    /**
     * Constructs an instance.
     *
     * @param out {@code non-null;} writer to send final output to
     * @param leftWidth {@code > 0;} width of the left column, in characters
     * @param rightWidth {@code > 0;} width of the right column, in characters
     * @param spacer {@code non-null;} spacer string to sit between the two columns
     */
    public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
                           String spacer) {
        if (out == null) {
            throw new NullPointerException("out == null");
        }

        if (leftWidth < 1) {
            throw new IllegalArgumentException("leftWidth < 1");
        }

        if (rightWidth < 1) {
            throw new IllegalArgumentException("rightWidth < 1");
        }

        if (spacer == null) {
            throw new NullPointerException("spacer == null");
        }

        StringWriter leftWriter = new StringWriter(1000);
        StringWriter rightWriter = new StringWriter(1000);

        this.out = out;
        this.leftWidth = leftWidth;
        this.leftBuf = leftWriter.getBuffer();
        this.rightBuf = rightWriter.getBuffer();
        this.leftColumn = new IndentingWriter(leftWriter, leftWidth);
        this.rightColumn =
            new IndentingWriter(rightWriter, rightWidth, spacer);
    }

    /**
     * Constructs an instance.
     *
     * @param out {@code non-null;} stream to send final output to
     * @param leftWidth {@code >= 1;} width of the left column, in characters
     * @param rightWidth {@code >= 1;} width of the right column, in characters
     * @param spacer {@code non-null;} spacer string to sit between the two columns
     */
    public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
                           String spacer) {
        this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
    }

    /**
     * Gets the writer to use to write to the left column.
     *
     * @return {@code non-null;} the left column writer
     */
    public Writer getLeft() {
        return leftColumn;
    }

    /**
     * Gets the writer to use to write to the right column.
     *
     * @return {@code non-null;} the right column writer
     */
    public Writer getRight() {
        return rightColumn;
    }

    /**
     * Flushes the output. If there are more lines of pending output in one
     * column, then the other column will get filled with blank lines.
     */
    public void flush() {
        try {
            appendNewlineIfNecessary(leftBuf, leftColumn);
            appendNewlineIfNecessary(rightBuf, rightColumn);
            outputFullLines();
            flushLeft();
            flushRight();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Outputs to the final destination as many full line pairs as
     * there are in the pending output, removing those lines from
     * their respective buffers. This method terminates when at
     * least one of the two column buffers is empty.
     */
    private void outputFullLines() throws IOException {
        for (;;) {
            int leftLen = leftBuf.indexOf("\n");
            if (leftLen < 0) {
                return;
            }

            int rightLen = rightBuf.indexOf("\n");
            if (rightLen < 0) {
                return;
            }

            if (leftLen != 0) {
                out.write(leftBuf.substring(0, leftLen));
            }

            if (rightLen != 0) {
                writeSpaces(out, leftWidth - leftLen);
                out.write(rightBuf.substring(0, rightLen));
            }

            out.write('\n');

            leftBuf.delete(0, leftLen + 1);
            rightBuf.delete(0, rightLen + 1);
        }
    }

    /**
     * Flushes the left column buffer, printing it and clearing the buffer.
     * If the buffer is already empty, this does nothing.
     */
    private void flushLeft() throws IOException {
        appendNewlineIfNecessary(leftBuf, leftColumn);

        while (leftBuf.length() != 0) {
            rightColumn.write('\n');
            outputFullLines();
        }
    }

    /**
     * Flushes the right column buffer, printing it and clearing the buffer.
     * If the buffer is already empty, this does nothing.
     */
    private void flushRight() throws IOException {
        appendNewlineIfNecessary(rightBuf, rightColumn);

        while (rightBuf.length() != 0) {
            leftColumn.write('\n');
            outputFullLines();
        }
    }

    /**
     * Appends a newline to the given buffer via the given writer, but
     * only if it isn't empty and doesn't already end with one.
     *
     * @param buf {@code non-null;} the buffer in question
     * @param out {@code non-null;} the writer to use
     */
    private static void appendNewlineIfNecessary(StringBuffer buf,
                                                 Writer out)
            throws IOException {
        int len = buf.length();

        if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
            out.write('\n');
        }
    }

    /**
     * Writes the given number of spaces to the given writer.
     *
     * @param out {@code non-null;} where to write
     * @param amt {@code >= 0;} the number of spaces to write
     */
    private static void writeSpaces(Writer out, int amt) throws IOException {
        while (amt > 0) {
            out.write(' ');
            amt--;
        }
    }
}