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}