001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.tools.randomaccess;
031:
032: import java.io.IOException;
033:
034: /**
035: * todo 1 length reply may be invalid
036: */
037: public class BufferedRandomAccess extends AbstractRandomAccess {
038: private static int DEFAULT_BUFFER_SIZE = 4096;
039:
040: /**
041: * The buffer for the delegates data
042: */
043: private byte[] bytes;
044:
045: /**
046: * The relative index of the buffer's first byte in the delegates data
047: */
048: private long bytesOffset = 0;
049:
050: /**
051: * The number of valid bytes in the buffer
052: */
053: private int count = 0;
054:
055: /**
056: * The index into the buffer indicating the current byte
057: */
058: private int localOffset = 0;
059:
060: /**
061: * The buffered random access delegate
062: */
063: private IRandomAccess randomAccess;
064:
065: /**
066: * The total offset in the delegates data.
067: * <p>
068: * totalOffset = bufferOffset + localOffset; or count == 0 (invalid/empty
069: * buffer);
070: *
071: */
072: private long totalOffset = 0;
073:
074: private boolean closed = false;
075:
076: /**
077: * The length of the random access data
078: */
079: private long length;
080:
081: /**
082: * Flag if data in the buffer is currently changed.
083: */
084: private boolean bufferChanged = false;
085:
086: public BufferedRandomAccess(IRandomAccess randomAccess)
087: throws IOException {
088: this (randomAccess, DEFAULT_BUFFER_SIZE);
089: }
090:
091: public BufferedRandomAccess(IRandomAccess randomAccess,
092: int bufferSize) throws IOException {
093: this .randomAccess = randomAccess;
094: this .bytes = new byte[bufferSize];
095: this .length = randomAccess.getLength();
096: }
097:
098: protected int basicRead(byte[] buffer, int start, int numBytes)
099: throws IOException {
100: if (localOffset >= count) {
101: if (numBytes >= bytes.length) {
102: flushBuffer();
103: randomAccess.seek(totalOffset);
104: int readBytes = randomAccess.read(buffer, start,
105: numBytes);
106: if (readBytes > 0) {
107: totalOffset += readBytes;
108: localOffset = count;
109: }
110: return readBytes;
111: } else {
112: fillBuffer();
113: if (localOffset >= count) {
114: return -1;
115: }
116: }
117: }
118: int avail = count - localOffset;
119: int cnt = (avail < numBytes) ? avail : numBytes;
120: System.arraycopy(bytes, localOffset, buffer, start, cnt);
121: localOffset += cnt;
122: totalOffset += cnt;
123: return cnt;
124: }
125:
126: /*
127: * (non-Javadoc)
128: *
129: * @see de.intarsys.tools.randomaccess.IRandomAccessData#close()
130: */
131: public void close() throws IOException {
132: if (isClosed()) {
133: return;
134: }
135: flushBuffer();
136: randomAccess.close();
137: setClosed(true);
138: }
139:
140: /*
141: * (non-Javadoc)
142: *
143: * @see de.intarsys.tools.randomaccess.IRandomAccess#flush()
144: */
145: public void flush() throws IOException {
146: if (isClosed()) {
147: throw new IOException("random access closed");
148: }
149: flushBuffer();
150: randomAccess.flush();
151: }
152:
153: protected void fillBuffer() throws IOException {
154: flushBuffer();
155: randomAccess.seek(totalOffset);
156: count = randomAccess.read(bytes, 0, bytes.length);
157: }
158:
159: protected void flushBuffer() throws IOException {
160: if (bufferChanged && (count > 0)) {
161: randomAccess.seek(bytesOffset);
162: randomAccess.write(bytes, 0, count);
163: }
164: bytesOffset = totalOffset;
165: localOffset = 0;
166: count = 0;
167: bufferChanged = false;
168: }
169:
170: /*
171: * (non-Javadoc)
172: *
173: * @see de.intarsys.tools.randomaccess.IRandomAccessData#getLength()
174: */
175: public long getLength() throws IOException {
176: if (isClosed()) {
177: throw new IOException("random access closed");
178: }
179: return length;
180: }
181:
182: /*
183: * (non-Javadoc)
184: *
185: * @see de.intarsys.tools.randomaccess.IRandomAccessData#getOffset()
186: */
187: public long getOffset() throws IOException {
188: if (isClosed()) {
189: throw new IOException("random access closed");
190: }
191: return totalOffset;
192: }
193:
194: /*
195: * (non-Javadoc)
196: *
197: * @see de.intarsys.tools.randomaccess.IRandomAccessData#isReadOnly()
198: */
199: public boolean isReadOnly() {
200: return randomAccess.isReadOnly();
201: }
202:
203: /*
204: * (non-Javadoc)
205: *
206: * @see de.intarsys.tools.randomaccess.IRandomAccessData#read()
207: */
208: public int read() throws IOException {
209: if (isClosed()) {
210: throw new IOException("random access closed");
211: }
212: if (localOffset >= count) {
213: fillBuffer();
214: if (localOffset >= count) {
215: return -1;
216: }
217: }
218: totalOffset++;
219: return bytes[localOffset++] & 0xff;
220: }
221:
222: /*
223: * (non-Javadoc)
224: *
225: * @see de.intarsys.tools.randomaccess.IRandomAccessData#read(byte[])
226: */
227: public int read(byte[] buffer) throws IOException {
228: if (isClosed()) {
229: throw new IOException("random access closed");
230: }
231: return read(buffer, 0, buffer.length);
232: }
233:
234: /*
235: * (non-Javadoc)
236: *
237: * @see de.intarsys.tools.randomaccess.IRandomAccessData#read(byte[], int,
238: * int)
239: */
240: public int read(byte[] buffer, int start, int numBytes)
241: throws IOException {
242: if (isClosed()) {
243: throw new IOException("random access closed");
244: }
245: if (numBytes == 0) {
246: return 0;
247: }
248: int totalByteCount = 0;
249: while (totalByteCount < numBytes) {
250: int byteCount = basicRead(buffer, start + totalByteCount,
251: numBytes - totalByteCount);
252: if (byteCount <= 0) {
253: break;
254: }
255: totalByteCount += byteCount;
256: }
257: if (totalByteCount == 0) {
258: return -1;
259: }
260: return totalByteCount;
261: }
262:
263: /*
264: * (non-Javadoc)
265: *
266: * @see de.intarsys.tools.randomaccess.IRandomAccessData#seek(long)
267: */
268: public void seek(long offset) throws IOException {
269: if (isClosed()) {
270: throw new IOException("random access closed");
271: }
272: totalOffset = offset;
273: long newLocalOffset = totalOffset - bytesOffset;
274: if ((newLocalOffset < 0) || (newLocalOffset >= count)) {
275: flushBuffer();
276: } else {
277: localOffset = (int) newLocalOffset;
278: }
279: }
280:
281: public void seekBy(long delta) throws IOException {
282: if (isClosed()) {
283: throw new IOException("random access closed");
284: }
285: totalOffset += delta;
286: long newLocalOffset = localOffset + delta;
287: if ((newLocalOffset < 0) || (newLocalOffset >= count)) {
288: flushBuffer();
289: } else {
290: localOffset = (int) newLocalOffset;
291: }
292: }
293:
294: /*
295: * (non-Javadoc)
296: *
297: * @see de.intarsys.tools.randomaccess.IRandomAccessData#setLength(int)
298: */
299: public void setLength(long newLength) throws IOException {
300: if (isClosed()) {
301: throw new IOException("random access closed");
302: }
303: if (newLength < (bytesOffset + bytes.length)) {
304: flushBuffer();
305: }
306: if (newLength < totalOffset) {
307: totalOffset = newLength;
308: bytesOffset = newLength;
309: }
310: length = newLength;
311: randomAccess.setLength(newLength);
312: }
313:
314: /*
315: * (non-Javadoc)
316: *
317: * @see de.intarsys.tools.randomaccess.IRandomAccessData#write(byte[])
318: */
319: public void write(byte[] buffer) throws IOException {
320: if (isClosed()) {
321: throw new IOException("random access closed");
322: }
323: write(buffer, 0, buffer.length);
324: }
325:
326: /*
327: * (non-Javadoc)
328: *
329: * @see de.intarsys.tools.randomaccess.IRandomAccessData#write(byte[], int,
330: * int)
331: */
332: public void write(byte[] buffer, int start, int numBytes)
333: throws IOException {
334: if (isClosed()) {
335: throw new IOException("random access closed");
336: }
337: if (numBytes >= bytes.length) {
338: // greater than local buffer size -> write directly
339: flushBuffer();
340: randomAccess.seek(totalOffset);
341: randomAccess.write(buffer, start, numBytes);
342: totalOffset += numBytes;
343: bytesOffset = totalOffset;
344: if (totalOffset > length) {
345: length = totalOffset;
346: }
347: return;
348: }
349: if (numBytes > (bytes.length - localOffset)) {
350: // greater than rest of buffer -> flush and fill new one
351: flushBuffer();
352: }
353: System.arraycopy(buffer, start, bytes, localOffset, numBytes);
354: bufferChanged = true;
355: totalOffset += numBytes;
356: localOffset += numBytes;
357: if (totalOffset > length) {
358: length = totalOffset;
359: }
360: if (localOffset >= count) {
361: count = localOffset;
362: }
363: }
364:
365: /*
366: * (non-Javadoc)
367: *
368: * @see de.intarsys.tools.randomaccess.IRandomAccessData#write(int)
369: */
370: public void write(int b) throws IOException {
371: if (isClosed()) {
372: throw new IOException("random access closed");
373: }
374: if (localOffset >= bytes.length) {
375: flushBuffer();
376: }
377: bufferChanged = true;
378: if (localOffset == count) {
379: count++;
380: }
381: totalOffset++;
382: if (totalOffset > length) {
383: length = totalOffset;
384: }
385: bytes[localOffset++] = (byte) b;
386: }
387:
388: protected boolean isClosed() {
389: return closed;
390: }
391:
392: protected void setClosed(boolean closed) {
393: this.closed = closed;
394: }
395: }
|