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