001    /*
002     * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
003     *
004     * http://uio.imagero.com
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *
009     *  o Redistributions of source code must retain the above copyright notice,
010     *    this list of conditions and the following disclaimer.
011     *
012     *  o Redistributions in binary form must reproduce the above copyright notice,
013     *    this list of conditions and the following disclaimer in the documentation
014     *    and/or other materials provided with the distribution.
015     *
016     *  o Neither the name of Andrey Kuznetsov nor the names of
017     *    its contributors may be used to endorse or promote products derived
018     *    from this software without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
023     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
027     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
028     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
029     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
030     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031     */
032    package com.imagero.uio.bio;
033    
034    import com.imagero.uio.ISeekable;
035    import com.imagero.uio.RandomAccessIO;
036    import com.imagero.uio.RandomAccessInput;
037    import com.imagero.uio.RandomAccessOutput;
038    import com.imagero.uio.io.UnexpectedEOFException;
039    import com.imagero.uio.impl.AbstractRandomAccessIO;
040    
041    import java.io.DataOutput;
042    import java.io.EOFException;
043    import java.io.IOException;
044    import java.io.OutputStream;
045    import java.io.InputStream;
046    
047    /**
048     * BufferedRandomAccessIO - buffered readable and writable stream with random access.
049     * @author Andrey Kuznetsov
050     */
051    public class BufferedRandomAccessIO extends AbstractRandomAccessIO {
052    
053        FixedSizeByteBuffer buffer;
054        BufferIndex bufferIndex;
055        BufferPosition bufferPosition;
056        IOController controller;
057    
058        StreamPosition streamPosition = new StreamPosition();
059        long offset;
060    
061    
062        public BufferedRandomAccessIO(IOController controller) {
063            this(controller, 0L);
064        }
065    
066        public BufferedRandomAccessIO(IOController controller, long offset) {
067            this.controller = controller;
068            this.offset = offset;
069            bufferPosition = new BufferPosition(controller.bufferSize);
070            seek(0);
071        }
072    
073        public final void setLength(long newLength) throws IOException {
074            controller.setLength(newLength);
075        }
076    
077        public long flushBefore(long pos) {
078            return controller.flushBefore(pos);
079        }
080    
081        protected void prepareBufferForReading(BufferIndex index) throws IOException {
082            if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
083                bufferIndex = index;
084                buffer = controller.getBuffer(streamPosition.pos, true);
085            }
086            bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
087        }
088    
089        protected void prepareBufferForWriting(BufferIndex index) throws IOException {
090            if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
091                bufferIndex = index;
092                buffer = controller.getBuffer(streamPosition.pos, false);
093            }
094            bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
095            buffer.changed = true;
096        }
097    
098        public void write(short[] data, int offset, int length, int byteOrder) throws IOException {
099            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
100    
101            while (length > 0) {
102                ensureBuffer(false);
103                if (buffer.availableForWriting(bufferPosition) < 2) {
104                    writeShort(data[offset++]);
105                    length--;
106                }
107                if (length > 0) {
108                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
109                    length -= copied;
110                    offset += copied;
111                    streamPosition.pos += copied << 1;
112                }
113            }
114        }
115    
116        public void write(char[] data, int offset, int length, int byteOrder) throws IOException {
117            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
118    
119            while (length > 0) {
120                ensureBuffer(false);
121                if (buffer.availableForWriting(bufferPosition) < 2) {
122                    writeShort(data[offset++]);
123                    length--;
124                }
125                if (length > 0) {
126                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
127                    length -= copied;
128                    offset += copied;
129                    streamPosition.pos += copied << 1;
130                }
131            }
132        }
133    
134        public void write(int[] data, int offset, int length, int byteOrder) throws IOException {
135            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
136    
137            while (length > 0) {
138                ensureBuffer(false);
139                if (buffer.availableForWriting(bufferPosition) < 4) {
140                    writeInt(data[offset++]);
141                    length--;
142                }
143                if (length > 0) {
144                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
145                    length -= copied;
146                    offset += copied;
147                    streamPosition.pos += copied << 2;
148                }
149            }
150        }
151    
152        public void write(float[] data, int offset, int length, int byteOrder) throws IOException {
153            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
154    
155            while (length > 0) {
156                ensureBuffer(false);
157                if (buffer.availableForWriting(bufferPosition) < 4) {
158                    writeFloat(data[offset++]);
159                    length--;
160                }
161                if (length > 0) {
162                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
163                    length -= copied;
164                    offset += copied;
165                    streamPosition.pos += copied << 2;
166                }
167            }
168        }
169    
170        public void write(long[] data, int offset, int length, int byteOrder) throws IOException {
171            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
172    
173            while (length > 0) {
174                ensureBuffer(false);
175                if (buffer.availableForWriting(bufferPosition) < 8) {
176                    writeLong(data[offset++]);
177                    length--;
178                }
179                if (length > 0) {
180                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
181                    length -= copied;
182                    offset += copied;
183                    streamPosition.pos += copied << 3;
184                }
185            }
186        }
187    
188        public void write(double[] data, int offset, int length, int byteOrder) throws IOException {
189            final boolean bigEndian = byteOrder == ISeekable.BIG_ENDIAN;
190    
191            while (length > 0) {
192                ensureBuffer(false);
193                if (buffer.availableForWriting(bufferPosition) < 8) {
194                    writeDouble(data[offset++]);
195                    length--;
196                }
197                if (length > 0) {
198                    int copied = buffer.write(data, offset, length, bigEndian, bufferPosition);
199                    length -= copied;
200                    offset += copied;
201                    streamPosition.pos += copied << 3;
202                }
203            }
204        }
205    
206        public long getFilePointer() {
207            return streamPosition.pos - offset;
208        }
209    
210        public long length() throws IOException {
211            return controller.length();
212        }
213    
214        public void seek(long pos) {
215            streamPosition.pos = pos + offset;
216            bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
217        }
218    
219        public int available() throws IOException {
220            if (buffer != null) {
221                return buffer.availableForReading(bufferPosition);
222            }
223            return 0;
224        }
225    
226        public int getByteOrder() {
227            return byteOrder;
228        }
229    
230        public void setByteOrder(int byteOrder) {
231            switch (byteOrder) {
232                case ISeekable.BIG_ENDIAN:
233                case ISeekable.LITTLE_ENDIAN:
234                    this.byteOrder = byteOrder;
235                    break;
236                default:
237                    throw new IllegalArgumentException("" + Integer.toHexString(byteOrder));
238            }
239        }
240    
241        public void write(int b) throws IOException {
242            ensureBuffer(false);
243            buffer.write(b, bufferPosition);
244            streamPosition.pos++;
245        }
246    
247        public void write(byte b[], int offset, int length) throws IOException {
248            while (length > 0) {
249                ensureBuffer(false);
250                int written = buffer.write(b, offset, length, bufferPosition);
251                length -= written;
252                offset += written;
253                streamPosition.pos += written;
254            }
255        }
256    
257        public void close() throws IOException {
258            if (controller != null) {
259                controller.sync();
260                controller = null;
261            }
262        }
263    
264        /**
265         * write buffer contents to given OutputStream
266         * @param out OutputStream
267         */
268        public void writeBuffer(OutputStream out) throws IOException {
269            controller.writeTo(out);
270        }
271    
272        /**
273         * write buffer contents to DataOutput
274         * @param out OutputStream
275         */
276        public void writeBuffer(DataOutput out) throws IOException {
277            controller.writeTo(out);
278        }
279    
280        public int read(short[] dest, int offset, int length, int byteOrder) throws IOException {
281            ensureBuffer(true);
282            if (buffer == null) {
283                return 0;
284            }
285            int read = 0;
286    
287            int available = buffer.availableForReading(bufferPosition);
288            if (available < 2) {
289                dest[offset++] = readShort(byteOrder);
290                length--;
291                read++;
292            }
293            if (length > 0) {
294                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
295                length -= copied;
296                offset += copied;
297                streamPosition.pos += copied << 1;
298                read += copied;
299            }
300            return read;
301        }
302    
303        private void ensureBuffer(boolean read) throws IOException {
304            BufferIndex index = controller.getBufferIndex(streamPosition.pos);
305            if (read) {
306                if (buffer == null || buffer.availableForReading(bufferPosition) <= 0 || bufferIndex != index) {
307                    prepareBufferForReading(index);
308                }
309            } else {
310                if (buffer == null || buffer.availableForWriting(bufferPosition) <= 0 || bufferIndex != index) {
311                    prepareBufferForWriting(index);
312                }
313            }
314        }
315    
316        public int read(char[] dest, int offset, int length, int byteOrder) throws IOException {
317            ensureBuffer(true);
318            if (buffer == null) {
319                return 0;
320            }
321    
322            int read = 0;
323    
324            int available = buffer.availableForReading(bufferPosition);
325            if (available < 2) {
326                dest[offset++] = readChar(byteOrder);
327                length--;
328                read++;
329            }
330            if (length > 0) {
331                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
332                length -= copied;
333                offset += copied;
334                streamPosition.pos += copied << 1;
335                read += copied;
336            }
337            return read;
338        }
339    
340        public int read(int[] dest, int offset, int length, int byteOrder) throws IOException {
341            ensureBuffer(true);
342            if (buffer == null) {
343                return 0;
344            }
345    
346            int read = 0;
347    
348            int available = buffer.availableForReading(bufferPosition);
349            if (available < 4) {
350                dest[offset++] = readInt(byteOrder);
351                length--;
352                read++;
353            }
354            if (length > 0) {
355                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
356                length -= copied;
357                offset += copied;
358                streamPosition.pos += copied << 2;
359                read += copied;
360            }
361            return read;
362        }
363    
364        public int read(long[] dest, int offset, int length, int byteOrder) throws IOException {
365            ensureBuffer(true);
366            if (buffer == null) {
367                return 0;
368            }
369    
370            int read = 0;
371    
372            int available = buffer.availableForReading(bufferPosition);
373            if (available < 8) {
374                dest[offset++] = readLong(byteOrder);
375                length--;
376                read++;
377            }
378            if (length > 0) {
379                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
380                length -= copied;
381                offset += copied;
382                streamPosition.pos += copied << 3;
383                read += copied;
384            }
385            return read;
386        }
387    
388        public int read(float[] dest, int offset, int length, int byteOrder) throws IOException {
389            ensureBuffer(true);
390            if (buffer == null) {
391                return 0;
392            }
393    
394            int read = 0;
395    
396            int available = buffer.availableForReading(bufferPosition);
397            if (available < 4) {
398                dest[offset++] = readFloat(byteOrder);
399                length--;
400                read++;
401            }
402            if (length > 0) {
403                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
404                length -= copied;
405                offset += copied;
406                streamPosition.pos += copied << 2;
407                read += copied;
408            }
409            return read;
410        }
411    
412        public int read(double[] dest, int offset, int length, int byteOrder) throws IOException {
413            ensureBuffer(true);
414            if (buffer == null) {
415                return 0;
416            }
417    
418            int read = 0;
419    
420            int available = buffer.availableForReading(bufferPosition);
421            if (available < 8) {
422                dest[offset++] = readDouble(byteOrder);
423                length--;
424                read++;
425            }
426            if (length > 0) {
427                int copied = buffer.read(dest, offset, length, byteOrder == BIG_ENDIAN, bufferPosition);
428                length -= copied;
429                offset += copied;
430                streamPosition.pos += copied << 3;
431                read += copied;
432            }
433            return read;
434        }
435    
436        private void assertBufferIsNotNull(int count) throws EOFException {
437            if (buffer == null) {
438                throw new UnexpectedEOFException(count);
439            }
440        }
441    
442        public int read() throws IOException {
443            try {
444                ensureBuffer(true);
445            } catch (IOException ex) {
446                return -1;
447            }
448            if (buffer != null) {
449                streamPosition.pos++;
450                return buffer.read(bufferPosition);
451            }
452            return -1;
453        }
454    
455        public long skip(long n) throws IOException {
456            ensureBuffer(true);
457            if (buffer == null) {
458                return 0;
459            }
460            long skipped = buffer.skip(n, bufferPosition);
461            streamPosition.pos += skipped;
462            return skipped;
463        }
464    
465        public int read(byte[] b, int offset, int length) throws IOException {
466            ensureBuffer(true);
467            if (buffer == null) {
468                return 0;
469            }
470            int rc = buffer.read(b, offset, length, bufferPosition);
471            if (rc > 0) {
472                streamPosition.pos += rc;
473            }
474            return rc;
475        }
476    
477        public void readFully(byte b[], int offset, int length) throws IOException {
478    //        super.readFully(b, offset, length);
479            int sum = 0;
480            while (length > 0) {
481                ensureBuffer(true);
482                assertBufferIsNotNull(sum);
483                int copied = buffer.read(b, offset, length, bufferPosition);
484                if(copied <= 0) {
485                    throw new UnexpectedEOFException(sum);
486                }
487                sum += copied;
488                length -= copied;
489                offset += copied;
490                streamPosition.pos += copied;
491            }
492        }
493    
494        public RandomAccessIO createIOChild(long offset, int byteOrder, boolean syncPointer) {
495            BufferedRandomAccessIO io = new BufferedRandomAccessIO(controller, offset);
496            io.setByteOrder(byteOrder);
497            if (syncPointer) {
498                io.streamPosition = streamPosition;
499            }
500            return io;
501        }
502    
503        public RandomAccessInput createInputChild(long offset, int byteOrder, boolean syncPointer) {
504            return createIOChild(offset, byteOrder, syncPointer);
505        }
506    
507        public InputStream createInputStream(long offset) {
508            return new IOCInputStream(controller, offset);
509        }
510    
511        public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) {
512            return createIOChild(offset, byteOrder, syncPointer);
513        }
514    
515        public OutputStream createOutputStream(long offset) {
516            return new IOCOutputStream(controller, offset);
517        }
518    
519        public void flush() throws IOException {
520            controller.sync();
521        }
522    
523        public boolean isBuffered() {
524            return true;
525        }
526    }