/*
* $RCSfile: FileChannelImageOutputStream.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.1 $
* $Date: 2005/02/11 05:01:20 $
* $State: Exp $
*/
package com.sun.media.imageio.stream;
import java.io.File;
import java.io.IOException;
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.ShortBuffer;
import java.nio.channels.FileChannel;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStreamImpl;
/**
* A class which implements <code>ImageOutputStream</code> using a
* <code>FileChannel</code> as the eventual data destination.
*
* <p>Memory mapping is used for reading and direct buffers for writing.
* 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.ImageOutputStream
* @see java.nio
* @see java.nio.channels.FileChannel
*/
public class FileChannelImageOutputStream extends ImageOutputStreamImpl {
/** The size of the write buffer. */
// XXX Should this be a different value? Smaller?
// Should there be an instance variable with public
// accessor/mutator methods?
private static final int DEFAULT_WRITE_BUFFER_SIZE = 1048576;
/** The <code>FileChannel</code> data destination. */
private FileChannel channel;
/** A <code>ByteBuffer</code> used for writing to the channel. */
private ByteBuffer byteBuffer;
/** An <code>ImageInputStream</code> used for reading. */
private ImageInputStream readStream = null;
/**
* Test method.
*
* @param args Command line arguments (ignored).
* @throws Throwable for any Exception or Error.
*/
/* XXX
public static void main(String[] args) throws Throwable {
// Create files.
java.io.RandomAccessFile fileFIOS =
new java.io.RandomAccessFile("fios.tmp", "rw");
java.io.RandomAccessFile fileFCIOS =
new java.io.RandomAccessFile("fcios.tmp", "rw");
// Create streams.
javax.imageio.stream.ImageOutputStream fios =
new javax.imageio.stream.FileImageOutputStream(fileFIOS);
javax.imageio.stream.ImageOutputStream fcios =
new FileChannelImageOutputStream(fileFCIOS.getChannel());
int datumSize = 4;
// Write some ints (257, 258)
fios.writeInts(new int[] {(int)256, (int)257,
(int)258, (int)59},
1, 2);
fcios.writeInts(new int[] {(int)256, (int)257,
(int)258, (int)259},
1, 2);
// Write a byte.
fios.write(127);
fcios.write(127);
// Write some more ints (262, 263, 264).
fios.writeInts(new int[] {(int)260, (int)261, (int)262,
(int)263, (int)264, (int)265}, 2, 3);
fcios.writeInts(new int[] {(int)260, (int)261, (int)262,
(int)263, (int)264, (int)265}, 2, 3);
// Seek to byte location.
fios.seek(2*datumSize);
fcios.seek(2*datumSize);
// Read and print the byte.
System.out.println(fios.read());
System.out.println(fcios.read());
// Seek to beginning.
fios.seek(0);
fcios.seek(0);
int[] fiosInts = new int[10];
int[] fciosInts = new int[10];
// Read the ints.
fios.readFully(fiosInts, 1, 2);
fcios.readFully(fciosInts, 1, 2);
// Print the ints
for(int i = 0; i < 2; i++) {
System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]);
}
// Seek to second set of ints
fios.seek(2*datumSize+1);
fcios.seek(2*datumSize+1);
// Read the ints.
fios.readFully(fiosInts, 1, 3);
fcios.readFully(fciosInts, 1, 3);
// Print the ints
for(int i = 0; i < 3; i++) {
System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]);
}
}
*/
/**
* Constructs a <code>FileChannelImageOutputStream</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 destination <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 FileChannelImageOutputStream(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;
// Set stream and flushed positions to initial channel position.
this.streamPos = this.flushedPos = channel.position();
// Allocate the write buffer.
byteBuffer = ByteBuffer.allocateDirect(DEFAULT_WRITE_BUFFER_SIZE);
// Create the read stream (initially zero-sized).
readStream = new FileChannelImageInputStream(channel);
}
/**
* Returns an <code>ImageInputStream</code> for reading. The
* returned stream has byte order, stream and flushed positions,
* and bit offset set to the current values for this stream.
*/
private ImageInputStream getImageInputStream() throws IOException {
// Write any unwritten bytes.
flushBuffer();
// Sync input stream state to state of this stream.
readStream.setByteOrder(byteOrder);
readStream.seek(streamPos);
readStream.flushBefore(flushedPos);
readStream.setBitOffset(bitOffset);
return readStream;
}
/**
* Write to the <code>FileChannel</code> any remaining bytes in
* the byte output buffer.
*/
private void flushBuffer() throws IOException {
if(byteBuffer.position() != 0) {
// Set the limit to the position.
byteBuffer.limit(byteBuffer.position());
// Set the position to zero.
byteBuffer.position(0);
// Write all bytes between zero and the previous position.
channel.write(byteBuffer);
// Prepare for subsequent put() calls if any.
byteBuffer.clear();
}
}
// --- Implementation of superclass abstract methods. ---
public int read() throws IOException {
checkClosed();
bitOffset = 0;
ImageInputStream inputStream = getImageInputStream();
streamPos++;
return inputStream.read();
}
public int read(byte[] b, int off, int len) throws IOException {
// Check parameters.
if(off < 0 || len < 0 || off + len > b.length) {
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > b.length");
} else if(len == 0) {
return 0;
}
checkClosed();
bitOffset = 0;
ImageInputStream inputStream = getImageInputStream();
int numBytesRead = inputStream.read(b, off, len);
streamPos += numBytesRead;
return numBytesRead;
}
public void write(int b) throws IOException {
write(new byte[] {(byte)(b&0xff)}, 0, 1);
}
public void write(byte[] b, int off, int len) throws IOException {
// Check parameters.
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;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of bytes to put.
int numPut = 0;
// Loop until all bytes have been put.
do {
// Determine number of bytes to put.
int numToPut = Math.min(len - numPut,
byteBuffer.remaining());
// If no bytes to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the bytes in the buffer.
byteBuffer.put(b, off + numPut, numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += len;
}
// --- Overriding of superclass methods. ---
// --- Bulk read methods ---
public void readFully(char[] c, int off, int len) throws IOException {
getImageInputStream().readFully(c, off, len);
streamPos += 2*len;
}
public void readFully(short[] s, int off, int len) throws IOException {
getImageInputStream().readFully(s, off, len);
streamPos += 2*len;
}
public void readFully(int[] i, int off, int len) throws IOException {
getImageInputStream().readFully(i, off, len);
streamPos += 4*len;
}
public void readFully(long[] l, int off, int len) throws IOException {
getImageInputStream().readFully(l, off, len);
streamPos += 8*len;
}
public void readFully(float[] f, int off, int len) throws IOException {
getImageInputStream().readFully(f, off, len);
streamPos += 4*len;
}
public void readFully(double[] d, int off, int len) throws IOException {
getImageInputStream().readFully(d, off, len);
streamPos += 8*len;
}
// --- Bulk write methods ---
public void writeChars(char[] c, int off, int len) throws IOException {
// Check parameters.
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;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of chars put.
int numPut = 0;
// Get view buffer.
CharBuffer viewBuffer = byteBuffer.asCharBuffer();
// Loop until all chars have been put.
do {
// Determine number of chars to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no chars to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the chars in the buffer.
viewBuffer.put(c, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 2*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 2*len;
}
public void writeShorts(short[] s, int off, int len) throws IOException {
// Check parameters.
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 > c.length");
} else if(len == 0) {
return;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of shorts put.
int numPut = 0;
// Get view buffer.
ShortBuffer viewBuffer = byteBuffer.asShortBuffer();
// Loop until all shorts have been put.
do {
// Determine number of shorts to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no shorts to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the shorts in the buffer.
viewBuffer.put(s, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 2*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 2*len;
}
public void writeInts(int[] i, int off, int len) throws IOException {
// Check parameters.
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 > c.length");
} else if(len == 0) {
return;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of ints put.
int numPut = 0;
// Get view buffer.
IntBuffer viewBuffer = byteBuffer.asIntBuffer();
// Loop until all ints have been put.
do {
// Determine number of ints to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no ints to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the ints in the buffer.
viewBuffer.put(i, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 4*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 4*len;
}
public void writeLongs(long[] l, int off, int len) throws IOException {
// Check parameters.
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 > c.length");
} else if(len == 0) {
return;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of longs put.
int numPut = 0;
// Get view buffer.
LongBuffer viewBuffer = byteBuffer.asLongBuffer();
// Loop until all longs have been put.
do {
// Determine number of longs to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no longs to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the longs in the buffer.
viewBuffer.put(l, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 8*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 8*len;
}
public void writeFloats(float[] f, int off, int len) throws IOException {
// Check parameters.
if(off < 0 || len < 0 || off + len > f.length) {
// NullPointerException will be thrown before this if c is null.
throw new IndexOutOfBoundsException
("off < 0 || len < 0 || off + len > f.length");
} else if(len == 0) {
return;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of floats put.
int numPut = 0;
// Get view buffer.
FloatBuffer viewBuffer = byteBuffer.asFloatBuffer();
// Loop until all floats have been put.
do {
// Determine number of floats to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no floats to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the floats in the buffer.
viewBuffer.put(f, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 4*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 4*len;
}
public void writeDoubles(double[] d, int off, int len) throws IOException {
// Check parameters.
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;
}
// Flush any bits in the current byte.
flushBits();
// Zero the number of doubles put.
int numPut = 0;
// Get view buffer.
DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer();
// Loop until all doubles have been put.
do {
// Determine number of doubles to put.
int numToPut = Math.min(len - numPut,
viewBuffer.remaining());
// If no doubles to put, the buffer has to be full as len
// is always greater than numPut so flush it and return
// to start of loop.
if(numToPut == 0) {
flushBuffer();
continue;
}
// Put the doubles in the buffer.
viewBuffer.put(d, off + numPut, numToPut);
// Sync the ByteBuffer position.
byteBuffer.position(byteBuffer.position() + 8*numToPut);
// Increment the put counter.
numPut += numToPut;
} while(numPut < len);
// Increment the stream position.
streamPos += 8*len;
}
// --- Other methods ---
/**
* Invokes the superclass method, writes any unwritten data, 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.
*/
// Note that this method is called by the superclass finalize()
// so this class does not need to implement finalize().
public void close() throws IOException {
// Flush any unwritten data in the buffer.
flushBuffer();
// Close the read channel and clear the reference to it.
readStream.close();
readStream = null;
// Clear reference to the channel.
channel = null;
// Clear reference to the internal ByteBuffer.
byteBuffer = null;
// Chain to the superclass.
super.close();
}
/**
* 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, writes any unwritten data,
* and sets the channel position to the supplied parameter.
*/
public void seek(long pos) throws IOException {
super.seek(pos);
// Flush any unwritten data in the buffer.
flushBuffer();
// Set the FileChannel position for WritableByteChannel.write().
channel.position(pos);
}
public void setByteOrder(ByteOrder networkByteOrder) {
super.setByteOrder(networkByteOrder);
byteBuffer.order(networkByteOrder);
}
}