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