| /* |
| * 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; |
| } |
| |
| } |