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