blob: f831e7a84d2fc28b46c6cd4056832ba20ab4e402 [file] [log] [blame]
/*
* 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);
}
}
}