1 /* 2 * Copyright (C) 2015 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.internal.util; 18 19 import java.io.PrintWriter; 20 import java.io.Writer; 21 import java.util.Arrays; 22 23 /** 24 * A writer that breaks up its output into chunks before writing to its out writer, 25 * and which is linebreak aware, i.e., chunks will created along line breaks, if 26 * possible. 27 * 28 * Note: this class is not thread-safe. 29 */ 30 public class LineBreakBufferedWriter extends PrintWriter { 31 32 /** 33 * A buffer to collect data until the buffer size is reached. 34 * 35 * Note: we manage a char[] ourselves to avoid an allocation when printing to the 36 * out writer. Otherwise a StringBuilder would have been simpler to use. 37 */ 38 private char[] buffer; 39 40 /** 41 * The index of the first free element in the buffer. 42 */ 43 private int bufferIndex; 44 45 /** 46 * The chunk size (=maximum buffer size) to use for this writer. 47 */ 48 private final int bufferSize; 49 50 51 /** 52 * Index of the last newline character discovered in the buffer. The writer will try 53 * to split there. 54 */ 55 private int lastNewline = -1; 56 57 /** 58 * The line separator for println(). 59 */ 60 private final String lineSeparator; 61 62 /** 63 * Create a new linebreak-aware buffered writer with the given output and buffer 64 * size. The initial capacity will be a default value. 65 * @param out The writer to write to. 66 * @param bufferSize The maximum buffer size. 67 */ LineBreakBufferedWriter(Writer out, int bufferSize)68 public LineBreakBufferedWriter(Writer out, int bufferSize) { 69 this(out, bufferSize, 16); // 16 is the default size of a StringBuilder buffer. 70 } 71 72 /** 73 * Create a new linebreak-aware buffered writer with the given output, buffer 74 * size and initial capacity. 75 * @param out The writer to write to. 76 * @param bufferSize The maximum buffer size. 77 * @param initialCapacity The initial capacity of the internal buffer. 78 */ LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity)79 public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) { 80 super(out); 81 this.buffer = new char[Math.min(initialCapacity, bufferSize)]; 82 this.bufferIndex = 0; 83 this.bufferSize = bufferSize; 84 this.lineSeparator = System.getProperty("line.separator"); 85 } 86 87 /** 88 * Flush the current buffer. This will ignore line breaks. 89 */ 90 @Override flush()91 public void flush() { 92 writeBuffer(bufferIndex); 93 bufferIndex = 0; 94 super.flush(); 95 } 96 97 @Override write(int c)98 public void write(int c) { 99 if (bufferIndex < buffer.length) { 100 buffer[bufferIndex] = (char)c; 101 bufferIndex++; 102 if ((char)c == '\n') { 103 lastNewline = bufferIndex; 104 } 105 } else { 106 // This should be an uncommon case, we mostly expect char[] and String. So 107 // let the chunking be handled by the char[] case. 108 write(new char[] { (char)c }, 0 ,1); 109 } 110 } 111 112 @Override println()113 public void println() { 114 write(lineSeparator); 115 } 116 117 @Override write(char[] buf, int off, int len)118 public void write(char[] buf, int off, int len) { 119 while (bufferIndex + len > bufferSize) { 120 // Find the next newline in the buffer, see if that's below the limit. 121 // Repeat. 122 int nextNewLine = -1; 123 int maxLength = bufferSize - bufferIndex; 124 for (int i = 0; i < maxLength; i++) { 125 if (buf[off + i] == '\n') { 126 if (bufferIndex + i < bufferSize) { 127 nextNewLine = i; 128 } else { 129 break; 130 } 131 } 132 } 133 134 if (nextNewLine != -1) { 135 // We can add some more data. 136 appendToBuffer(buf, off, nextNewLine); 137 writeBuffer(bufferIndex); 138 bufferIndex = 0; 139 lastNewline = -1; 140 off += nextNewLine + 1; 141 len -= nextNewLine + 1; 142 } else if (lastNewline != -1) { 143 // Use the last newline. 144 writeBuffer(lastNewline); 145 removeFromBuffer(lastNewline + 1); 146 lastNewline = -1; 147 } else { 148 // OK, there was no newline, break at a full buffer. 149 int rest = bufferSize - bufferIndex; 150 appendToBuffer(buf, off, rest); 151 writeBuffer(bufferIndex); 152 bufferIndex = 0; 153 off += rest; 154 len -= rest; 155 } 156 } 157 158 // Add to the buffer, this will fit. 159 if (len > 0) { 160 // Add the chars, find the last newline. 161 appendToBuffer(buf, off, len); 162 for (int i = len - 1; i >= 0; i--) { 163 if (buf[off + i] == '\n') { 164 lastNewline = bufferIndex - len + i; 165 break; 166 } 167 } 168 } 169 } 170 171 @Override write(String s, int off, int len)172 public void write(String s, int off, int len) { 173 while (bufferIndex + len > bufferSize) { 174 // Find the next newline in the buffer, see if that's below the limit. 175 // Repeat. 176 int nextNewLine = -1; 177 int maxLength = bufferSize - bufferIndex; 178 for (int i = 0; i < maxLength; i++) { 179 if (s.charAt(off + i) == '\n') { 180 if (bufferIndex + i < bufferSize) { 181 nextNewLine = i; 182 } else { 183 break; 184 } 185 } 186 } 187 188 if (nextNewLine != -1) { 189 // We can add some more data. 190 appendToBuffer(s, off, nextNewLine); 191 writeBuffer(bufferIndex); 192 bufferIndex = 0; 193 lastNewline = -1; 194 off += nextNewLine + 1; 195 len -= nextNewLine + 1; 196 } else if (lastNewline != -1) { 197 // Use the last newline. 198 writeBuffer(lastNewline); 199 removeFromBuffer(lastNewline + 1); 200 lastNewline = -1; 201 } else { 202 // OK, there was no newline, break at a full buffer. 203 int rest = bufferSize - bufferIndex; 204 appendToBuffer(s, off, rest); 205 writeBuffer(bufferIndex); 206 bufferIndex = 0; 207 off += rest; 208 len -= rest; 209 } 210 } 211 212 // Add to the buffer, this will fit. 213 if (len > 0) { 214 // Add the chars, find the last newline. 215 appendToBuffer(s, off, len); 216 for (int i = len - 1; i >= 0; i--) { 217 if (s.charAt(off + i) == '\n') { 218 lastNewline = bufferIndex - len + i; 219 break; 220 } 221 } 222 } 223 } 224 225 /** 226 * Append the characters to the buffer. This will potentially resize the buffer, 227 * and move the index along. 228 * @param buf The char[] containing the data. 229 * @param off The start index to copy from. 230 * @param len The number of characters to copy. 231 */ appendToBuffer(char[] buf, int off, int len)232 private void appendToBuffer(char[] buf, int off, int len) { 233 if (bufferIndex + len > buffer.length) { 234 ensureCapacity(bufferIndex + len); 235 } 236 System.arraycopy(buf, off, buffer, bufferIndex, len); 237 bufferIndex += len; 238 } 239 240 /** 241 * Append the characters from the given string to the buffer. This will potentially 242 * resize the buffer, and move the index along. 243 * @param s The string supplying the characters. 244 * @param off The start index to copy from. 245 * @param len The number of characters to copy. 246 */ appendToBuffer(String s, int off, int len)247 private void appendToBuffer(String s, int off, int len) { 248 if (bufferIndex + len > buffer.length) { 249 ensureCapacity(bufferIndex + len); 250 } 251 s.getChars(off, off + len, buffer, bufferIndex); 252 bufferIndex += len; 253 } 254 255 /** 256 * Resize the buffer. We use the usual double-the-size plus constant scheme for 257 * amortized O(1) insert. Note: we expect small buffers, so this won't check for 258 * overflow. 259 * @param capacity The size to be ensured. 260 */ ensureCapacity(int capacity)261 private void ensureCapacity(int capacity) { 262 int newSize = buffer.length * 2 + 2; 263 if (newSize < capacity) { 264 newSize = capacity; 265 } 266 buffer = Arrays.copyOf(buffer, newSize); 267 } 268 269 /** 270 * Remove the characters up to (and excluding) index i from the buffer. This will 271 * not resize the buffer, but will update bufferIndex. 272 * @param i The number of characters to remove from the front. 273 */ removeFromBuffer(int i)274 private void removeFromBuffer(int i) { 275 int rest = bufferIndex - i; 276 if (rest > 0) { 277 System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest); 278 bufferIndex = rest; 279 } else { 280 bufferIndex = 0; 281 } 282 } 283 284 /** 285 * Helper method, write the given part of the buffer, [start,length), to the output. 286 * @param length The number of characters to flush. 287 */ writeBuffer(int length)288 private void writeBuffer(int length) { 289 if (length > 0) { 290 super.write(buffer, 0, length); 291 } 292 } 293 } 294