001/* 002 * $RCSfile: FileChannelImageOutputStream.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.1 $ 042 * $Date: 2005/02/11 05:01:20 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.stream; 046 047import java.io.IOException; 048import java.nio.ByteBuffer; 049import java.nio.ByteOrder; 050import java.nio.CharBuffer; 051import java.nio.DoubleBuffer; 052import java.nio.FloatBuffer; 053import java.nio.IntBuffer; 054import java.nio.LongBuffer; 055import java.nio.ShortBuffer; 056import java.nio.channels.FileChannel; 057 058import javax.imageio.stream.ImageInputStream; 059import javax.imageio.stream.ImageOutputStreamImpl; 060 061/** 062 * A class which implements <code>ImageOutputStream</code> using a 063 * <code>FileChannel</code> as the eventual data destination. 064 * 065 * <p>Memory mapping is used for reading and direct buffers for writing. 066 * Only methods which provide significant performance improvement with 067 * respect to the superclass implementation are overridden. Overridden 068 * methods are not commented individually unless some noteworthy aspect 069 * of the implementation must be described.</p> 070 * 071 * <p>The methods of this class are <b>not</b> synchronized.</p> 072 * 073 * @see javax.imageio.stream.ImageOutputStream 074 * @see java.nio 075 * @see java.nio.channels.FileChannel 076 */ 077public class FileChannelImageOutputStream extends ImageOutputStreamImpl { 078 079 /** The size of the write buffer. */ 080 // XXX Should this be a different value? Smaller? 081 // Should there be an instance variable with public 082 // accessor/mutator methods? 083 private static final int DEFAULT_WRITE_BUFFER_SIZE = 1048576; 084 085 /** The <code>FileChannel</code> data destination. */ 086 private FileChannel channel; 087 088 /** A <code>ByteBuffer</code> used for writing to the channel. */ 089 private ByteBuffer byteBuffer; 090 091 /** An <code>ImageInputStream</code> used for reading. */ 092 private ImageInputStream readStream = null; 093 094 /** 095 * Test method. 096 * 097 * @param args Command line arguments (ignored). 098 * @throws Throwable for any Exception or Error. 099 */ 100 /* XXX 101 public static void main(String[] args) throws Throwable { 102 // Create files. 103 java.io.RandomAccessFile fileFIOS = 104 new java.io.RandomAccessFile("fios.tmp", "rw"); 105 java.io.RandomAccessFile fileFCIOS = 106 new java.io.RandomAccessFile("fcios.tmp", "rw"); 107 108 // Create streams. 109 javax.imageio.stream.ImageOutputStream fios = 110 new javax.imageio.stream.FileImageOutputStream(fileFIOS); 111 javax.imageio.stream.ImageOutputStream fcios = 112 new FileChannelImageOutputStream(fileFCIOS.getChannel()); 113 114 int datumSize = 4; 115 116 // Write some ints (257, 258) 117 fios.writeInts(new int[] {(int)256, (int)257, 118 (int)258, (int)59}, 119 1, 2); 120 fcios.writeInts(new int[] {(int)256, (int)257, 121 (int)258, (int)259}, 122 1, 2); 123 124 // Write a byte. 125 fios.write(127); 126 fcios.write(127); 127 128 // Write some more ints (262, 263, 264). 129 fios.writeInts(new int[] {(int)260, (int)261, (int)262, 130 (int)263, (int)264, (int)265}, 2, 3); 131 fcios.writeInts(new int[] {(int)260, (int)261, (int)262, 132 (int)263, (int)264, (int)265}, 2, 3); 133 134 // Seek to byte location. 135 fios.seek(2*datumSize); 136 fcios.seek(2*datumSize); 137 138 // Read and print the byte. 139 System.out.println(fios.read()); 140 System.out.println(fcios.read()); 141 142 // Seek to beginning. 143 fios.seek(0); 144 fcios.seek(0); 145 146 int[] fiosInts = new int[10]; 147 int[] fciosInts = new int[10]; 148 149 // Read the ints. 150 fios.readFully(fiosInts, 1, 2); 151 fcios.readFully(fciosInts, 1, 2); 152 153 // Print the ints 154 for(int i = 0; i < 2; i++) { 155 System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]); 156 } 157 158 // Seek to second set of ints 159 fios.seek(2*datumSize+1); 160 fcios.seek(2*datumSize+1); 161 162 // Read the ints. 163 fios.readFully(fiosInts, 1, 3); 164 fcios.readFully(fciosInts, 1, 3); 165 166 // Print the ints 167 for(int i = 0; i < 3; i++) { 168 System.out.println((int)fiosInts[i+1]+" "+(int)fciosInts[i+1]); 169 } 170 } 171 */ 172 173 /** 174 * Constructs a <code>FileChannelImageOutputStream</code> from a 175 * <code>FileChannel</code>. The initial position of the stream 176 * stream is taken to be the position of the <code>FileChannel</code> 177 * parameter when this constructor is invoked. The stream and flushed 178 * positions are therefore both initialized to 179 * <code>channel.position()</code>. 180 * 181 * @param channel the destination <code>FileChannel</code>. 182 * 183 * @throws IllegalArgumentException if <code>channel</code> is 184 * <code>null</code> or is not open. 185 * @throws IOException if a method invoked on <code>channel</code> 186 * throws an <code>IOException</code>. 187 */ 188 public FileChannelImageOutputStream(FileChannel channel) 189 throws IOException { 190 191 // Check the parameter. 192 if(channel == null) { 193 throw new IllegalArgumentException("channel == null"); 194 } else if(!channel.isOpen()) { 195 throw new IllegalArgumentException("channel.isOpen() == false"); 196 } 197 198 // Save the channel reference. 199 this.channel = channel; 200 201 // Set stream and flushed positions to initial channel position. 202 this.streamPos = this.flushedPos = channel.position(); 203 204 // Allocate the write buffer. 205 byteBuffer = ByteBuffer.allocateDirect(DEFAULT_WRITE_BUFFER_SIZE); 206 207 // Create the read stream (initially zero-sized). 208 readStream = new FileChannelImageInputStream(channel); 209 } 210 211 /** 212 * Returns an <code>ImageInputStream</code> for reading. The 213 * returned stream has byte order, stream and flushed positions, 214 * and bit offset set to the current values for this stream. 215 */ 216 private ImageInputStream getImageInputStream() throws IOException { 217 // Write any unwritten bytes. 218 flushBuffer(); 219 220 // Sync input stream state to state of this stream. 221 readStream.setByteOrder(byteOrder); 222 readStream.seek(streamPos); 223 readStream.flushBefore(flushedPos); 224 readStream.setBitOffset(bitOffset); 225 226 return readStream; 227 } 228 229 /** 230 * Write to the <code>FileChannel</code> any remaining bytes in 231 * the byte output buffer. 232 */ 233 private void flushBuffer() throws IOException { 234 if(byteBuffer.position() != 0) { 235 // Set the limit to the position. 236 byteBuffer.limit(byteBuffer.position()); 237 238 // Set the position to zero. 239 byteBuffer.position(0); 240 241 // Write all bytes between zero and the previous position. 242 channel.write(byteBuffer); 243 244 // Prepare for subsequent put() calls if any. 245 byteBuffer.clear(); 246 } 247 } 248 249 // --- Implementation of superclass abstract methods. --- 250 251 public int read() throws IOException { 252 checkClosed(); 253 bitOffset = 0; 254 255 ImageInputStream inputStream = getImageInputStream(); 256 257 streamPos++; 258 259 return inputStream.read(); 260 } 261 262 public int read(byte[] b, int off, int len) throws IOException { 263 // Check parameters. 264 if(off < 0 || len < 0 || off + len > b.length) { 265 throw new IndexOutOfBoundsException 266 ("off < 0 || len < 0 || off + len > b.length"); 267 } else if(len == 0) { 268 return 0; 269 } 270 271 checkClosed(); 272 bitOffset = 0; 273 274 ImageInputStream inputStream = getImageInputStream(); 275 276 int numBytesRead = inputStream.read(b, off, len); 277 278 streamPos += numBytesRead; 279 280 return numBytesRead; 281 } 282 283 public void write(int b) throws IOException { 284 write(new byte[] {(byte)(b&0xff)}, 0, 1); 285 } 286 287 public void write(byte[] b, int off, int len) throws IOException { 288 289 // Check parameters. 290 if(off < 0 || len < 0 || off + len > b.length) { 291 // NullPointerException will be thrown before this if b is null. 292 throw new IndexOutOfBoundsException 293 ("off < 0 || len < 0 || off + len > b.length"); 294 } else if(len == 0) { 295 return; 296 } 297 298 // Flush any bits in the current byte. 299 flushBits(); 300 301 // Zero the number of bytes to put. 302 int numPut = 0; 303 304 // Loop until all bytes have been put. 305 do { 306 // Determine number of bytes to put. 307 int numToPut = Math.min(len - numPut, 308 byteBuffer.remaining()); 309 310 // If no bytes to put, the buffer has to be full as len 311 // is always greater than numPut so flush it and return 312 // to start of loop. 313 if(numToPut == 0) { 314 flushBuffer(); 315 continue; 316 } 317 318 // Put the bytes in the buffer. 319 byteBuffer.put(b, off + numPut, numToPut); 320 321 // Increment the put counter. 322 numPut += numToPut; 323 } while(numPut < len); 324 325 // Increment the stream position. 326 streamPos += len; 327 } 328 329 // --- Overriding of superclass methods. --- 330 331 // --- Bulk read methods --- 332 333 public void readFully(char[] c, int off, int len) throws IOException { 334 getImageInputStream().readFully(c, off, len); 335 streamPos += 2*len; 336 } 337 338 public void readFully(short[] s, int off, int len) throws IOException { 339 getImageInputStream().readFully(s, off, len); 340 streamPos += 2*len; 341 } 342 343 public void readFully(int[] i, int off, int len) throws IOException { 344 getImageInputStream().readFully(i, off, len); 345 streamPos += 4*len; 346 } 347 348 public void readFully(long[] l, int off, int len) throws IOException { 349 getImageInputStream().readFully(l, off, len); 350 streamPos += 8*len; 351 } 352 353 public void readFully(float[] f, int off, int len) throws IOException { 354 getImageInputStream().readFully(f, off, len); 355 streamPos += 4*len; 356 } 357 358 public void readFully(double[] d, int off, int len) throws IOException { 359 getImageInputStream().readFully(d, off, len); 360 streamPos += 8*len; 361 } 362 363 // --- Bulk write methods --- 364 365 public void writeChars(char[] c, int off, int len) throws IOException { 366 367 // Check parameters. 368 if(off < 0 || len < 0 || off + len > c.length) { 369 // NullPointerException will be thrown before this if c is null. 370 throw new IndexOutOfBoundsException 371 ("off < 0 || len < 0 || off + len > c.length"); 372 } else if(len == 0) { 373 return; 374 } 375 376 // Flush any bits in the current byte. 377 flushBits(); 378 379 // Zero the number of chars put. 380 int numPut = 0; 381 382 // Get view buffer. 383 CharBuffer viewBuffer = byteBuffer.asCharBuffer(); 384 385 // Loop until all chars have been put. 386 do { 387 // Determine number of chars to put. 388 int numToPut = Math.min(len - numPut, 389 viewBuffer.remaining()); 390 391 // If no chars to put, the buffer has to be full as len 392 // is always greater than numPut so flush it and return 393 // to start of loop. 394 if(numToPut == 0) { 395 flushBuffer(); 396 continue; 397 } 398 399 // Put the chars in the buffer. 400 viewBuffer.put(c, off + numPut, numToPut); 401 402 // Sync the ByteBuffer position. 403 byteBuffer.position(byteBuffer.position() + 2*numToPut); 404 405 // Increment the put counter. 406 numPut += numToPut; 407 } while(numPut < len); 408 409 // Increment the stream position. 410 streamPos += 2*len; 411 } 412 413 public void writeShorts(short[] s, int off, int len) throws IOException { 414 415 // Check parameters. 416 if(off < 0 || len < 0 || off + len > s.length) { 417 // NullPointerException will be thrown before this if s is null. 418 throw new IndexOutOfBoundsException 419 ("off < 0 || len < 0 || off + len > c.length"); 420 } else if(len == 0) { 421 return; 422 } 423 424 // Flush any bits in the current byte. 425 flushBits(); 426 427 // Zero the number of shorts put. 428 int numPut = 0; 429 430 // Get view buffer. 431 ShortBuffer viewBuffer = byteBuffer.asShortBuffer(); 432 433 // Loop until all shorts have been put. 434 do { 435 // Determine number of shorts to put. 436 int numToPut = Math.min(len - numPut, 437 viewBuffer.remaining()); 438 439 // If no shorts to put, the buffer has to be full as len 440 // is always greater than numPut so flush it and return 441 // to start of loop. 442 if(numToPut == 0) { 443 flushBuffer(); 444 continue; 445 } 446 447 // Put the shorts in the buffer. 448 viewBuffer.put(s, off + numPut, numToPut); 449 450 // Sync the ByteBuffer position. 451 byteBuffer.position(byteBuffer.position() + 2*numToPut); 452 453 // Increment the put counter. 454 numPut += numToPut; 455 } while(numPut < len); 456 457 // Increment the stream position. 458 streamPos += 2*len; 459 } 460 461 public void writeInts(int[] i, int off, int len) throws IOException { 462 463 // Check parameters. 464 if(off < 0 || len < 0 || off + len > i.length) { 465 // NullPointerException will be thrown before this if i is null. 466 throw new IndexOutOfBoundsException 467 ("off < 0 || len < 0 || off + len > c.length"); 468 } else if(len == 0) { 469 return; 470 } 471 472 // Flush any bits in the current byte. 473 flushBits(); 474 475 // Zero the number of ints put. 476 int numPut = 0; 477 478 // Get view buffer. 479 IntBuffer viewBuffer = byteBuffer.asIntBuffer(); 480 481 // Loop until all ints have been put. 482 do { 483 // Determine number of ints to put. 484 int numToPut = Math.min(len - numPut, 485 viewBuffer.remaining()); 486 487 // If no ints to put, the buffer has to be full as len 488 // is always greater than numPut so flush it and return 489 // to start of loop. 490 if(numToPut == 0) { 491 flushBuffer(); 492 continue; 493 } 494 495 // Put the ints in the buffer. 496 viewBuffer.put(i, off + numPut, numToPut); 497 498 // Sync the ByteBuffer position. 499 byteBuffer.position(byteBuffer.position() + 4*numToPut); 500 501 // Increment the put counter. 502 numPut += numToPut; 503 } while(numPut < len); 504 505 // Increment the stream position. 506 streamPos += 4*len; 507 } 508 509 public void writeLongs(long[] l, int off, int len) throws IOException { 510 511 // Check parameters. 512 if(off < 0 || len < 0 || off + len > l.length) { 513 // NullPointerException will be thrown before this if l is null. 514 throw new IndexOutOfBoundsException 515 ("off < 0 || len < 0 || off + len > c.length"); 516 } else if(len == 0) { 517 return; 518 } 519 520 // Flush any bits in the current byte. 521 flushBits(); 522 523 // Zero the number of longs put. 524 int numPut = 0; 525 526 // Get view buffer. 527 LongBuffer viewBuffer = byteBuffer.asLongBuffer(); 528 529 // Loop until all longs have been put. 530 do { 531 // Determine number of longs to put. 532 int numToPut = Math.min(len - numPut, 533 viewBuffer.remaining()); 534 535 // If no longs to put, the buffer has to be full as len 536 // is always greater than numPut so flush it and return 537 // to start of loop. 538 if(numToPut == 0) { 539 flushBuffer(); 540 continue; 541 } 542 543 // Put the longs in the buffer. 544 viewBuffer.put(l, off + numPut, numToPut); 545 546 // Sync the ByteBuffer position. 547 byteBuffer.position(byteBuffer.position() + 8*numToPut); 548 549 // Increment the put counter. 550 numPut += numToPut; 551 } while(numPut < len); 552 553 // Increment the stream position. 554 streamPos += 8*len; 555 } 556 557 public void writeFloats(float[] f, int off, int len) throws IOException { 558 559 // Check parameters. 560 if(off < 0 || len < 0 || off + len > f.length) { 561 // NullPointerException will be thrown before this if c is null. 562 throw new IndexOutOfBoundsException 563 ("off < 0 || len < 0 || off + len > f.length"); 564 } else if(len == 0) { 565 return; 566 } 567 568 // Flush any bits in the current byte. 569 flushBits(); 570 571 // Zero the number of floats put. 572 int numPut = 0; 573 574 // Get view buffer. 575 FloatBuffer viewBuffer = byteBuffer.asFloatBuffer(); 576 577 // Loop until all floats have been put. 578 do { 579 // Determine number of floats to put. 580 int numToPut = Math.min(len - numPut, 581 viewBuffer.remaining()); 582 583 // If no floats to put, the buffer has to be full as len 584 // is always greater than numPut so flush it and return 585 // to start of loop. 586 if(numToPut == 0) { 587 flushBuffer(); 588 continue; 589 } 590 591 // Put the floats in the buffer. 592 viewBuffer.put(f, off + numPut, numToPut); 593 594 // Sync the ByteBuffer position. 595 byteBuffer.position(byteBuffer.position() + 4*numToPut); 596 597 // Increment the put counter. 598 numPut += numToPut; 599 } while(numPut < len); 600 601 // Increment the stream position. 602 streamPos += 4*len; 603 } 604 605 public void writeDoubles(double[] d, int off, int len) throws IOException { 606 607 // Check parameters. 608 if(off < 0 || len < 0 || off + len > d.length) { 609 // NullPointerException will be thrown before this if d is null. 610 throw new IndexOutOfBoundsException 611 ("off < 0 || len < 0 || off + len > d.length"); 612 } else if(len == 0) { 613 return; 614 } 615 616 // Flush any bits in the current byte. 617 flushBits(); 618 619 // Zero the number of doubles put. 620 int numPut = 0; 621 622 // Get view buffer. 623 DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer(); 624 625 // Loop until all doubles have been put. 626 do { 627 // Determine number of doubles to put. 628 int numToPut = Math.min(len - numPut, 629 viewBuffer.remaining()); 630 631 // If no doubles to put, the buffer has to be full as len 632 // is always greater than numPut so flush it and return 633 // to start of loop. 634 if(numToPut == 0) { 635 flushBuffer(); 636 continue; 637 } 638 639 // Put the doubles in the buffer. 640 viewBuffer.put(d, off + numPut, numToPut); 641 642 // Sync the ByteBuffer position. 643 byteBuffer.position(byteBuffer.position() + 8*numToPut); 644 645 // Increment the put counter. 646 numPut += numToPut; 647 } while(numPut < len); 648 649 // Increment the stream position. 650 streamPos += 8*len; 651 } 652 653 // --- Other methods --- 654 655 /** 656 * Invokes the superclass method, writes any unwritten data, and 657 * sets the internal reference to the source <code>FileChannel</code> 658 * to <code>null</code>. The source <code>FileChannel</code> is not 659 * closed. 660 * 661 * @exception IOException if an error occurs. 662 */ 663 // Note that this method is called by the superclass finalize() 664 // so this class does not need to implement finalize(). 665 public void close() throws IOException { 666 // Flush any unwritten data in the buffer. 667 flushBuffer(); 668 669 // Close the read channel and clear the reference to it. 670 readStream.close(); 671 readStream = null; 672 673 // Clear reference to the channel. 674 channel = null; 675 676 // Clear reference to the internal ByteBuffer. 677 byteBuffer = null; 678 679 // Chain to the superclass. 680 super.close(); 681 } 682 683 /** 684 * Returns the number of bytes currently in the <code>FileChannel</code>. 685 * If an <code>IOException</code> is encountered when querying the 686 * channel's size, -1L will be returned. 687 * 688 * @return The number of bytes in the channel 689 * -1L to indicate unknown length. 690 */ 691 public long length() { 692 // Initialize to value indicating unknown length. 693 long length = -1L; 694 695 // Set length to current size with respect to initial position. 696 try { 697 length = channel.size(); 698 } catch(IOException e) { 699 // Default to unknown length. 700 } 701 702 return length; 703 } 704 705 /** 706 * Invokes the superclass method, writes any unwritten data, 707 * and sets the channel position to the supplied parameter. 708 */ 709 public void seek(long pos) throws IOException { 710 super.seek(pos); 711 712 // Flush any unwritten data in the buffer. 713 flushBuffer(); 714 715 // Set the FileChannel position for WritableByteChannel.write(). 716 channel.position(pos); 717 } 718 719 public void setByteOrder(ByteOrder networkByteOrder) { 720 super.setByteOrder(networkByteOrder); 721 byteBuffer.order(networkByteOrder); 722 } 723}