001/*
002 * $RCSfile: FileChannelImageOutputStream.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.1 $
042 * $Date: 2005/02/11 05:01:20 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.stream;
046
047import java.io.IOException;
048import java.nio.ByteBuffer;
049import java.nio.ByteOrder;
050import java.nio.CharBuffer;
051import java.nio.DoubleBuffer;
052import java.nio.FloatBuffer;
053import java.nio.IntBuffer;
054import java.nio.LongBuffer;
055import java.nio.ShortBuffer;
056import java.nio.channels.FileChannel;
057
058import javax.imageio.stream.ImageInputStream;
059import javax.imageio.stream.ImageOutputStreamImpl;
060
061/**
062 * A class which implements <code>ImageOutputStream</code> using a
063 * <code>FileChannel</code> as the eventual data destination.
064 *
065 * <p>Memory mapping is used for reading and direct buffers for writing.
066 * Only methods which provide significant performance improvement with
067 * respect to the superclass implementation are overridden.  Overridden
068 * methods are not commented individually unless some noteworthy aspect
069 * of the implementation must be described.</p>
070 *
071 * <p>The methods of this class are <b>not</b> synchronized.</p>
072 *
073 * @see javax.imageio.stream.ImageOutputStream
074 * @see java.nio
075 * @see java.nio.channels.FileChannel
076 */
077public class FileChannelImageOutputStream extends ImageOutputStreamImpl {
078
079    /** The size of the write buffer. */
080    // XXX Should this be a different value? Smaller?
081    // Should there be an instance variable with public
082    // accessor/mutator methods?
083    private static final int DEFAULT_WRITE_BUFFER_SIZE = 1048576;
084
085    /** The <code>FileChannel</code> data destination. */
086    private FileChannel channel;
087
088    /** A <code>ByteBuffer</code> used for writing to the channel. */
089    private ByteBuffer byteBuffer;
090
091    /** An <code>ImageInputStream</code> used for reading. */
092    private ImageInputStream readStream = null;
093
094    /**
095     * Test method.
096     *
097     * @param args Command line arguments (ignored).
098     * @throws Throwable for any Exception or Error.
099     */
100    /* XXX
101    public static void main(String[] args) throws Throwable {
102        // Create files.
103        java.io.RandomAccessFile fileFIOS =
104            new java.io.RandomAccessFile("fios.tmp", "rw");
105        java.io.RandomAccessFile fileFCIOS =
106            new java.io.RandomAccessFile("fcios.tmp", "rw");
107
108        // Create streams.
109        javax.imageio.stream.ImageOutputStream fios =
110            new javax.imageio.stream.FileImageOutputStream(fileFIOS);
111        javax.imageio.stream.ImageOutputStream fcios =
112            new FileChannelImageOutputStream(fileFCIOS.getChannel());
113
114        int datumSize = 4;
115
116        // Write some ints (257, 258)
117        fios.writeInts(new int[] {(int)256, (int)257,
118                                    (int)258, (int)59},
119                        1, 2);
120        fcios.writeInts(new int[] {(int)256, (int)257,
121                                     (int)258, (int)259},
122                         1, 2);
123
124        // Write a byte.
125        fios.write(127);
126        fcios.write(127);
127
128        // Write some more ints (262, 263, 264).
129        fios.writeInts(new int[] {(int)260, (int)261, (int)262,
130                                    (int)263, (int)264, (int)265}, 2, 3);
131        fcios.writeInts(new int[] {(int)260, (int)261, (int)262,
132                                     (int)263, (int)264, (int)265}, 2, 3);
133
134        // Seek to byte location.
135        fios.seek(2*datumSize);
136        fcios.seek(2*datumSize);
137
138        // Read and print the byte.
139        System.out.println(fios.read());
140        System.out.println(fcios.read());
141
142        // Seek to beginning.
143        fios.seek(0);
144        fcios.seek(0);
145
146        int[] fiosInts = new int[10];
147        int[] fciosInts = new int[10];
148
149        // Read the ints.
150        fios.readFully(fiosInts, 1, 2);
151        fcios.readFully(fciosInts, 1, 2);
152
153        // Print the ints
154        for(int i = 0; i < 2; i++) {
155            System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]);
156        }
157
158        // Seek to second set of ints
159        fios.seek(2*datumSize+1);
160        fcios.seek(2*datumSize+1);
161
162        // Read the ints.
163        fios.readFully(fiosInts, 1, 3);
164        fcios.readFully(fciosInts, 1, 3);
165
166        // Print the ints
167        for(int i = 0; i < 3; i++) {
168            System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]);
169        }
170    }
171    */
172
173    /**
174     * Constructs a <code>FileChannelImageOutputStream</code> from a
175     * <code>FileChannel</code>.  The initial position of the stream
176     * stream is taken to be the position of the <code>FileChannel</code>
177     * parameter when this constructor is invoked.  The stream and flushed
178     * positions are therefore both initialized to
179     * <code>channel.position()</code>.
180     *
181     * @param channel the destination <code>FileChannel</code>.
182     *
183     * @throws IllegalArgumentException if <code>channel</code> is
184     *         <code>null</code> or is not open.
185     * @throws IOException if a method invoked on <code>channel</code>
186     *         throws an <code>IOException</code>.
187     */
188    public FileChannelImageOutputStream(FileChannel channel)
189        throws IOException {
190
191        // Check the parameter.
192        if(channel == null) {
193            throw new IllegalArgumentException("channel == null");
194        } else if(!channel.isOpen()) {
195            throw new IllegalArgumentException("channel.isOpen() == false");
196        }
197
198        // Save the channel reference.
199        this.channel = channel;
200
201        // Set stream and flushed positions to initial channel position.
202        this.streamPos = this.flushedPos = channel.position();
203
204        // Allocate the write buffer.
205        byteBuffer = ByteBuffer.allocateDirect(DEFAULT_WRITE_BUFFER_SIZE);
206
207        // Create the read stream (initially zero-sized).
208        readStream = new FileChannelImageInputStream(channel);
209    }
210
211    /**
212     * Returns an <code>ImageInputStream</code> for reading.  The
213     * returned stream has byte order, stream and flushed positions,
214     * and bit offset set to the current values for this stream.
215     */
216    private ImageInputStream getImageInputStream() throws IOException {
217        // Write any unwritten bytes.
218        flushBuffer();
219
220        // Sync input stream state to state of this stream.
221        readStream.setByteOrder(byteOrder);
222        readStream.seek(streamPos);
223        readStream.flushBefore(flushedPos);
224        readStream.setBitOffset(bitOffset);
225
226        return readStream;
227    }
228
229    /**
230     * Write to the <code>FileChannel</code> any remaining bytes in
231     * the byte output buffer.
232     */
233    private void flushBuffer() throws IOException {
234        if(byteBuffer.position() != 0) {
235            // Set the limit to the position.
236            byteBuffer.limit(byteBuffer.position());
237
238            // Set the position to zero.
239            byteBuffer.position(0);
240
241            // Write all bytes between zero and the previous position.
242            channel.write(byteBuffer);
243
244            // Prepare for subsequent put() calls if any.
245            byteBuffer.clear();
246        }
247    }
248
249    // --- Implementation of superclass abstract methods. ---
250
251    public int read() throws IOException {
252        checkClosed();
253        bitOffset = 0;
254
255        ImageInputStream inputStream = getImageInputStream();
256
257        streamPos++;
258
259        return inputStream.read();
260    }
261
262    public int read(byte[] b, int off, int len) throws IOException {
263        // Check parameters.
264        if(off < 0 || len < 0 || off + len > b.length) {
265            throw new IndexOutOfBoundsException
266                ("off < 0 || len < 0 || off + len > b.length");
267        } else if(len == 0) {
268            return 0;
269        }
270
271        checkClosed();
272        bitOffset = 0;
273
274        ImageInputStream inputStream = getImageInputStream();
275
276        int numBytesRead = inputStream.read(b, off, len);
277
278        streamPos += numBytesRead;
279
280        return numBytesRead;
281    }
282
283    public void write(int b) throws IOException {
284        write(new byte[] {(byte)(b&0xff)}, 0, 1);
285    }
286
287    public void write(byte[] b, int off, int len) throws IOException {
288
289        // Check parameters.
290        if(off < 0 || len < 0 || off + len > b.length) {
291            // NullPointerException will be thrown before this if b is null.
292            throw new IndexOutOfBoundsException
293                ("off < 0 || len < 0 || off + len > b.length");
294        } else if(len == 0) {
295            return;
296        }
297
298        // Flush any bits in the current byte.
299        flushBits();
300
301        // Zero the number of bytes to put.
302        int numPut = 0;
303
304        // Loop until all bytes have been put.
305        do {
306            // Determine number of bytes to put.
307            int numToPut = Math.min(len - numPut,
308                                    byteBuffer.remaining());
309
310            // If no bytes to put, the buffer has to be full as len
311            // is always greater than numPut so flush it and return
312            // to start of loop.
313            if(numToPut == 0) {
314                flushBuffer();
315                continue;
316            }
317
318            // Put the bytes in the buffer.
319            byteBuffer.put(b, off + numPut, numToPut);
320
321            // Increment the put counter.
322            numPut += numToPut;
323        } while(numPut < len);
324
325        // Increment the stream position.
326        streamPos += len;
327    }
328
329    // --- Overriding of superclass methods. ---
330
331    // --- Bulk read methods ---
332
333    public void readFully(char[] c, int off, int len) throws IOException {
334        getImageInputStream().readFully(c, off, len);
335        streamPos += 2*len;
336    }
337
338    public void readFully(short[] s, int off, int len) throws IOException {
339        getImageInputStream().readFully(s, off, len);
340        streamPos += 2*len;
341    }
342
343    public void readFully(int[] i, int off, int len) throws IOException {
344        getImageInputStream().readFully(i, off, len);
345        streamPos += 4*len;
346    }
347
348    public void readFully(long[] l, int off, int len) throws IOException {
349        getImageInputStream().readFully(l, off, len);
350        streamPos += 8*len;
351    }
352
353    public void readFully(float[] f, int off, int len) throws IOException {
354        getImageInputStream().readFully(f, off, len);
355        streamPos += 4*len;
356    }
357
358    public void readFully(double[] d, int off, int len) throws IOException {
359        getImageInputStream().readFully(d, off, len);
360        streamPos += 8*len;
361    }
362
363    // --- Bulk write methods ---
364
365    public void writeChars(char[] c, int off, int len) throws IOException {
366
367        // Check parameters.
368        if(off < 0 || len < 0 || off + len > c.length) {
369            // NullPointerException will be thrown before this if c is null.
370            throw new IndexOutOfBoundsException
371                ("off < 0 || len < 0 || off + len > c.length");
372        } else if(len == 0) {
373            return;
374        }
375
376        // Flush any bits in the current byte.
377        flushBits();
378
379        // Zero the number of chars put.
380        int numPut = 0;
381
382        // Get view buffer.
383        CharBuffer viewBuffer = byteBuffer.asCharBuffer();
384
385        // Loop until all chars have been put.
386        do {
387            // Determine number of chars to put.
388            int numToPut = Math.min(len - numPut,
389                                    viewBuffer.remaining());
390
391            // If no chars to put, the buffer has to be full as len
392            // is always greater than numPut so flush it and return
393            // to start of loop.
394            if(numToPut == 0) {
395                flushBuffer();
396                continue;
397            }
398
399            // Put the chars in the buffer.
400            viewBuffer.put(c, off + numPut, numToPut);
401
402            // Sync the ByteBuffer position.
403            byteBuffer.position(byteBuffer.position() + 2*numToPut);
404
405            // Increment the put counter.
406            numPut += numToPut;
407        } while(numPut < len);
408
409        // Increment the stream position.
410        streamPos += 2*len;
411    }
412
413    public void writeShorts(short[] s, int off, int len) throws IOException {
414
415        // Check parameters.
416        if(off < 0 || len < 0 || off + len > s.length) {
417            // NullPointerException will be thrown before this if s is null.
418            throw new IndexOutOfBoundsException
419                ("off < 0 || len < 0 || off + len > c.length");
420        } else if(len == 0) {
421            return;
422        }
423
424        // Flush any bits in the current byte.
425        flushBits();
426
427        // Zero the number of shorts put.
428        int numPut = 0;
429
430        // Get view buffer.
431        ShortBuffer viewBuffer = byteBuffer.asShortBuffer();
432
433        // Loop until all shorts have been put.
434        do {
435            // Determine number of shorts to put.
436            int numToPut = Math.min(len - numPut,
437                                    viewBuffer.remaining());
438
439            // If no shorts to put, the buffer has to be full as len
440            // is always greater than numPut so flush it and return
441            // to start of loop.
442            if(numToPut == 0) {
443                flushBuffer();
444                continue;
445            }
446
447            // Put the shorts in the buffer.
448            viewBuffer.put(s, off + numPut, numToPut);
449
450            // Sync the ByteBuffer position.
451            byteBuffer.position(byteBuffer.position() + 2*numToPut);
452
453            // Increment the put counter.
454            numPut += numToPut;
455        } while(numPut < len);
456
457        // Increment the stream position.
458        streamPos += 2*len;
459    }
460
461    public void writeInts(int[] i, int off, int len) throws IOException {
462
463        // Check parameters.
464        if(off < 0 || len < 0 || off + len > i.length) {
465            // NullPointerException will be thrown before this if i is null.
466            throw new IndexOutOfBoundsException
467                ("off < 0 || len < 0 || off + len > c.length");
468        } else if(len == 0) {
469            return;
470        }
471
472        // Flush any bits in the current byte.
473        flushBits();
474
475        // Zero the number of ints put.
476        int numPut = 0;
477
478        // Get view buffer.
479        IntBuffer viewBuffer = byteBuffer.asIntBuffer();
480
481        // Loop until all ints have been put.
482        do {
483            // Determine number of ints to put.
484            int numToPut = Math.min(len - numPut,
485                                    viewBuffer.remaining());
486
487            // If no ints to put, the buffer has to be full as len
488            // is always greater than numPut so flush it and return
489            // to start of loop.
490            if(numToPut == 0) {
491                flushBuffer();
492                continue;
493            }
494
495            // Put the ints in the buffer.
496            viewBuffer.put(i, off + numPut, numToPut);
497
498            // Sync the ByteBuffer position.
499            byteBuffer.position(byteBuffer.position() + 4*numToPut);
500
501            // Increment the put counter.
502            numPut += numToPut;
503        } while(numPut < len);
504
505        // Increment the stream position.
506        streamPos += 4*len;
507    }
508
509    public void writeLongs(long[] l, int off, int len) throws IOException {
510
511        // Check parameters.
512        if(off < 0 || len < 0 || off + len > l.length) {
513            // NullPointerException will be thrown before this if l is null.
514            throw new IndexOutOfBoundsException
515                ("off < 0 || len < 0 || off + len > c.length");
516        } else if(len == 0) {
517            return;
518        }
519
520        // Flush any bits in the current byte.
521        flushBits();
522
523        // Zero the number of longs put.
524        int numPut = 0;
525
526        // Get view buffer.
527        LongBuffer viewBuffer = byteBuffer.asLongBuffer();
528
529        // Loop until all longs have been put.
530        do {
531            // Determine number of longs to put.
532            int numToPut = Math.min(len - numPut,
533                                    viewBuffer.remaining());
534
535            // If no longs to put, the buffer has to be full as len
536            // is always greater than numPut so flush it and return
537            // to start of loop.
538            if(numToPut == 0) {
539                flushBuffer();
540                continue;
541            }
542
543            // Put the longs in the buffer.
544            viewBuffer.put(l, off + numPut, numToPut);
545
546            // Sync the ByteBuffer position.
547            byteBuffer.position(byteBuffer.position() + 8*numToPut);
548
549            // Increment the put counter.
550            numPut += numToPut;
551        } while(numPut < len);
552
553        // Increment the stream position.
554        streamPos += 8*len;
555    }
556
557    public void writeFloats(float[] f, int off, int len) throws IOException {
558
559        // Check parameters.
560        if(off < 0 || len < 0 || off + len > f.length) {
561            // NullPointerException will be thrown before this if c is null.
562            throw new IndexOutOfBoundsException
563                ("off < 0 || len < 0 || off + len > f.length");
564        } else if(len == 0) {
565            return;
566        }
567
568        // Flush any bits in the current byte.
569        flushBits();
570
571        // Zero the number of floats put.
572        int numPut = 0;
573
574        // Get view buffer.
575        FloatBuffer viewBuffer = byteBuffer.asFloatBuffer();
576
577        // Loop until all floats have been put.
578        do {
579            // Determine number of floats to put.
580            int numToPut = Math.min(len - numPut,
581                                    viewBuffer.remaining());
582
583            // If no floats to put, the buffer has to be full as len
584            // is always greater than numPut so flush it and return
585            // to start of loop.
586            if(numToPut == 0) {
587                flushBuffer();
588                continue;
589            }
590
591            // Put the floats in the buffer.
592            viewBuffer.put(f, off + numPut, numToPut);
593
594            // Sync the ByteBuffer position.
595            byteBuffer.position(byteBuffer.position() + 4*numToPut);
596
597            // Increment the put counter.
598            numPut += numToPut;
599        } while(numPut < len);
600
601        // Increment the stream position.
602        streamPos += 4*len;
603    }
604
605    public void writeDoubles(double[] d, int off, int len) throws IOException {
606
607        // Check parameters.
608        if(off < 0 || len < 0 || off + len > d.length) {
609            // NullPointerException will be thrown before this if d is null.
610            throw new IndexOutOfBoundsException
611                ("off < 0 || len < 0 || off + len > d.length");
612        } else if(len == 0) {
613            return;
614        }
615
616        // Flush any bits in the current byte.
617        flushBits();
618
619        // Zero the number of doubles put.
620        int numPut = 0;
621
622        // Get view buffer.
623        DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer();
624
625        // Loop until all doubles have been put.
626        do {
627            // Determine number of doubles to put.
628            int numToPut = Math.min(len - numPut,
629                                    viewBuffer.remaining());
630
631            // If no doubles to put, the buffer has to be full as len
632            // is always greater than numPut so flush it and return
633            // to start of loop.
634            if(numToPut == 0) {
635                flushBuffer();
636                continue;
637            }
638
639            // Put the doubles in the buffer.
640            viewBuffer.put(d, off + numPut, numToPut);
641
642            // Sync the ByteBuffer position.
643            byteBuffer.position(byteBuffer.position() + 8*numToPut);
644
645            // Increment the put counter.
646            numPut += numToPut;
647        } while(numPut < len);
648
649        // Increment the stream position.
650        streamPos += 8*len;
651    }
652
653    // --- Other methods ---
654
655    /**
656     * Invokes the superclass method, writes any unwritten data, and
657     * sets the internal reference to the source <code>FileChannel</code>
658     * to <code>null</code>.  The source <code>FileChannel</code> is not
659     * closed.
660     *
661     * @exception IOException if an error occurs.
662     */
663    // Note that this method is called by the superclass finalize()
664    // so this class does not need to implement finalize().
665    public void close() throws IOException {
666        // Flush any unwritten data in the buffer.
667        flushBuffer();
668
669        // Close the read channel and clear the reference to it.
670        readStream.close();
671        readStream = null;
672
673        // Clear reference to the channel.
674        channel = null;
675
676        // Clear reference to the internal ByteBuffer.
677        byteBuffer = null;
678
679        // Chain to the superclass.
680        super.close();
681    }
682
683    /**
684     * Returns the number of bytes currently in the <code>FileChannel</code>.
685     * If an <code>IOException</code> is encountered when querying the
686     * channel's size, -1L will be returned.
687     *
688     * @return The number of bytes in the channel
689     * -1L to indicate unknown length.
690     */
691    public long length() {
692        // Initialize to value indicating unknown length.
693        long length = -1L;
694
695        // Set length to current size with respect to initial position.
696        try {
697            length = channel.size();
698        } catch(IOException e) {
699            // Default to unknown length.
700        }
701
702        return length;
703    }
704
705    /**
706     * Invokes the superclass method, writes any unwritten data,
707     * and sets the channel position to the supplied parameter.
708     */
709    public void seek(long pos) throws IOException {
710        super.seek(pos);
711
712        // Flush any unwritten data in the buffer.
713        flushBuffer();
714
715        // Set the FileChannel position for WritableByteChannel.write().
716        channel.position(pos);
717    }
718
719    public void setByteOrder(ByteOrder networkByteOrder) {
720        super.setByteOrder(networkByteOrder);
721        byteBuffer.order(networkByteOrder);
722    }
723}