001/* 002 * jDTAUS Core Utilities 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.core.io.util; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.util.Iterator; 027import java.util.Locale; 028import java.util.Map; 029import java.util.TreeMap; 030import org.jdtaus.core.container.ContainerFactory; 031import org.jdtaus.core.io.FileOperations; 032import org.jdtaus.core.lang.spi.MemoryManager; 033import org.jdtaus.core.logging.spi.Logger; 034 035/** 036 * Coalescing {@code FileOperations} cache. 037 * <p>This implementation implements a coalescing cache for 038 * {@code FileOperations} implementations. The cache is controlled by 039 * configuration property {@code blockSize}. By default property 040 * {@code blockSize} is initialized to {@code 2097152} leading to a cache 041 * size of 10 MB (multiplied by property {@code cacheSize} which defaults to 042 * {@code 5}). All memory is allocated during instantiation so that an 043 * {@code OutOfMemoryError} may be thrown when constructing the cache but not 044 * when working with the instance.</p> 045 * 046 * <p><b>Note:</b><br> 047 * This implementation is not thread-safe and concurrent changes to the 048 * underlying {@code FileOperations} implementation are not supported.</p> 049 * 050 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 051 * @version $JDTAUS: CoalescingFileOperations.java 8641 2012-09-27 06:45:17Z schulte $ 052 */ 053public final class CoalescingFileOperations implements FlushableFileOperations 054{ 055 //--Dependencies------------------------------------------------------------ 056 057// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 058 // This section is managed by jdtaus-container-mojo. 059 060 /** 061 * Gets the configured <code>MemoryManager</code> implementation. 062 * 063 * @return The configured <code>MemoryManager</code> implementation. 064 */ 065 private MemoryManager getMemoryManager() 066 { 067 return (MemoryManager) ContainerFactory.getContainer(). 068 getDependency( this, "MemoryManager" ); 069 070 } 071 072 /** 073 * Gets the configured <code>Locale</code> implementation. 074 * 075 * @return The configured <code>Locale</code> implementation. 076 */ 077 private Locale getLocale() 078 { 079 return (Locale) ContainerFactory.getContainer(). 080 getDependency( this, "Locale" ); 081 082 } 083 084 /** 085 * Gets the configured <code>Logger</code> implementation. 086 * 087 * @return The configured <code>Logger</code> implementation. 088 */ 089 private Logger getLogger() 090 { 091 return (Logger) ContainerFactory.getContainer(). 092 getDependency( this, "Logger" ); 093 094 } 095 096// </editor-fold>//GEN-END:jdtausDependencies 097 098 //------------------------------------------------------------Dependencies-- 099 //--Properties-------------------------------------------------------------- 100 101// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 102 // This section is managed by jdtaus-container-mojo. 103 104 /** 105 * Gets the value of property <code>defaultCacheBlocks</code>. 106 * 107 * @return Default number of cache blocks. 108 */ 109 private java.lang.Integer getDefaultCacheBlocks() 110 { 111 return (java.lang.Integer) ContainerFactory.getContainer(). 112 getProperty( this, "defaultCacheBlocks" ); 113 114 } 115 116 /** 117 * Gets the value of property <code>defaultBlockSize</code>. 118 * 119 * @return Default size of one cache block in byte. 120 */ 121 private java.lang.Integer getDefaultBlockSize() 122 { 123 return (java.lang.Integer) ContainerFactory.getContainer(). 124 getProperty( this, "defaultBlockSize" ); 125 126 } 127 128// </editor-fold>//GEN-END:jdtausProperties 129 130 //--------------------------------------------------------------Properties-- 131 //--FileOperations---------------------------------------------------------- 132 133 public long getLength() throws IOException 134 { 135 this.assertNotClosed(); 136 137 return this.getFileOperations().getLength(); 138 } 139 140 public void setLength( final long newLength ) throws IOException 141 { 142 this.assertNotClosed(); 143 144 // Update the length of any cache nodes involved in the operation. 145 final long oldLength = this.getLength(); 146 if ( newLength > oldLength ) 147 { 148 final long delta = newLength - oldLength; 149 150 assert delta <= Integer.MAX_VALUE : 151 "Unexpected implementation limit reached."; 152 153 final Node[] nodes = 154 this.getCacheNodesForLength( oldLength, (int) delta ); 155 156 for ( int i = 0; i < nodes.length; i++ ) 157 { 158 final long startPos = nodes[i].block * this.getBlockSize(); 159 final long blockDelta = newLength - startPos; 160 161 assert blockDelta <= Integer.MAX_VALUE : 162 "Unexpected implementation limit reached."; 163 164 nodes[i].length = blockDelta >= this.getBlockSize() 165 ? this.getBlockSize() 166 : (int) blockDelta; 167 168 } 169 } 170 else if ( newLength < oldLength ) 171 { 172 final long delta = oldLength - newLength; 173 174 assert delta <= Integer.MAX_VALUE : 175 "Unexpected implementation limit reached."; 176 177 final Node[] nodes = 178 this.getCacheNodesForLength( newLength, (int) delta ); 179 180 for ( int i = 0; i < nodes.length; i++ ) 181 { 182 final long startPos = nodes[i].block * this.getBlockSize(); 183 if ( startPos > newLength ) 184 { // Discard the block. 185 this.root.remove( new Long( nodes[i].block ) ); 186 } 187 else 188 { // Update the blocks length. 189 final long blockDelta = newLength - startPos; 190 191 assert blockDelta <= Integer.MAX_VALUE : 192 "Unexpected implementation limit reached."; 193 194 nodes[i].length = blockDelta >= this.getBlockSize() 195 ? this.getBlockSize() 196 : (int) blockDelta; 197 198 } 199 } 200 } 201 202 this.getFileOperations().setLength( newLength ); 203 204 if ( this.filePointer > newLength ) 205 { 206 this.filePointer = newLength; 207 } 208 } 209 210 public long getFilePointer() throws IOException 211 { 212 this.assertNotClosed(); 213 214 return this.filePointer; 215 } 216 217 public void setFilePointer( final long pos ) throws IOException 218 { 219 this.assertNotClosed(); 220 221 this.filePointer = pos; 222 } 223 224 public int read( final byte[] buf, int off, int len ) throws IOException 225 { 226 if ( buf == null ) 227 { 228 throw new NullPointerException( "buf" ); 229 } 230 if ( off < 0 ) 231 { 232 throw new IndexOutOfBoundsException( Integer.toString( off ) ); 233 } 234 if ( len < 0 ) 235 { 236 throw new IndexOutOfBoundsException( Integer.toString( len ) ); 237 } 238 if ( off + len > buf.length ) 239 { 240 throw new IndexOutOfBoundsException( Integer.toString( off + len ) ); 241 } 242 243 this.assertNotClosed(); 244 245 int read = FileOperations.EOF; 246 247 if ( len == 0 ) 248 { 249 read = 0; 250 } 251 else if ( this.filePointer < this.getLength() ) 252 { // End of file not reached. 253 final Node[] nodes = 254 this.getCacheNodesForLength( this.filePointer, len ); 255 256 // Ensure cache holds the data of the involved blocks. 257 this.fillCache( nodes ); 258 259 int copied = 0; 260 for ( int i = 0; i < nodes.length; i++ ) 261 { 262 if ( nodes[i].length == FileOperations.EOF ) 263 { // Skip any end of file nodes. 264 continue; 265 } 266 267 if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX ) 268 { // Node is associated with cache memory; cache is used. 269 270 // Use the current file pointer as the starting index. 271 final long delta = 272 nodes[i].cacheIndex + 273 ( this.filePointer - nodes[i].block * 274 this.getBlockSize() ); 275 276 assert delta <= Integer.MAX_VALUE : 277 "Unexpected implementation limit reached."; 278 279 final int blockOffset = (int) delta; 280 final int blockDelta = nodes[i].length - 281 ( blockOffset - nodes[i].cacheIndex ); 282 283 final int copyLength = len > blockDelta 284 ? blockDelta 285 : len; 286 287 System.arraycopy( this.getCache(), blockOffset, buf, off, 288 copyLength ); 289 290 off += copyLength; 291 len -= copyLength; 292 copied += copyLength; 293 this.filePointer += copyLength; 294 } 295 else 296 { // Node is not associated with cache memory; read directly. 297 this.getFileOperations().setFilePointer( this.filePointer ); 298 copied += this.getFileOperations().read( buf, off, len ); 299 this.filePointer += len; 300 301 this.getLogger().debug( 302 this.getReadBypassesCacheMessage( 303 this.getLocale(), 304 new Integer( this.getBlockSize() ), 305 new Integer( this.getCacheBlocks() ), 306 new Integer( len ) ) ); 307 308 309 break; 310 } 311 } 312 313 read = copied; 314 } 315 316 return read; 317 } 318 319 public void write( final byte[] buf, int off, int len ) throws IOException 320 { 321 if ( buf == null ) 322 { 323 throw new NullPointerException( "buf" ); 324 } 325 if ( off < 0 ) 326 { 327 throw new IndexOutOfBoundsException( Integer.toString( off ) ); 328 } 329 if ( len < 0 ) 330 { 331 throw new IndexOutOfBoundsException( Integer.toString( len ) ); 332 } 333 if ( off + len > buf.length ) 334 { 335 throw new IndexOutOfBoundsException( Integer.toString( off + len ) ); 336 } 337 338 this.assertNotClosed(); 339 340 if ( this.filePointer + len > this.getLength() ) 341 { // Expand the file of the backing instance. 342 this.setLength( this.filePointer + len ); 343 } 344 345 final Node[] nodes = 346 this.getCacheNodesForLength( this.filePointer, len ); 347 348 // Ensure cache holds the data of the involved blocks. 349 this.fillCache( nodes ); 350 351 for ( int i = 0; i < nodes.length; i++ ) 352 { 353 // Check for correct file length update. 354 assert nodes[i].length != FileOperations.EOF : 355 "Unexpected cache state."; 356 357 if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX ) 358 { // Node is associated with cache memory; cache is used. 359 360 // Use the current file pointer as the starting index. 361 final long delta = nodes[i].cacheIndex + 362 ( this.filePointer - nodes[i].block * 363 this.getBlockSize() ); 364 365 assert delta <= Integer.MAX_VALUE : 366 "Unexpected implementation limit reached."; 367 368 final int blockOffset = (int) delta; 369 final int blockDelta = nodes[i].length - 370 ( blockOffset - nodes[i].cacheIndex ); 371 372 final int copyLength = len > blockDelta 373 ? blockDelta 374 : len; 375 376 System.arraycopy( buf, off, this.getCache(), blockOffset, 377 copyLength ); 378 379 off += copyLength; 380 len -= copyLength; 381 this.filePointer += copyLength; 382 nodes[i].dirty = true; 383 } 384 else 385 { // Node is not associated with cache memory; write out directly. 386 this.getFileOperations().setFilePointer( this.filePointer ); 387 this.getFileOperations().write( buf, off, len ); 388 this.filePointer += len; 389 390 this.getLogger().debug( 391 this.getWriteBypassesCacheMessage( 392 this.getLocale(), 393 new Integer( this.getBlockSize() ), 394 new Integer( this.getCacheBlocks() ), 395 new Integer( len ) ) ); 396 397 398 break; 399 } 400 } 401 } 402 403 public void read( final OutputStream out ) throws IOException 404 { 405 this.assertNotClosed(); 406 407 this.getFileOperations().read( out ); 408 this.filePointer = this.getFileOperations().getFilePointer(); 409 } 410 411 public void write( final InputStream in ) throws IOException 412 { 413 this.assertNotClosed(); 414 415 this.getFileOperations().write( in ); 416 this.filePointer = this.getFileOperations().getFilePointer(); 417 } 418 419 /** 420 * {@inheritDoc} 421 * Flushes the cache and closes the {@code FileOperations} implementation 422 * backing the instance. 423 * 424 * @throws IOException if flushing or closing the {@code FileOperations} 425 * implementation backing the instance fails, or if the instance already 426 * is closed. 427 */ 428 public void close() throws IOException 429 { 430 this.assertNotClosed(); 431 432 this.flush(); 433 this.getFileOperations().close(); 434 this.closed = true; 435 } 436 437 //----------------------------------------------------------FileOperations-- 438 //--FlushableFileOperations------------------------------------------------- 439 440 /** 441 * {@inheritDoc} 442 * This method calls the {@code flush()} method of an underlying 443 * {@code FlushableFileOperations} implementation, if any. 444 * 445 * @throws IOException if writing any pending changes fails or if the 446 * instance is closed. 447 */ 448 public void flush() throws IOException 449 { 450 this.assertNotClosed(); 451 452 this.defragmentCache(); 453 454 long startPos = FileOperations.EOF; 455 int startIndex = FileOperations.EOF; 456 int length = FileOperations.EOF; 457 Node previous = null; 458 boolean dirty = false; 459 460 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); ) 461 { 462 final Map.Entry entry = (Map.Entry) it.next(); 463 final long block = ( (Long) entry.getKey() ).longValue(); 464 final Node current = (Node) entry.getValue(); 465 466 // Skip any end of file nodes and nodes not associated with memory. 467 if ( current.length == FileOperations.EOF || 468 current.cacheIndex == Node.NO_CACHEINDEX ) 469 { 470 continue; 471 } 472 473 assert current.block == block : "Unexpected cache state."; 474 475 if ( previous == null ) 476 { // Start the first chunk. 477 previous = current; 478 startPos = current.block * this.getBlockSize(); 479 startIndex = current.cacheIndex; 480 length = current.length; 481 dirty = current.dirty; 482 } 483 else if ( current.block == previous.block + 1L ) 484 { // Expand the current chunk. 485 486 assert current.cacheIndex == previous.cacheIndex + 487 this.getBlockSize() : 488 "Unexpected cache state."; 489 490 previous = current; 491 length += current.length; 492 if ( !dirty ) 493 { 494 dirty = current.dirty; 495 } 496 } 497 else 498 { // Write out the current chunk and start a new one. 499 if ( dirty ) 500 { 501 this.getFileOperations().setFilePointer( startPos ); 502 this.getFileOperations().write( 503 this.getCache(), startIndex, length ); 504 505 } 506 507 previous = current; 508 startPos = current.block * this.getBlockSize(); 509 startIndex = current.cacheIndex; 510 length = current.length; 511 dirty = current.dirty; 512 } 513 } 514 515 if ( dirty ) 516 { // Write the remaining chunk. 517 this.getFileOperations().setFilePointer( startPos ); 518 this.getFileOperations().write( 519 this.getCache(), startIndex, length ); 520 521 } 522 523 // Reset cache state. 524 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); ) 525 { 526 final Map.Entry entry = (Map.Entry) it.next(); 527 final Node current = (Node) entry.getValue(); 528 529 current.cacheIndex = Node.NO_CACHEINDEX; 530 current.dirty = false; 531 } 532 533 this.nextCacheIndex = 0; 534 535 if ( this.getFileOperations() instanceof FlushableFileOperations ) 536 { // Cache of the backing instance also needs to get flushed. 537 ( (FlushableFileOperations) this.getFileOperations() ).flush(); 538 } 539 } 540 541 //-------------------------------------------------FlushableFileOperations-- 542 //--CoalescingFileOperations------------------------------------------------ 543 544 /** Node describing a cache block. */ 545 private static final class Node 546 { 547 548 private static final int NO_CACHEINDEX = Integer.MIN_VALUE; 549 550 long block; 551 552 int cacheIndex = NO_CACHEINDEX; 553 554 int length; 555 556 boolean dirty; 557 558 } 559 560 /** The {@code FileOperations} backing the instance. */ 561 private final FileOperations fileOperations; 562 563 /** Cached blocks. */ 564 private byte[] cache; 565 566 /** Second cache memory used during defragmentation. */ 567 private byte[] defragCache; 568 569 /** Index of the next free cached block. */ 570 private int nextCacheIndex; 571 572 /** Maps blocks to corresponding {@code Node}s. */ 573 private final Map root = new TreeMap(); 574 575 /** File pointer. */ 576 private long filePointer; 577 578 /** Caches the value returned by method {@code getFilePointerBlock}. */ 579 private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK; 580 581 private long cachedFilePointerBlockStart = FileOperations.EOF; 582 583 private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE; 584 585 /** Flags the instance as beeing closed. */ 586 private boolean closed; 587 588 /** The number of bytes occupied by one cache block. */ 589 private Integer blockSize; 590 591 /** The number of cache blocks. */ 592 private Integer cacheBlocks; 593 594 /** 595 * Creates a new {@code CoalescingFileOperations} instance taking the 596 * {@code FileOperations} backing the instance. 597 * 598 * @param fileOperations the {@code FileOperations} backing the instance. 599 * 600 * @throws NullPointerException if {@code fileOperations} is {@code null}. 601 * @throws IOException if reading fails. 602 */ 603 public CoalescingFileOperations( final FileOperations fileOperations ) 604 throws IOException 605 { 606 super(); 607 608 if ( fileOperations == null ) 609 { 610 throw new NullPointerException( "fileOperations" ); 611 } 612 613 this.fileOperations = fileOperations; 614 this.filePointer = fileOperations.getFilePointer(); 615 } 616 617 /** 618 * Creates a new {@code CoalescingFileOperations} instance taking the 619 * {@code FileOperations} backing the instance and the number of bytes 620 * occupied by one cache block. 621 * 622 * @param fileOperations the {@code FileOperations} backing the instance. 623 * @param blockSize the number of bytes occupied by one cache block. 624 * 625 * @throws NullPointerException if {@code fileOperations} is {@code null}. 626 * @throws IOException if reading fails. 627 */ 628 public CoalescingFileOperations( final FileOperations fileOperations, 629 final int blockSize ) throws IOException 630 { 631 this( fileOperations ); 632 if ( blockSize > 0 ) 633 { 634 this.blockSize = new Integer( blockSize ); 635 } 636 } 637 638 /** 639 * Creates a new {@code CoalescingFileOperations} instance taking the 640 * {@code FileOperations} backing the instance, the number of bytes 641 * occupied by one cache block and the number of cache blocks. 642 * 643 * @param fileOperations the {@code FileOperations} backing the instance. 644 * @param blockSize the number of bytes occupied by one cache block. 645 * @param cacheBlocks number of cache blocks. 646 * 647 * @throws NullPointerException if {@code fileOperations} is {@code null}. 648 * @throws IOException if reading fails. 649 */ 650 public CoalescingFileOperations( final FileOperations fileOperations, 651 final int blockSize, 652 final int cacheBlocks ) 653 throws IOException 654 { 655 this( fileOperations, blockSize ); 656 if ( cacheBlocks > 0 ) 657 { 658 this.cacheBlocks = new Integer( cacheBlocks ); 659 } 660 } 661 662 /** 663 * Gets the {@code FileOperations} implementation operations are performed 664 * with. 665 * 666 * @return the {@code FileOperations} implementation operations are 667 * performed with. 668 */ 669 public FileOperations getFileOperations() 670 { 671 return this.fileOperations; 672 } 673 674 /** 675 * Gets the number of bytes occupied by one cache block. 676 * 677 * @return the number of bytes occupied by one cache block. 678 */ 679 public int getBlockSize() 680 { 681 if ( this.blockSize == null ) 682 { 683 this.blockSize = this.getDefaultBlockSize(); 684 } 685 686 return this.blockSize.intValue(); 687 } 688 689 /** 690 * Gets the number of blocks in the cache. 691 * 692 * @return the number of blocks in the cache. 693 */ 694 public int getCacheBlocks() 695 { 696 if ( this.cacheBlocks == null ) 697 { 698 this.cacheBlocks = this.getDefaultCacheBlocks(); 699 } 700 701 return this.cacheBlocks.intValue(); 702 } 703 704 /** 705 * Gets the cache buffer. 706 * 707 * @return the cache buffer. 708 */ 709 private byte[] getCache() 710 { 711 if ( this.cache == null ) 712 { 713 this.cache = this.getMemoryManager().allocateBytes( 714 this.getBlockSize() * this.getCacheBlocks() ); 715 716 } 717 718 return this.cache; 719 } 720 721 /** 722 * Gets the buffer used during defragmentation of the cache. 723 * 724 * @return the buffer used during defragmentation of the cache. 725 */ 726 private byte[] getDefragCache() 727 { 728 if ( this.defragCache == null ) 729 { 730 this.defragCache = this.getMemoryManager().allocateBytes( 731 this.getBlockSize() * this.getCacheBlocks() ); 732 733 } 734 735 return this.defragCache; 736 } 737 738 /** 739 * Gets the block pointed to by a given file pointer value. 740 * 741 * @param filePointer the file pointer value for which to return the 742 * corresponding block. 743 * 744 * @return the block pointed to by {@code filePointer}. 745 */ 746 private long getFilePointerBlock( final long filePointer ) 747 { 748 if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK ) 749 { 750 this.cachedFilePointerBlock = 751 ( filePointer / this.getBlockSize() ) - 752 ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() ); 753 754 this.cachedFilePointerBlockStart = 755 this.cachedFilePointerBlock * this.getBlockSize(); 756 757 } 758 else 759 { 760 if ( !( filePointer >= this.cachedFilePointerBlockStart && 761 filePointer <= this.cachedFilePointerBlockStart + 762 this.getBlockSize() ) ) 763 { 764 this.cachedFilePointerBlock = 765 ( filePointer / this.getBlockSize() ) - 766 ( ( filePointer % this.getBlockSize() ) / 767 this.getBlockSize() ); 768 769 this.cachedFilePointerBlockStart = 770 this.cachedFilePointerBlock * this.getBlockSize(); 771 772 } 773 } 774 775 return this.cachedFilePointerBlock; 776 } 777 778 /** 779 * Gets the cache nodes for all blocks involved in a read or write operation 780 * of a given length for a given file pointer value. 781 * 782 * @param filePointer the file pointer value to use for computing the 783 * number of involved blocks for a read or write operation of 784 * {@code length}. 785 * @param length the length of the operation to perform. 786 * 787 * @return an array of cache nodes for all blocks involved in the 788 * operation in the order corresponding to the operation's needs. 789 */ 790 private Node[] getCacheNodesForLength( final long filePointer, 791 final int length ) 792 { 793 final long startingBlock = this.getFilePointerBlock( filePointer ); 794 final long endingBlock = 795 this.getFilePointerBlock( filePointer + length ); 796 797 assert endingBlock - startingBlock <= Integer.MAX_VALUE : 798 "Unexpected implementation limit reached."; 799 800 final Node[] nodes = 801 new Node[ (int) ( endingBlock - startingBlock + 1L ) ]; 802 803 if ( startingBlock == endingBlock ) 804 { 805 nodes[0] = this.getCacheNode( startingBlock ); 806 } 807 else 808 { 809 int i; 810 long block; 811 812 for ( block = startingBlock, i = 0; block <= endingBlock; 813 block++, i++ ) 814 { 815 nodes[i] = this.getCacheNode( block ); 816 } 817 } 818 819 return nodes; 820 } 821 822 /** 823 * Fills the cache for a given set of cache nodes. 824 * <p>This method ensures that each given node gets associated with 825 * corresponding cache memory possibly flushing the cache before 826 * reading.</p> 827 * 828 * @param nodes the nodes to fill the cache for. 829 * 830 * @throws NullPointerException if {@code nodes} is {@code null}. 831 * @throws IOException if reading fails. 832 */ 833 private void fillCache( final Node[] nodes ) throws IOException 834 { 835 if ( nodes == null ) 836 { 837 throw new NullPointerException( "nodes" ); 838 } 839 840 // Calculate the amount of bytes needed to be available in the cache 841 // and flush the cache if nodes would not fit. 842 long neededBytes = 0L; 843 for ( int i = nodes.length - 1; i >= 0; i-- ) 844 { 845 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX ) 846 { // Node's block needs to be read. 847 neededBytes += this.getBlockSize(); 848 } 849 } 850 851 if ( this.nextCacheIndex + neededBytes > this.getCache().length ) 852 { // Cache cannot hold the needed blocks so needs flushing. 853 this.flush(); 854 } 855 856 // Associate each node with cache memory for nodes not already 857 // associated with cache memory and fill these nodes' cache memory. 858 for ( int i = nodes.length - 1; i >= 0; i-- ) 859 { 860 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX && 861 this.nextCacheIndex < this.getCache().length ) 862 { // Node is not associated with any cache memory and can be read. 863 864 // Update the length field of the node for the block checking 865 // for a possible end of file condition. 866 long pos = nodes[i].block * this.getBlockSize(); 867 if ( pos > this.getLength() ) 868 { // Node is behind the end of the file. 869 nodes[i].length = FileOperations.EOF; 870 continue; 871 } 872 else if ( pos + this.getBlockSize() > this.getLength() ) 873 { 874 final long delta = this.getLength() - pos; 875 876 assert delta <= Integer.MAX_VALUE : 877 "Unexpected implementation limit reached."; 878 879 nodes[i].length = (int) delta; 880 } 881 else 882 { 883 nodes[i].length = this.getBlockSize(); 884 } 885 886 // Associated the node with cache memory. 887 nodes[i].cacheIndex = this.nextCacheIndex; 888 this.nextCacheIndex += this.getBlockSize(); 889 890 // Read the node's block into cache. 891 int read = FileOperations.EOF; 892 int totalRead = 0; 893 int toRead = nodes[i].length; 894 this.getFileOperations().setFilePointer( pos ); 895 896 do 897 { 898 read = this.getFileOperations().read( 899 this.getCache(), nodes[i].cacheIndex + totalRead, 900 toRead ); 901 902 assert read != FileOperations.EOF : 903 "Unexpected end of file."; 904 905 totalRead += read; 906 toRead -= read; 907 908 } 909 while ( totalRead < nodes[i].length ); 910 } 911 } 912 } 913 914 /** Defragments the cache. */ 915 private void defragmentCache() 916 { 917 int defragIndex = 0; 918 919 // Step through the cached blocks and defragment the cache. 920 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); ) 921 { 922 final Map.Entry entry = (Map.Entry) it.next(); 923 final long block = ( (Long) entry.getKey() ).longValue(); 924 final Node current = (Node) entry.getValue(); 925 926 // Skip any end of file nodes and nodes not associated with memory. 927 if ( current.length == FileOperations.EOF || 928 current.cacheIndex == Node.NO_CACHEINDEX ) 929 { 930 continue; 931 } 932 933 assert current.block == block : "Unexpected cache state."; 934 935 System.arraycopy( this.getCache(), current.cacheIndex, 936 this.getDefragCache(), defragIndex, 937 this.getBlockSize() ); 938 939 current.cacheIndex = defragIndex; 940 defragIndex += this.getBlockSize(); 941 } 942 943 System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0, 944 this.getCache().length ); 945 946 } 947 948 /** 949 * Gets the cache node for a given block. 950 * 951 * @param block the block to return the corresponding cache node for. 952 * 953 * @return the cache node for {@code block}. 954 */ 955 private Node getCacheNode( final long block ) 956 { 957 final Long key = new Long( block ); 958 Node node = (Node) this.root.get( key ); 959 if ( node == null ) 960 { 961 node = new Node(); 962 node.block = block; 963 this.root.put( key, node ); 964 } 965 966 return node; 967 } 968 969 /** 970 * Checks that the instance is not closed. 971 * 972 * @throws IOException if the instance is closed. 973 */ 974 private void assertNotClosed() throws IOException 975 { 976 if ( this.closed ) 977 { 978 throw new IOException( this.getAlreadyClosedMessage( 979 this.getLocale() ) ); 980 981 } 982 } 983 984 //------------------------------------------------CoalescingFileOperations-- 985 //--Messages---------------------------------------------------------------- 986 987// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 988 // This section is managed by jdtaus-container-mojo. 989 990 /** 991 * Gets the text of message <code>readBypassesCache</code>. 992 * <blockquote><pre>Eine Lese-Operation umging den Cache. Cache zu klein dimensioniert. Aktuelle Blockgröße ist {0,number} und Cache umfaßt {1,number} Blöcke. {2,number} Bytes konnten nicht zwischengespeichert werden.</pre></blockquote> 993 * <blockquote><pre>A read operation bypassed the cache. Consider increasing the cache. Current block size is {0,number} and current number of cache blocks is {1,number}. {2,number} bytes could not be cached.</pre></blockquote> 994 * 995 * @param locale The locale of the message instance to return. 996 * @param blockSize The current block size in use. 997 * @param cacheBlocks The current number of blocks in use. 998 * @param uncachedBytes The number of bytes bypassing caching. 999 * 1000 * @return Information about a misconfigured cache. 1001 */ 1002 private String getReadBypassesCacheMessage( final Locale locale, 1003 final java.lang.Number blockSize, 1004 final java.lang.Number cacheBlocks, 1005 final java.lang.Number uncachedBytes ) 1006 { 1007 return ContainerFactory.getContainer(). 1008 getMessage( this, "readBypassesCache", locale, 1009 new Object[] 1010 { 1011 blockSize, 1012 cacheBlocks, 1013 uncachedBytes 1014 }); 1015 1016 } 1017 1018 /** 1019 * Gets the text of message <code>writeBypassesCache</code>. 1020 * <blockquote><pre>Eine Schreib-Operation umging den Cache. Cache zu klein dimensioniert. Aktuelle Blockgröße ist {0,number} und Cache umfaßt {1,number} Blöcke. {2,number} Bytes konnten nicht zwischengespeichert werden.</pre></blockquote> 1021 * <blockquote><pre>A write operation bypassed the cache. Consider increasing the cache. Current block size is {0,number} and current number of cache blocks is {1,number}. {2,number} bytes could not be cached.</pre></blockquote> 1022 * 1023 * @param locale The locale of the message instance to return. 1024 * @param blockSize The current block size in use. 1025 * @param cacheBlocks The current number of blocks in use. 1026 * @param uncachedBytes The number of bytes bypassing caching. 1027 * 1028 * @return Information about a misconfigured cache. 1029 */ 1030 private String getWriteBypassesCacheMessage( final Locale locale, 1031 final java.lang.Number blockSize, 1032 final java.lang.Number cacheBlocks, 1033 final java.lang.Number uncachedBytes ) 1034 { 1035 return ContainerFactory.getContainer(). 1036 getMessage( this, "writeBypassesCache", locale, 1037 new Object[] 1038 { 1039 blockSize, 1040 cacheBlocks, 1041 uncachedBytes 1042 }); 1043 1044 } 1045 1046 /** 1047 * Gets the text of message <code>alreadyClosed</code>. 1048 * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote> 1049 * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote> 1050 * 1051 * @param locale The locale of the message instance to return. 1052 * 1053 * @return Message stating that an instance is already closed. 1054 */ 1055 private String getAlreadyClosedMessage( final Locale locale ) 1056 { 1057 return ContainerFactory.getContainer(). 1058 getMessage( this, "alreadyClosed", locale, null ); 1059 1060 } 1061 1062// </editor-fold>//GEN-END:jdtausMessages 1063 1064 //----------------------------------------------------------------Messages-- 1065}