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