blob: c19f3971e4c5fb5198e5563f7fca1f7a3328d407 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.cocoon.util;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* This class is similar to the {@link java.io.BufferedOutputStream}. In
* addition it provides an increasing buffer, the possibility to reset the
* buffer and it counts the number of bytes written to the output stream.
*
* @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
* @version CVS $Id$
* @since 2.1
*/
public class BufferedOutputStream extends FilterOutputStream {
private byte buffer[];
private int count;
private int totalCount;
private final int flushBufferSize;
/**
* Creates a new buffered output stream to write data to the specified
* underlying output stream with a default flush buffer size of 32768 bytes
* and a default initial buffer size of 8192 bytes.
*
* @param out the underlying output stream.
*/
public BufferedOutputStream(final OutputStream out) {
this(out, 32768);
}
/**
* Creates a new buffered output stream to write data to the specified
* underlying output stream with the specified flush buffer size and a
* default initial buffer size of 8192 bytes.
*
* @param out the underlying output stream.
*/
public BufferedOutputStream(final OutputStream out, final int flushBufferSize) {
this(out, flushBufferSize, 8192);
}
/**
* Creates a new buffered output stream to write data to the specified
* underlying output stream with the specified buffer sizes.
*
* @param out the underlying output stream.
* @param flushBufferSize the buffer size when the stream is flushed. Must
* be greater than 0 or -1 meaning the stream never
* flushes itself.
* @param initialBufferSize the initial buffer size. Must be greater than 0.
* Will be limited to the flush buffer size.
*/
public BufferedOutputStream(final OutputStream out,
final int flushBufferSize,
final int initialBufferSize) {
super(out);
if (flushBufferSize <= 0 && flushBufferSize != -1) {
throw new IllegalArgumentException("Flush buffer size <= 0 && != -1");
}
if (initialBufferSize <= 0) {
throw new IllegalArgumentException("Initial buffer size <= 0");
}
int actualInitialBufferSize =
flushBufferSize > 0 && initialBufferSize > flushBufferSize ? flushBufferSize
: initialBufferSize;
this.buffer = new byte[actualInitialBufferSize];
this.flushBufferSize = flushBufferSize;
}
/**
* Writes the specified byte to this buffered output stream.
*
* @param b the byte to be written.
* @exception IOException if an I/O error occurs.
*/
public void write(final int b) throws IOException {
if (this.count == this.buffer.length) {
// No need to check return value, can NEVER be 0.
this.increaseBuffer(1);
}
this.buffer[this.count++] = (byte)b;
this.totalCount++;
checkForFlush();
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this buffered output stream.
*
* <p> Ordinarily this method stores bytes from the given array into this
* stream's buffer, flushing the buffer to the underlying output stream as
* needed. If the requested length is at least as large as this stream's
* buffer, however, then this method will flush the buffer and write the
* bytes directly to the underlying output stream. Thus redundant
* <code>BufferedOutputStream</code>s will not copy data unnecessarily.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @exception IOException if an I/O error occurs.
*/
public void write(final byte[] b, final int off, final int len) throws IOException {
int free = this.buffer.length - this.count;
int necessaryIncrease = len - free;
if (necessaryIncrease > 0) {
int actualIncrease = this.increaseBuffer(necessaryIncrease);
if (actualIncrease < necessaryIncrease) {
free += actualIncrease;
// Needs to be written in chunks by recursive calls to this method.
writeToBuffer(b, off, free);
int newOff = off + free;
int newLen = len - free;
while (newLen > 0) {
writeToBuffer(b, newOff, Math.min(newLen, this.flushBufferSize));
newOff += this.flushBufferSize;
newLen -= this.flushBufferSize;
}
return;
}
}
writeToBuffer(b, off, len);
}
private void writeToBuffer(final byte[] b, final int off, final int len) throws IOException {
System.arraycopy(b, off, this.buffer, this.count, len);
this.count += len;
this.totalCount += len;
checkForFlush();
}
private void checkForFlush() throws IOException {
if (this.count == this.flushBufferSize) {
flush();
}
}
/**
* Flushes this buffered output stream.
*
* @exception IOException if an I/O error occurs.
*/
public void flush() throws IOException {
if (this.count > 0) {
this.out.write(this.buffer, 0, this.count);
this.count = 0;
}
this.out.flush();
}
/**
* Closes this buffered output stream.
* Flush before closing.
*
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
flush();
super.close();
}
/**
* Increase the buffer by at least as many bytes as specified via the
* parameter, but not exceeding the flushBufferSize. The actual increase is
* returned.
*
* @return increase in buffer size.
*/
private int increaseBuffer(final int increase) {
int oldLength = this.buffer.length;
if (oldLength == this.flushBufferSize) {
// fast way out
return 0;
}
int newLength = oldLength;
int actualIncrease;
do {
newLength = newLength * 2;
if (this.flushBufferSize > 0 && newLength >= this.flushBufferSize) {
newLength = this.flushBufferSize;
actualIncrease = newLength - oldLength;
break;
}
actualIncrease = newLength - oldLength;
} while (actualIncrease < increase);
// Because of the "fast way out" above at this point there should always be an increase.
byte[] newBuffer = new byte[newLength];
if (this.count > 0) {
System.arraycopy(this.buffer, 0, newBuffer, 0, this.count);
}
this.buffer = newBuffer;
return actualIncrease;
}
/**
* Clear the buffer.
* @deprecated Public access is deprecated. Use {@link #reset()} instead.
*/
public void clearBuffer() {
this.totalCount -= this.count;
this.count = 0;
}
/**
* Reset the BufferedOutputStream to the last {@link #flush()}.
*/
public void reset() {
clearBuffer();
}
/**
* @return if it is possible to reset the buffer completely, i.e. nothing has been flushed yet.
*/
public boolean isResettable() {
return this.count == this.totalCount;
}
/**
* Return the size of the current buffer
*/
public int getCount() {
return this.totalCount;
}
}