001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.io;
020
021 import java.io.IOException;
022
023 /**
024 * BufferedRandomOutputStream is a buffered output stream for {@link RandomAccessOutputStream} which, unlike a regular
025 * <code>java.io.BufferedOutputStream</code>, makes it safe to seek in the underlying <code>RandomAccessOutputStream</code>.
026 *
027 * <p>This class uses {@link BufferPool} to create the internal buffer, to avoid excessive memory allocation and
028 * garbage collection. The buffer is released when this stream is closed.</p>
029 *
030 * @author Maxence Bernard
031 */
032 public class BufferedRandomOutputStream extends RandomAccessOutputStream {
033
034 /** The underlying random access output stream */
035 private RandomAccessOutputStream raos;
036
037 /** The buffer where written bytes are accumulated before being sent to the underlying output stream */
038 private byte buffer[];
039
040 /** The current number of bytes waiting to be flushed to the underlying output stream */
041 private int count;
042
043 /** The default buffer size if none is specified */
044 public final static int DEFAULT_BUFFER_SIZE = 65536;
045
046
047 /**
048 * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.
049 * An internal buffer of {@link #DEFAULT_BUFFER_SIZE} bytes is created.
050 *
051 * @param raos the underlying RandomAccessOutputStream used by this buffered output stream
052 */
053 public BufferedRandomOutputStream(RandomAccessOutputStream raos) {
054 this(raos, DEFAULT_BUFFER_SIZE);
055 }
056
057 /**
058 * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.
059 * An internal buffer of the specified size is created.
060 *
061 * @param raos the underlying RandomAccessOutputStream used by this buffered output stream
062 * @param size size of the buffer in bytes
063 */
064 public BufferedRandomOutputStream(RandomAccessOutputStream raos, int size) {
065 this.raos = raos;
066 this.buffer = BufferPool.getByteArray(size);
067 }
068
069 /**
070 * Flushes the internal buffer.
071 *
072 * @throws IOException if an error occurs
073 */
074 private void flushBuffer() throws IOException {
075 if (count > 0) {
076 raos.write(buffer, 0, count);
077 count = 0;
078 }
079 }
080
081
082 /////////////////////////////////////////////
083 // RandomAccessOutputStream implementation //
084 /////////////////////////////////////////////
085
086 /**
087 * Writes the specified byte to this buffered output stream.
088 *
089 * @param b the byte to be written
090 * @throws IOException if an I/O error occurs
091 */
092 public synchronized void write(int b) throws IOException {
093 if (count >= buffer.length)
094 flushBuffer();
095
096 buffer[count++] = (byte)b;
097 }
098
099 /**
100 * Writes the specified byte array to this buffered output stream.
101 *
102 * @param b the bytes to be written
103 * @throws IOException if an I/O error occurs
104 */
105 public synchronized void write(byte b[]) throws IOException {
106 write(b, 0, b.length);
107 }
108
109 /**
110 * Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this
111 * buffered output stream.
112 *
113 * <p>Usually this method stores bytes from the given array into this
114 * stream's buffer, flushing the buffer to the underlying output stream as
115 * needed. However, if the requested data length is equal or larger than this stream's
116 * buffer, then this method will flush the buffer and write the
117 * bytes directly to the underlying output stream. Thus redundant
118 * <code>RandomBufferedOutputStream</code>s will not copy data unnecessarily.</p>
119 *
120 * @param b the data.
121 * @param off the start offset in the data.
122 * @param len the number of bytes to write.
123 * @throws IOException if an I/O error occurs.
124 */
125 public synchronized void write(byte b[], int off, int len) throws IOException {
126 if (len >= buffer.length) {
127 /* If the request length exceeds the size of the output buffer,
128 flush the output buffer and then write the data directly.
129 In this way buffered streams will cascade harmlessly. */
130 flushBuffer();
131 raos.write(b, off, len);
132 return;
133 }
134
135 if (len > buffer.length - count)
136 flushBuffer();
137
138 System.arraycopy(b, off, buffer, count, len);
139 count += len;
140 }
141
142 /**
143 * Flushes this buffered output stream. This forces any buffered
144 * output bytes to be written out to the underlying output stream.
145 *
146 * @throws IOException if an I/O error occurs.
147 */
148 public synchronized void flush() throws IOException {
149 flushBuffer();
150 raos.flush();
151 }
152
153 public synchronized long getOffset() throws IOException {
154 // Add the buffered byte count
155 return raos.getOffset() + count;
156 }
157
158 public synchronized void seek(long offset) throws IOException {
159 // Flush any buffered bytes before seeking, otherwise buffered bytes would be written at the wrong offset
160 flush();
161
162 raos.seek(offset);
163 }
164
165 public synchronized long getLength() throws IOException {
166 // Anticipate if the file is to be expanded by the bytes awaiting in the buffer
167 return Math.max(raos.getLength(), getOffset());
168 }
169
170 public synchronized void setLength(long newLength) throws IOException {
171 // Flush before changing the file's length, otherwise the behavior of setLength() would be modified, especially
172 // when truncating the file
173 flush();
174
175 raos.setLength(newLength);
176 }
177
178
179 ////////////////////////
180 // Overridden methods //
181 ////////////////////////
182
183 /**
184 * This method is overridden to release the internal buffer when this stream is closed.
185 */
186 public synchronized void close() throws IOException {
187 if(buffer!=null) { // buffer is null if close() was already called
188 try {
189 flush();
190 }
191 catch(IOException e) {
192 // Continue anyway
193 }
194
195 // Release the buffer
196 BufferPool.releaseByteArray(buffer);
197 buffer = null;
198 }
199
200 raos.close();
201 }
202
203 /**
204 * This method is overridden to release the internal buffer if {@link #close()} has not been called, to avoid any
205 * memory leak.
206 */
207 protected void finalize() throws Throwable {
208 // If this stream hasn't been closed, release the buffer before finalizing the object
209 if(buffer!=null)
210 BufferPool.releaseByteArray(buffer);
211
212 super.finalize();
213 }
214 }