| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.util; |
| |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.util.Arrays; |
| |
| /** |
| * A writer that breaks up its output into chunks before writing to its out writer, |
| * and which is linebreak aware, i.e., chunks will created along line breaks, if |
| * possible. |
| * |
| * Note: this class is not thread-safe. |
| */ |
| public class LineBreakBufferedWriter extends PrintWriter { |
| |
| /** |
| * A buffer to collect data until the buffer size is reached. |
| * |
| * Note: we manage a char[] ourselves to avoid an allocation when printing to the |
| * out writer. Otherwise a StringBuilder would have been simpler to use. |
| */ |
| private char[] buffer; |
| |
| /** |
| * The index of the first free element in the buffer. |
| */ |
| private int bufferIndex; |
| |
| /** |
| * The chunk size (=maximum buffer size) to use for this writer. |
| */ |
| private final int bufferSize; |
| |
| |
| /** |
| * Index of the last newline character discovered in the buffer. The writer will try |
| * to split there. |
| */ |
| private int lastNewline = -1; |
| |
| /** |
| * The line separator for println(). |
| */ |
| private final String lineSeparator; |
| |
| /** |
| * Create a new linebreak-aware buffered writer with the given output and buffer |
| * size. The initial capacity will be a default value. |
| * @param out The writer to write to. |
| * @param bufferSize The maximum buffer size. |
| */ |
| public LineBreakBufferedWriter(Writer out, int bufferSize) { |
| this(out, bufferSize, 16); // 16 is the default size of a StringBuilder buffer. |
| } |
| |
| /** |
| * Create a new linebreak-aware buffered writer with the given output, buffer |
| * size and initial capacity. |
| * @param out The writer to write to. |
| * @param bufferSize The maximum buffer size. |
| * @param initialCapacity The initial capacity of the internal buffer. |
| */ |
| public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) { |
| super(out); |
| this.buffer = new char[Math.min(initialCapacity, bufferSize)]; |
| this.bufferIndex = 0; |
| this.bufferSize = bufferSize; |
| this.lineSeparator = System.getProperty("line.separator"); |
| } |
| |
| /** |
| * Flush the current buffer. This will ignore line breaks. |
| */ |
| @Override |
| public void flush() { |
| writeBuffer(bufferIndex); |
| bufferIndex = 0; |
| super.flush(); |
| } |
| |
| @Override |
| public void write(int c) { |
| if (bufferIndex < bufferSize) { |
| buffer[bufferIndex] = (char)c; |
| bufferIndex++; |
| if ((char)c == '\n') { |
| lastNewline = bufferIndex; |
| } |
| } else { |
| // This should be an uncommon case, we mostly expect char[] and String. So |
| // let the chunking be handled by the char[] case. |
| write(new char[] { (char)c }, 0 ,1); |
| } |
| } |
| |
| @Override |
| public void println() { |
| write(lineSeparator); |
| } |
| |
| @Override |
| public void write(char[] buf, int off, int len) { |
| while (bufferIndex + len > bufferSize) { |
| // Find the next newline in the buffer, see if that's below the limit. |
| // Repeat. |
| int nextNewLine = -1; |
| int maxLength = bufferSize - bufferIndex; |
| for (int i = 0; i < maxLength; i++) { |
| if (buf[off + i] == '\n') { |
| if (bufferIndex + i < bufferSize) { |
| nextNewLine = i; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| if (nextNewLine != -1) { |
| // We can add some more data. |
| appendToBuffer(buf, off, nextNewLine); |
| writeBuffer(bufferIndex); |
| bufferIndex = 0; |
| lastNewline = -1; |
| off += nextNewLine + 1; |
| len -= nextNewLine + 1; |
| } else if (lastNewline != -1) { |
| // Use the last newline. |
| writeBuffer(lastNewline); |
| removeFromBuffer(lastNewline + 1); |
| lastNewline = -1; |
| } else { |
| // OK, there was no newline, break at a full buffer. |
| int rest = bufferSize - bufferIndex; |
| appendToBuffer(buf, off, rest); |
| writeBuffer(bufferIndex); |
| bufferIndex = 0; |
| off += rest; |
| len -= rest; |
| } |
| } |
| |
| // Add to the buffer, this will fit. |
| if (len > 0) { |
| // Add the chars, find the last newline. |
| appendToBuffer(buf, off, len); |
| for (int i = len - 1; i >= 0; i--) { |
| if (buf[off + i] == '\n') { |
| lastNewline = bufferIndex - len + i; |
| break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void write(String s, int off, int len) { |
| while (bufferIndex + len > bufferSize) { |
| // Find the next newline in the buffer, see if that's below the limit. |
| // Repeat. |
| int nextNewLine = -1; |
| int maxLength = bufferSize - bufferIndex; |
| for (int i = 0; i < maxLength; i++) { |
| if (s.charAt(off + i) == '\n') { |
| if (bufferIndex + i < bufferSize) { |
| nextNewLine = i; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| if (nextNewLine != -1) { |
| // We can add some more data. |
| appendToBuffer(s, off, nextNewLine); |
| writeBuffer(bufferIndex); |
| bufferIndex = 0; |
| lastNewline = -1; |
| off += nextNewLine + 1; |
| len -= nextNewLine + 1; |
| } else if (lastNewline != -1) { |
| // Use the last newline. |
| writeBuffer(lastNewline); |
| removeFromBuffer(lastNewline + 1); |
| lastNewline = -1; |
| } else { |
| // OK, there was no newline, break at a full buffer. |
| int rest = bufferSize - bufferIndex; |
| appendToBuffer(s, off, rest); |
| writeBuffer(bufferIndex); |
| bufferIndex = 0; |
| off += rest; |
| len -= rest; |
| } |
| } |
| |
| // Add to the buffer, this will fit. |
| if (len > 0) { |
| // Add the chars, find the last newline. |
| appendToBuffer(s, off, len); |
| for (int i = len - 1; i >= 0; i--) { |
| if (s.charAt(off + i) == '\n') { |
| lastNewline = bufferIndex - len + i; |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Append the characters to the buffer. This will potentially resize the buffer, |
| * and move the index along. |
| * @param buf The char[] containing the data. |
| * @param off The start index to copy from. |
| * @param len The number of characters to copy. |
| */ |
| private void appendToBuffer(char[] buf, int off, int len) { |
| if (bufferIndex + len > buffer.length) { |
| ensureCapacity(bufferIndex + len); |
| } |
| System.arraycopy(buf, off, buffer, bufferIndex, len); |
| bufferIndex += len; |
| } |
| |
| /** |
| * Append the characters from the given string to the buffer. This will potentially |
| * resize the buffer, and move the index along. |
| * @param s The string supplying the characters. |
| * @param off The start index to copy from. |
| * @param len The number of characters to copy. |
| */ |
| private void appendToBuffer(String s, int off, int len) { |
| if (bufferIndex + len > buffer.length) { |
| ensureCapacity(bufferIndex + len); |
| } |
| s.getChars(off, off + len, buffer, bufferIndex); |
| bufferIndex += len; |
| } |
| |
| /** |
| * Resize the buffer. We use the usual double-the-size plus constant scheme for |
| * amortized O(1) insert. Note: we expect small buffers, so this won't check for |
| * overflow. |
| * @param capacity The size to be ensured. |
| */ |
| private void ensureCapacity(int capacity) { |
| int newSize = buffer.length * 2 + 2; |
| if (newSize < capacity) { |
| newSize = capacity; |
| } |
| buffer = Arrays.copyOf(buffer, newSize); |
| } |
| |
| /** |
| * Remove the characters up to (and excluding) index i from the buffer. This will |
| * not resize the buffer, but will update bufferIndex. |
| * @param i The number of characters to remove from the front. |
| */ |
| private void removeFromBuffer(int i) { |
| int rest = bufferIndex - i; |
| if (rest > 0) { |
| System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest); |
| bufferIndex = rest; |
| } else { |
| bufferIndex = 0; |
| } |
| } |
| |
| /** |
| * Helper method, write the given part of the buffer, [start,length), to the output. |
| * @param length The number of characters to flush. |
| */ |
| private void writeBuffer(int length) { |
| if (length > 0) { |
| super.write(buffer, 0, length); |
| } |
| } |
| } |