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