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}