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 package com.android.dx.util;
18 
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.io.OutputStreamWriter;
22 import java.io.StringWriter;
23 import java.io.Writer;
24 
25 /**
26  * Class that takes a combined output destination and provides two
27  * output writers, one of which ends up writing to the left column and
28  * one which goes on the right.
29  */
30 public final class TwoColumnOutput {
31     /** {@code non-null;} underlying writer for final output */
32     private final Writer out;
33 
34     /** {@code > 0;} the left column width */
35     private final int leftWidth;
36 
37     /** {@code non-null;} pending left column output */
38     private final StringBuffer leftBuf;
39 
40     /** {@code non-null;} pending right column output */
41     private final StringBuffer rightBuf;
42 
43     /** {@code non-null;} left column writer */
44     private final IndentingWriter leftColumn;
45 
46     /** {@code non-null;} right column writer */
47     private final IndentingWriter rightColumn;
48 
49     /**
50      * Turns the given two strings (with widths) and spacer into a formatted
51      * two-column string.
52      *
53      * @param s1 {@code non-null;} first string
54      * @param width1 {@code > 0;} width of the first column
55      * @param spacer {@code non-null;} spacer string
56      * @param s2 {@code non-null;} second string
57      * @param width2 {@code > 0;} width of the second column
58      * @return {@code non-null;} an appropriately-formatted string
59      */
toString(String s1, int width1, String spacer, String s2, int width2)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      */
TwoColumnOutput(Writer out, int leftWidth, int rightWidth, String spacer)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      */
TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth, String spacer)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      */
getLeft()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      */
getRight()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      */
flush()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      */
outputFullLines()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      */
flushLeft()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      */
flushRight()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      */
appendNewlineIfNecessary(StringBuffer buf, Writer out)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      */
writeSpaces(Writer out, int amt)248     private static void writeSpaces(Writer out, int amt) throws IOException {
249         while (amt > 0) {
250             out.write(' ');
251             amt--;
252         }
253     }
254 }
255