TwoColumnOutput.java revision 373ff22ec69bb6e93646994347b6d80502be1588
1/* 2 * Copyright 2013, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32package org.jf.util; 33 34import java.io.*; 35 36/** 37 * Class that takes a combined output destination and provides two 38 * output writers, one of which ends up writing to the left column and 39 * one which goes on the right. 40 */ 41public final class TwoColumnOutput { 42 /** non-null; underlying writer for final output */ 43 private final Writer out; 44 45 /** > 0; the left column width */ 46 private final int leftWidth; 47 48 /** non-null; pending left column output */ 49 private final StringBuffer leftBuf; 50 51 /** non-null; pending right column output */ 52 private final StringBuffer rightBuf; 53 54 /** non-null; left column writer */ 55 private final WrappedIndentingWriter leftColumn; 56 57 /** non-null; right column writer */ 58 private final WrappedIndentingWriter rightColumn; 59 60 /** 61 * Turns the given two strings (with widths) and spacer into a formatted 62 * two-column string. 63 * 64 * @param s1 non-null; first string 65 * @param width1 > 0; width of the first column 66 * @param spacer non-null; spacer string 67 * @param s2 non-null; second string 68 * @param width2 > 0; width of the second column 69 * @return non-null; an appropriately-formatted string 70 */ 71 public static String toString(String s1, int width1, String spacer, 72 String s2, int width2) { 73 int len1 = s1.length(); 74 int len2 = s2.length(); 75 76 StringWriter sw = new StringWriter((len1 + len2) * 3); 77 TwoColumnOutput twoOut = 78 new TwoColumnOutput(sw, width1, width2, spacer); 79 80 try { 81 twoOut.getLeft().write(s1); 82 twoOut.getRight().write(s2); 83 } catch (IOException ex) { 84 throw new RuntimeException("shouldn't happen", ex); 85 } 86 87 twoOut.flush(); 88 return sw.toString(); 89 } 90 91 /** 92 * Constructs an instance. 93 * 94 * @param out non-null; writer to send final output to 95 * @param leftWidth > 0; width of the left column, in characters 96 * @param rightWidth > 0; width of the right column, in characters 97 * @param spacer non-null; spacer string to sit between the two columns 98 */ 99 public TwoColumnOutput(Writer out, int leftWidth, int rightWidth, 100 String spacer) { 101 if (out == null) { 102 throw new NullPointerException("out == null"); 103 } 104 105 if (leftWidth < 1) { 106 throw new IllegalArgumentException("leftWidth < 1"); 107 } 108 109 if (rightWidth < 1) { 110 throw new IllegalArgumentException("rightWidth < 1"); 111 } 112 113 if (spacer == null) { 114 throw new NullPointerException("spacer == null"); 115 } 116 117 StringWriter leftWriter = new StringWriter(1000); 118 StringWriter rightWriter = new StringWriter(1000); 119 120 this.out = out; 121 this.leftWidth = leftWidth; 122 this.leftBuf = leftWriter.getBuffer(); 123 this.rightBuf = rightWriter.getBuffer(); 124 this.leftColumn = new WrappedIndentingWriter(leftWriter, leftWidth); 125 this.rightColumn = 126 new WrappedIndentingWriter(rightWriter, rightWidth, spacer); 127 } 128 129 /** 130 * Constructs an instance. 131 * 132 * @param out non-null; stream to send final output to 133 * @param leftWidth >= 1; width of the left column, in characters 134 * @param rightWidth >= 1; width of the right column, in characters 135 * @param spacer non-null; spacer string to sit between the two columns 136 */ 137 public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth, 138 String spacer) { 139 this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer); 140 } 141 142 /** 143 * Gets the writer to use to write to the left column. 144 * 145 * @return non-null; the left column writer 146 */ 147 public Writer getLeft() { 148 return leftColumn; 149 } 150 151 /** 152 * Gets the writer to use to write to the right column. 153 * 154 * @return non-null; the right column writer 155 */ 156 public Writer getRight() { 157 return rightColumn; 158 } 159 160 /** 161 * Flushes the output. If there are more lines of pending output in one 162 * column, then the other column will get filled with blank lines. 163 */ 164 public void flush() { 165 try { 166 appendNewlineIfNecessary(leftBuf, leftColumn); 167 appendNewlineIfNecessary(rightBuf, rightColumn); 168 outputFullLines(); 169 flushLeft(); 170 flushRight(); 171 } catch (IOException ex) { 172 throw new RuntimeException(ex); 173 } 174 } 175 176 /** 177 * Outputs to the final destination as many full line pairs as 178 * there are in the pending output, removing those lines from 179 * their respective buffers. This method terminates when at 180 * least one of the two column buffers is empty. 181 */ 182 private void outputFullLines() throws IOException { 183 for (;;) { 184 int leftLen = leftBuf.indexOf("\n"); 185 if (leftLen < 0) { 186 return; 187 } 188 189 int rightLen = rightBuf.indexOf("\n"); 190 if (rightLen < 0) { 191 return; 192 } 193 194 if (leftLen != 0) { 195 out.write(leftBuf.substring(0, leftLen)); 196 } 197 198 if (rightLen != 0) { 199 writeSpaces(out, leftWidth - leftLen); 200 out.write(rightBuf.substring(0, rightLen)); 201 } 202 203 out.write('\n'); 204 205 leftBuf.delete(0, leftLen + 1); 206 rightBuf.delete(0, rightLen + 1); 207 } 208 } 209 210 /** 211 * Flushes the left column buffer, printing it and clearing the buffer. 212 * If the buffer is already empty, this does nothing. 213 */ 214 private void flushLeft() throws IOException { 215 appendNewlineIfNecessary(leftBuf, leftColumn); 216 217 while (leftBuf.length() != 0) { 218 rightColumn.write('\n'); 219 outputFullLines(); 220 } 221 } 222 223 /** 224 * Flushes the right column buffer, printing it and clearing the buffer. 225 * If the buffer is already empty, this does nothing. 226 */ 227 private void flushRight() throws IOException { 228 appendNewlineIfNecessary(rightBuf, rightColumn); 229 230 while (rightBuf.length() != 0) { 231 leftColumn.write('\n'); 232 outputFullLines(); 233 } 234 } 235 236 /** 237 * Appends a newline to the given buffer via the given writer, but 238 * only if it isn't empty and doesn't already end with one. 239 * 240 * @param buf non-null; the buffer in question 241 * @param out non-null; the writer to use 242 */ 243 private static void appendNewlineIfNecessary(StringBuffer buf, 244 Writer out) 245 throws IOException { 246 int len = buf.length(); 247 248 if ((len != 0) && (buf.charAt(len - 1) != '\n')) { 249 out.write('\n'); 250 } 251 } 252 253 /** 254 * Writes the given number of spaces to the given writer. 255 * 256 * @param out non-null; where to write 257 * @param amt >= 0; the number of spaces to write 258 */ 259 private static void writeSpaces(Writer out, int amt) throws IOException { 260 while (amt > 0) { 261 out.write(' '); 262 amt--; 263 } 264 } 265} 266