/*
* $RCSfile: FileChannelImageInputStream.java,v $
*
*
* Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for
* use in the design, construction, operation or maintenance of any
* nuclear facility.
*
* $Revision: 1.2 $
* $Date: 2007/09/14 22:57:55 $
* $State: Exp $
*/
package com.sun.media.imageio.stream;
import java.io.File;
import java.io.EOFException;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import javax.imageio.stream.ImageInputStreamImpl;
/**
* A class which implements <code>ImageInputStream</code> using a
* <code>FileChannel</code> as the eventual data source. The channel
* contents are assumed to be stable during the lifetime of the object.
*
* <p>Memory mapping and new I/O view <code>Buffer</code>s are used to
* read the data. Only methods which provide significant performance
* improvement with respect to the superclass implementation are overridden.
* Overridden methods are not commented individually unless some noteworthy
* aspect of the implementation must be described.</p>
*
* <p>The methods of this class are <b>not</b> synchronized.</p>
*
* @see javax.imageio.stream.ImageInputStream
* @see java.nio
* @see java.nio.channels.FileChannel
*/
public class FileChannelImageInputStream extends ImageInputStreamImpl {
/** The <code>FileChannel</code> data source. */
private FileChannel channel;
/** A memory mapping of all or part of the channel. */
private MappedByteBuffer mappedBuffer;
/** The stream position of the mapping. */
private long mappedPos;
/** The stream position least upper bound of the mapping. */
private long mappedUpperBound;
/**
* Constructs a <code>FileChannelImageInputStream</code> from a
* <code>FileChannel</code>. The initial position of the stream
* stream is taken to be the position of the <code>FileChannel</code>
* parameter when this constructor is invoked. The stream and flushed
* positions are therefore both initialized to
* <code>channel.position()</code>.
*
* @param channel the source <code>FileChannel</code>.
*
* @throws IllegalArgumentException if <code>channel</code> is
* <code>null</code> or is not open.
* @throws IOException if a method invoked on <code>channel</code>
* throws an <code>IOException</code>.
*/
public FileChannelImageInputStream(FileChannel channel)
throws IOException {
// Check the parameter.
if(channel == null) {
throw new IllegalArgumentException("channel == null");
} else if(!channel.isOpen()) {
throw new IllegalArgumentException("channel.isOpen() == false");
}
// Save the channel reference.
this.channel = channel;
// Get the channel position.
long channelPosition = channel.position();
// Set stream and flushed positions to initial channel position.
this.streamPos = this.flushedPos = channelPosition;
// Determine the size of the mapping.
long fullSize = channel.size() - channelPosition;
long mappedSize = Math.min(fullSize, Integer.MAX_VALUE);
// Set the mapped position and upper bound.
this.mappedPos = 0;
this.mappedUpperBound = mappedPos + mappedSize;
// Map the file.
this.mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
channelPosition,
mappedSize);
}
/**
* Returns a <code>MappedByteBuffer</code> which memory maps
* at least from the channel position corresponding to the
* current stream position to <code>len</code> bytes beyond.
* A new buffer is created only if necessary.
*
* @param len The number of bytes required beyond the current stream
* position.
*/
private MappedByteBuffer getMappedBuffer(int len) throws IOException {
// If request is outside mapped region, map a new region.
if(streamPos < mappedPos || streamPos + len >= mappedUpperBound) {
// Set the map position.
mappedPos = streamPos;
// Determine the map size.
long mappedSize = Math.min(channel.size() - mappedPos,
Integer.MAX_VALUE);
// Set the mapped upper bound.
mappedUpperBound = mappedPos + mappedSize;
// Map the file.
mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
mappedPos,
mappedSize);
mappedBuffer.order(super.getByteOrder());
}
return mappedBuffer;
}
// --- Implementation of superclass abstract methods. ---
public int read() throws IOException {
checkClosed();
bitOffset = 0;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(1);
// Check number of bytes left.
if(byteBuffer.remaining() < 1) {
// Return EOF.
return -1;
}
// Get the byte from the buffer.
int value = byteBuffer.get()&0xff;
// Increment the stream position.
streamPos++;
//System.out.println("value = "+value);
return value;
}
public int read(byte[] b, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > b.length) {
// NullPointerException will be thrown before this if b is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > b.length");
} else if(len == 0) {
return 0;
}
checkClosed();
bitOffset = 0;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(len);
// Get the number of bytes remaining.
int numBytesRemaining = byteBuffer.remaining();
// Check number of bytes left.
if(numBytesRemaining < 1) {
// Return EOF.
return -1;
} else if(len > numBytesRemaining) {
// Clamp 'len' to number of bytes in Buffer. Apparently some
// readers (JPEG) request data beyond the end of the file.
len = numBytesRemaining;
}
// Read the data from the buffer.
byteBuffer.get(b, off, len);
// Increment the stream position.
streamPos += len;
return len;
}
// --- Overriding of superclass methods. ---
/**
* Invokes the superclass method and sets the internal reference
* to the source <code>FileChannel</code> to <code>null</code>.
* The source <code>FileChannel</code> is not closed.
*
* @exception IOException if an error occurs.
*/
public void close() throws IOException {
super.close();
channel = null;
}
public void readFully(char[] c, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > c.length) {
// NullPointerException will be thrown before this if c is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > c.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 2*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
CharBuffer viewBuffer = byteBuffer.asCharBuffer();
// Get the chars.
viewBuffer.get(c, off, len);
// Update the position.
seek(streamPos + byteLen);
}
public void readFully(short[] s, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > s.length) {
// NullPointerException will be thrown before this if s is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > s.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 2*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
ShortBuffer viewBuffer = byteBuffer.asShortBuffer();
// Get the shorts.
viewBuffer.get(s, off, len);
// Update the position.
seek(streamPos + byteLen);
}
public void readFully(int[] i, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > i.length) {
// NullPointerException will be thrown before this if i is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > i.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 4*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
IntBuffer viewBuffer = byteBuffer.asIntBuffer();
// Get the ints.
viewBuffer.get(i, off, len);
// Update the position.
seek(streamPos + byteLen);
}
public void readFully(long[] l, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > l.length) {
// NullPointerException will be thrown before this if l is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > l.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 8*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
LongBuffer viewBuffer = byteBuffer.asLongBuffer();
// Get the longs.
viewBuffer.get(l, off, len);
// Update the position.
seek(streamPos + byteLen);
}
public void readFully(float[] f, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > f.length) {
// NullPointerException will be thrown before this if f is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > f.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 4*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
FloatBuffer viewBuffer = byteBuffer.asFloatBuffer();
// Get the floats.
viewBuffer.get(f, off, len);
// Update the position.
seek(streamPos + byteLen);
}
public void readFully(double[] d, int off, int len) throws IOException {
if(off < 0 || len < 0 || off + len > d.length) {
// NullPointerException will be thrown before this if d is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > d.length");
} else if(len == 0) {
return;
}
// Determine the requested length in bytes.
int byteLen = 8*len;
// Get the mapped buffer.
ByteBuffer byteBuffer = getMappedBuffer(byteLen);
// Ensure enough bytes remain.
if(byteBuffer.remaining() < byteLen) {
throw new EOFException();
}
// Get the view Buffer.
DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer();
// Get the doubles.
viewBuffer.get(d, off, len);
// Update the position.
seek(streamPos + byteLen);
}
/**
* Returns the number of bytes currently in the <code>FileChannel</code>.
* If an <code>IOException</code> is encountered when querying the
* channel's size, -1L will be returned.
*
* @return The number of bytes in the channel
* -1L to indicate unknown length.
*/
public long length() {
// Initialize to value indicating unknown length.
long length = -1L;
// Set length to current size with respect to initial position.
try {
length = channel.size();
} catch(IOException e) {
// Default to unknown length.
}
return length;
}
/**
* Invokes the superclass method and sets the position within the
* memory mapped buffer. A new region is mapped if necessary. The
* position of the source <code>FileChannel</code> is not changed, i.e.,
* {@link java.nio.channels.FileChannel#position(long)} is not invoked.
*/
public void seek(long pos) throws IOException {
super.seek(pos);
if(pos >= mappedPos && pos < mappedUpperBound) {
// Seeking to location within mapped buffer: set buffer position.
mappedBuffer.position((int)(pos - mappedPos));
} else {
// Seeking to location outside mapped buffer: get a new mapped
// buffer at current position with maximal size.
int len = (int)Math.min(channel.size() - pos, Integer.MAX_VALUE);
mappedBuffer = getMappedBuffer(len);
}
}
public void setByteOrder(ByteOrder networkByteOrder) {
super.setByteOrder(networkByteOrder);
mappedBuffer.order(networkByteOrder);
}
}