001/*
002 * $RCSfile: FileChannelImageInputStream.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.2 $
042 * $Date: 2007/09/14 22:57:55 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.stream;
046
047import java.io.EOFException;
048import java.io.IOException;
049import java.nio.ByteBuffer;
050import java.nio.ByteOrder;
051import java.nio.CharBuffer;
052import java.nio.DoubleBuffer;
053import java.nio.FloatBuffer;
054import java.nio.IntBuffer;
055import java.nio.LongBuffer;
056import java.nio.MappedByteBuffer;
057import java.nio.ShortBuffer;
058import java.nio.channels.FileChannel;
059
060import javax.imageio.stream.ImageInputStreamImpl;
061
062/**
063 * A class which implements <code>ImageInputStream</code> using a
064 * <code>FileChannel</code> as the eventual data source. The channel
065 * contents are assumed to be stable during the lifetime of the object.
066 *
067 * <p>Memory mapping and new I/O view <code>Buffer</code>s are used to
068 * read the data. Only methods which provide significant performance
069 * improvement with respect to the superclass implementation are overridden.
070 * Overridden methods are not commented individually unless some noteworthy
071 * aspect of the implementation must be described.</p>
072 *
073 * <p>The methods of this class are <b>not</b> synchronized.</p>
074 *
075 * @see javax.imageio.stream.ImageInputStream
076 * @see java.nio
077 * @see java.nio.channels.FileChannel
078 */
079public class FileChannelImageInputStream extends ImageInputStreamImpl {
080
081    /** The <code>FileChannel</code> data source. */
082    private FileChannel channel;
083
084    /** A memory mapping of all or part of the channel. */
085    private MappedByteBuffer mappedBuffer;
086
087    /** The stream position of the mapping. */
088    private long mappedPos;
089
090    /** The stream position least upper bound of the mapping. */
091    private long mappedUpperBound;
092
093    /**
094     * Constructs a <code>FileChannelImageInputStream</code> from a
095     * <code>FileChannel</code>.  The initial position of the stream
096     * stream is taken to be the position of the <code>FileChannel</code>
097     * parameter when this constructor is invoked.  The stream and flushed
098     * positions are therefore both initialized to
099     * <code>channel.position()</code>.
100     *
101     * @param channel the source <code>FileChannel</code>.
102     *
103     * @throws IllegalArgumentException if <code>channel</code> is
104     *         <code>null</code> or is not open.
105     * @throws IOException if a method invoked on <code>channel</code>
106     *         throws an <code>IOException</code>.
107     */
108    public FileChannelImageInputStream(FileChannel channel)
109        throws IOException {
110
111        // Check the parameter.
112        if(channel == null) {
113            throw new IllegalArgumentException("channel == null");
114        } else if(!channel.isOpen()) {
115            throw new IllegalArgumentException("channel.isOpen() == false");
116        }
117
118        // Save the channel reference.
119        this.channel = channel;
120
121        // Get the channel position.
122        long channelPosition = channel.position();
123
124        // Set stream and flushed positions to initial channel position.
125        this.streamPos = this.flushedPos = channelPosition;
126
127        // Determine the size of the mapping.
128        long fullSize = channel.size() - channelPosition;
129        long mappedSize = Math.min(fullSize, Integer.MAX_VALUE);
130
131        // Set the mapped position and upper bound.
132        this.mappedPos = 0;
133        this.mappedUpperBound = mappedPos + mappedSize;
134
135        // Map the file.
136        this.mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
137                                        channelPosition,
138                                        mappedSize);
139    }
140
141    /**
142     * Returns a <code>MappedByteBuffer</code> which memory maps
143     * at least from the channel position corresponding to the
144     * current stream position to <code>len</code> bytes beyond.
145     * A new buffer is created only if necessary.
146     *
147     * @param len The number of bytes required beyond the current stream
148     * position.
149     */
150    private MappedByteBuffer getMappedBuffer(int len) throws IOException {
151        // If request is outside mapped region, map a new region.
152        if(streamPos < mappedPos || streamPos + len >= mappedUpperBound) {
153
154            // Set the map position.
155            mappedPos = streamPos;
156
157            // Determine the map size.
158            long mappedSize = Math.min(channel.size() - mappedPos,
159                                       Integer.MAX_VALUE);
160
161            // Set the mapped upper bound.
162            mappedUpperBound = mappedPos + mappedSize;
163
164            // Map the file.
165            mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
166                                       mappedPos,
167                                       mappedSize);
168            
169            mappedBuffer.order(super.getByteOrder());
170
171        }
172        
173        return mappedBuffer;
174    }
175
176    // --- Implementation of superclass abstract methods. ---
177
178    public int read() throws IOException {
179        checkClosed();
180        bitOffset = 0;
181
182        // Get the mapped buffer.
183        ByteBuffer byteBuffer = getMappedBuffer(1);
184
185        // Check number of bytes left.
186        if(byteBuffer.remaining() < 1) {
187            // Return EOF.
188            return -1;
189        }
190
191        // Get the byte from the buffer.
192        int value = byteBuffer.get()&0xff;
193
194        // Increment the stream position.
195        streamPos++;
196
197        //System.out.println("value = "+value);
198
199        return value;
200    }
201
202    public int read(byte[] b, int off, int len) throws IOException {
203        if(off < 0 || len < 0 || off + len > b.length) {
204            // NullPointerException will be thrown before this if b is null.
205            throw new IndexOutOfBoundsException
206                ("off < 0 || len < 0 || off + len > b.length");
207        } else if(len == 0) {
208            return 0;
209        }
210
211        checkClosed();
212        bitOffset = 0;
213
214        // Get the mapped buffer.
215        ByteBuffer byteBuffer = getMappedBuffer(len);
216
217        // Get the number of bytes remaining.
218        int numBytesRemaining = byteBuffer.remaining();
219
220        // Check number of bytes left.
221        if(numBytesRemaining < 1) {
222            // Return EOF.
223            return -1;
224        } else if(len > numBytesRemaining) {
225            // Clamp 'len' to number of bytes in Buffer. Apparently some
226            // readers (JPEG) request data beyond the end of the file.
227            len = numBytesRemaining;
228        }
229
230        // Read the data from the buffer.
231        byteBuffer.get(b, off, len);
232
233        // Increment the stream position.
234        streamPos += len;
235
236        return len;
237    }
238
239    // --- Overriding of superclass methods. ---
240
241    /**
242     * Invokes the superclass method and sets the internal reference
243     * to the source <code>FileChannel</code> to <code>null</code>.
244     * The source <code>FileChannel</code> is not closed.
245     *
246     * @exception IOException if an error occurs.
247     */
248    public void close() throws IOException {
249        super.close();
250        channel = null;
251    }
252
253    public void readFully(char[] c, int off, int len) throws IOException {
254        if(off < 0 || len < 0 || off + len > c.length) {
255            // NullPointerException will be thrown before this if c is null.
256            throw new IndexOutOfBoundsException
257                ("off < 0 || len < 0 || off + len > c.length");
258        } else if(len == 0) {
259            return;
260        }
261
262        // Determine the requested length in bytes.
263        int byteLen = 2*len;
264
265        // Get the mapped buffer.
266        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
267
268        // Ensure enough bytes remain.
269        if(byteBuffer.remaining() < byteLen) {
270            throw new EOFException();
271        }
272
273        // Get the view Buffer.
274        CharBuffer viewBuffer = byteBuffer.asCharBuffer();
275
276        // Get the chars.
277        viewBuffer.get(c, off, len);
278
279        // Update the position.
280        seek(streamPos + byteLen);
281    }
282
283    public void readFully(short[] s, int off, int len) throws IOException {
284        if(off < 0 || len < 0 || off + len > s.length) {
285            // NullPointerException will be thrown before this if s is null.
286            throw new IndexOutOfBoundsException
287                ("off < 0 || len < 0 || off + len > s.length");
288        } else if(len == 0) {
289            return;
290        }
291
292        // Determine the requested length in bytes.
293        int byteLen = 2*len;
294
295        // Get the mapped buffer.
296        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
297
298        // Ensure enough bytes remain.
299        if(byteBuffer.remaining() < byteLen) {
300            throw new EOFException();
301        }
302
303        // Get the view Buffer.
304        ShortBuffer viewBuffer = byteBuffer.asShortBuffer();
305
306        // Get the shorts.
307        viewBuffer.get(s, off, len);
308
309        // Update the position.
310        seek(streamPos + byteLen);
311    }
312
313    public void readFully(int[] i, int off, int len) throws IOException {
314        if(off < 0 || len < 0 || off + len > i.length) {
315            // NullPointerException will be thrown before this if i is null.
316            throw new IndexOutOfBoundsException
317                ("off < 0 || len < 0 || off + len > i.length");
318        } else if(len == 0) {
319            return;
320        }
321
322        // Determine the requested length in bytes.
323        int byteLen = 4*len;
324
325        // Get the mapped buffer.
326        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
327
328        // Ensure enough bytes remain.
329        if(byteBuffer.remaining() < byteLen) {
330            throw new EOFException();
331        }
332
333        // Get the view Buffer.
334        IntBuffer viewBuffer = byteBuffer.asIntBuffer();
335
336        // Get the ints.
337        viewBuffer.get(i, off, len);
338
339        // Update the position.
340        seek(streamPos + byteLen);
341    }
342
343    public void readFully(long[] l, int off, int len) throws IOException {
344        if(off < 0 || len < 0 || off + len > l.length) {
345            // NullPointerException will be thrown before this if l is null.
346            throw new IndexOutOfBoundsException
347                ("off < 0 || len < 0 || off + len > l.length");
348        } else if(len == 0) {
349            return;
350        }
351
352        // Determine the requested length in bytes.
353        int byteLen = 8*len;
354
355        // Get the mapped buffer.
356        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
357
358        // Ensure enough bytes remain.
359        if(byteBuffer.remaining() < byteLen) {
360            throw new EOFException();
361        }
362
363        // Get the view Buffer.
364        LongBuffer viewBuffer = byteBuffer.asLongBuffer();
365
366        // Get the longs.
367        viewBuffer.get(l, off, len);
368
369        // Update the position.
370        seek(streamPos + byteLen);
371    }
372
373    public void readFully(float[] f, int off, int len) throws IOException {
374        if(off < 0 || len < 0 || off + len > f.length) {
375            // NullPointerException will be thrown before this if f is null.
376            throw new IndexOutOfBoundsException
377                ("off < 0 || len < 0 || off + len > f.length");
378        } else if(len == 0) {
379            return;
380        }
381
382        // Determine the requested length in bytes.
383        int byteLen = 4*len;
384
385        // Get the mapped buffer.
386        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
387
388        // Ensure enough bytes remain.
389        if(byteBuffer.remaining() < byteLen) {
390            throw new EOFException();
391        }
392
393        // Get the view Buffer.
394        FloatBuffer viewBuffer = byteBuffer.asFloatBuffer();
395
396        // Get the floats.
397        viewBuffer.get(f, off, len);
398
399        // Update the position.
400        seek(streamPos + byteLen);
401    }
402
403    public void readFully(double[] d, int off, int len) throws IOException {
404        if(off < 0 || len < 0 || off + len > d.length) {
405            // NullPointerException will be thrown before this if d is null.
406            throw new IndexOutOfBoundsException
407                ("off < 0 || len < 0 || off + len > d.length");
408        } else if(len == 0) {
409            return;
410        }
411
412        // Determine the requested length in bytes.
413        int byteLen = 8*len;
414
415        // Get the mapped buffer.
416        ByteBuffer byteBuffer = getMappedBuffer(byteLen);
417
418        // Ensure enough bytes remain.
419        if(byteBuffer.remaining() < byteLen) {
420            throw new EOFException();
421        }
422
423        // Get the view Buffer.
424        DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer();
425
426        // Get the doubles.
427        viewBuffer.get(d, off, len);
428
429        // Update the position.
430        seek(streamPos + byteLen);
431    }
432
433    /**
434     * Returns the number of bytes currently in the <code>FileChannel</code>.
435     * If an <code>IOException</code> is encountered when querying the
436     * channel's size, -1L will be returned.
437     *
438     * @return The number of bytes in the channel
439     * -1L to indicate unknown length.
440     */
441    public long length() {
442        // Initialize to value indicating unknown length.
443        long length = -1L;
444
445        // Set length to current size with respect to initial position.
446        try {
447            length = channel.size();
448        } catch(IOException e) {
449            // Default to unknown length.
450        }
451
452        return length;
453    }
454
455    /**
456     * Invokes the superclass method and sets the position within the
457     * memory mapped buffer.  A new region is mapped if necessary.  The
458     * position of the source <code>FileChannel</code> is not changed, i.e.,
459     * {@link java.nio.channels.FileChannel#position(long)} is not invoked.
460     */
461    public void seek(long pos) throws IOException {
462        super.seek(pos);
463
464        if(pos >= mappedPos && pos < mappedUpperBound) {
465            // Seeking to location within mapped buffer: set buffer position.
466            mappedBuffer.position((int)(pos - mappedPos));
467        } else {
468            // Seeking to location outside mapped buffer: get a new mapped
469            // buffer at current position with maximal size.
470            int len = (int)Math.min(channel.size() - pos, Integer.MAX_VALUE);
471            mappedBuffer = getMappedBuffer(len);
472        }
473    }
474
475    public void setByteOrder(ByteOrder networkByteOrder) {
476        super.setByteOrder(networkByteOrder);
477        mappedBuffer.order(networkByteOrder);
478    }
479}