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 8743 2012-10-07 03:06:20Z 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 ( final Iterator it = this.root.entrySet().iterator(); 461 it.hasNext(); ) 462 { 463 final Map.Entry entry = (Map.Entry) it.next(); 464 final long block = ( (Long) entry.getKey() ).longValue(); 465 final Node current = (Node) entry.getValue(); 466 467 // Skip any end of file nodes and nodes not associated with memory. 468 if ( current.length == FileOperations.EOF || 469 current.cacheIndex == Node.NO_CACHEINDEX ) 470 { 471 continue; 472 } 473 474 assert current.block == block : "Unexpected cache state."; 475 476 if ( previous == null ) 477 { // Start the first chunk. 478 previous = current; 479 startPos = current.block * this.getBlockSize(); 480 startIndex = current.cacheIndex; 481 length = current.length; 482 dirty = current.dirty; 483 } 484 else if ( current.block == previous.block + 1L ) 485 { // Expand the current chunk. 486 487 assert current.cacheIndex == previous.cacheIndex + 488 this.getBlockSize() : 489 "Unexpected cache state."; 490 491 previous = current; 492 length += current.length; 493 if ( !dirty ) 494 { 495 dirty = current.dirty; 496 } 497 } 498 else 499 { // Write out the current chunk and start a new one. 500 if ( dirty ) 501 { 502 this.getFileOperations().setFilePointer( startPos ); 503 this.getFileOperations().write( 504 this.getCache(), startIndex, length ); 505 506 } 507 508 previous = current; 509 startPos = current.block * this.getBlockSize(); 510 startIndex = current.cacheIndex; 511 length = current.length; 512 dirty = current.dirty; 513 } 514 } 515 516 if ( dirty ) 517 { // Write the remaining chunk. 518 this.getFileOperations().setFilePointer( startPos ); 519 this.getFileOperations().write( 520 this.getCache(), startIndex, length ); 521 522 } 523 524 // Reset cache state. 525 for ( final Iterator it = this.root.entrySet().iterator(); 526 it.hasNext(); ) 527 { 528 final Map.Entry entry = (Map.Entry) it.next(); 529 final Node current = (Node) entry.getValue(); 530 531 current.cacheIndex = Node.NO_CACHEINDEX; 532 current.dirty = false; 533 } 534 535 this.nextCacheIndex = 0; 536 537 if ( this.getFileOperations() instanceof FlushableFileOperations ) 538 { // Cache of the backing instance also needs to get flushed. 539 ( (FlushableFileOperations) this.getFileOperations() ).flush(); 540 } 541 } 542 543 //-------------------------------------------------FlushableFileOperations-- 544 //--CoalescingFileOperations------------------------------------------------ 545 546 /** Node describing a cache block. */ 547 private static final class Node 548 { 549 550 private static final int NO_CACHEINDEX = Integer.MIN_VALUE; 551 552 private Node() 553 { 554 super(); 555 } 556 557 private long block; 558 559 private int cacheIndex = NO_CACHEINDEX; 560 561 private int length; 562 563 private boolean dirty; 564 565 } 566 567 /** The {@code FileOperations} backing the instance. */ 568 private final FileOperations fileOperations; 569 570 /** Cached blocks. */ 571 private byte[] cache; 572 573 /** Second cache memory used during defragmentation. */ 574 private byte[] defragCache; 575 576 /** Index of the next free cached block. */ 577 private int nextCacheIndex; 578 579 /** Maps blocks to corresponding {@code Node}s. */ 580 private final Map root = new TreeMap(); 581 582 /** File pointer. */ 583 private long filePointer; 584 585 /** Caches the value returned by method {@code getFilePointerBlock}. */ 586 private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK; 587 588 private long cachedFilePointerBlockStart = FileOperations.EOF; 589 590 private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE; 591 592 /** Flags the instance as beeing closed. */ 593 private boolean closed; 594 595 /** The number of bytes occupied by one cache block. */ 596 private Integer blockSize; 597 598 /** The number of cache blocks. */ 599 private Integer cacheBlocks; 600 601 /** 602 * Creates a new {@code CoalescingFileOperations} instance taking the 603 * {@code FileOperations} backing the instance. 604 * 605 * @param fileOperations the {@code FileOperations} backing the instance. 606 * 607 * @throws NullPointerException if {@code fileOperations} is {@code null}. 608 * @throws IOException if reading fails. 609 */ 610 public CoalescingFileOperations( final FileOperations fileOperations ) 611 throws IOException 612 { 613 super(); 614 615 if ( fileOperations == null ) 616 { 617 throw new NullPointerException( "fileOperations" ); 618 } 619 620 this.fileOperations = fileOperations; 621 this.filePointer = fileOperations.getFilePointer(); 622 } 623 624 /** 625 * Creates a new {@code CoalescingFileOperations} instance taking the 626 * {@code FileOperations} backing the instance and the number of bytes 627 * occupied by one cache block. 628 * 629 * @param fileOperations the {@code FileOperations} backing the instance. 630 * @param blockSize the number of bytes occupied by one cache block. 631 * 632 * @throws NullPointerException if {@code fileOperations} is {@code null}. 633 * @throws IOException if reading fails. 634 */ 635 public CoalescingFileOperations( final FileOperations fileOperations, 636 final int blockSize ) throws IOException 637 { 638 this( fileOperations ); 639 if ( blockSize > 0 ) 640 { 641 this.blockSize = new Integer( blockSize ); 642 } 643 } 644 645 /** 646 * Creates a new {@code CoalescingFileOperations} instance taking the 647 * {@code FileOperations} backing the instance, the number of bytes 648 * occupied by one cache block and the number of cache blocks. 649 * 650 * @param fileOperations the {@code FileOperations} backing the instance. 651 * @param blockSize the number of bytes occupied by one cache block. 652 * @param cacheBlocks number of cache blocks. 653 * 654 * @throws NullPointerException if {@code fileOperations} is {@code null}. 655 * @throws IOException if reading fails. 656 */ 657 public CoalescingFileOperations( final FileOperations fileOperations, 658 final int blockSize, 659 final int cacheBlocks ) 660 throws IOException 661 { 662 this( fileOperations, blockSize ); 663 if ( cacheBlocks > 0 ) 664 { 665 this.cacheBlocks = new Integer( cacheBlocks ); 666 } 667 } 668 669 /** 670 * Gets the {@code FileOperations} implementation operations are performed 671 * with. 672 * 673 * @return the {@code FileOperations} implementation operations are 674 * performed with. 675 */ 676 public FileOperations getFileOperations() 677 { 678 return this.fileOperations; 679 } 680 681 /** 682 * Gets the number of bytes occupied by one cache block. 683 * 684 * @return the number of bytes occupied by one cache block. 685 */ 686 public int getBlockSize() 687 { 688 if ( this.blockSize == null ) 689 { 690 this.blockSize = this.getDefaultBlockSize(); 691 } 692 693 return this.blockSize.intValue(); 694 } 695 696 /** 697 * Gets the number of blocks in the cache. 698 * 699 * @return the number of blocks in the cache. 700 */ 701 public int getCacheBlocks() 702 { 703 if ( this.cacheBlocks == null ) 704 { 705 this.cacheBlocks = this.getDefaultCacheBlocks(); 706 } 707 708 return this.cacheBlocks.intValue(); 709 } 710 711 /** 712 * Gets the cache buffer. 713 * 714 * @return the cache buffer. 715 */ 716 private byte[] getCache() 717 { 718 if ( this.cache == null ) 719 { 720 this.cache = this.getMemoryManager().allocateBytes( 721 this.getBlockSize() * this.getCacheBlocks() ); 722 723 } 724 725 return this.cache; 726 } 727 728 /** 729 * Gets the buffer used during defragmentation of the cache. 730 * 731 * @return the buffer used during defragmentation of the cache. 732 */ 733 private byte[] getDefragCache() 734 { 735 if ( this.defragCache == null ) 736 { 737 this.defragCache = this.getMemoryManager().allocateBytes( 738 this.getBlockSize() * this.getCacheBlocks() ); 739 740 } 741 742 return this.defragCache; 743 } 744 745 /** 746 * Gets the block pointed to by a given file pointer value. 747 * 748 * @param filePointer the file pointer value for which to return the 749 * corresponding block. 750 * 751 * @return the block pointed to by {@code filePointer}. 752 */ 753 private long getFilePointerBlock( final long filePointer ) 754 { 755 if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK ) 756 { 757 this.cachedFilePointerBlock = 758 ( filePointer / this.getBlockSize() ) - 759 ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() ); 760 761 this.cachedFilePointerBlockStart = 762 this.cachedFilePointerBlock * this.getBlockSize(); 763 764 } 765 else 766 { 767 if ( !( filePointer >= this.cachedFilePointerBlockStart && 768 filePointer <= this.cachedFilePointerBlockStart + 769 this.getBlockSize() ) ) 770 { 771 this.cachedFilePointerBlock = 772 ( filePointer / this.getBlockSize() ) - 773 ( ( filePointer % this.getBlockSize() ) / 774 this.getBlockSize() ); 775 776 this.cachedFilePointerBlockStart = 777 this.cachedFilePointerBlock * this.getBlockSize(); 778 779 } 780 } 781 782 return this.cachedFilePointerBlock; 783 } 784 785 /** 786 * Gets the cache nodes for all blocks involved in a read or write operation 787 * of a given length for a given file pointer value. 788 * 789 * @param filePointer the file pointer value to use for computing the 790 * number of involved blocks for a read or write operation of 791 * {@code length}. 792 * @param length the length of the operation to perform. 793 * 794 * @return an array of cache nodes for all blocks involved in the 795 * operation in the order corresponding to the operation's needs. 796 */ 797 private Node[] getCacheNodesForLength( final long filePointer, 798 final int length ) 799 { 800 final long startingBlock = this.getFilePointerBlock( filePointer ); 801 final long endingBlock = 802 this.getFilePointerBlock( filePointer + length ); 803 804 assert endingBlock - startingBlock <= Integer.MAX_VALUE : 805 "Unexpected implementation limit reached."; 806 807 final Node[] nodes = 808 new Node[ (int) ( endingBlock - startingBlock + 1L ) ]; 809 810 if ( startingBlock == endingBlock ) 811 { 812 nodes[0] = this.getCacheNode( startingBlock ); 813 } 814 else 815 { 816 int i; 817 long block; 818 819 for ( block = startingBlock, i = 0; block <= endingBlock; 820 block++, i++ ) 821 { 822 nodes[i] = this.getCacheNode( block ); 823 } 824 } 825 826 return nodes; 827 } 828 829 /** 830 * Fills the cache for a given set of cache nodes. 831 * <p>This method ensures that each given node gets associated with 832 * corresponding cache memory possibly flushing the cache before 833 * reading.</p> 834 * 835 * @param nodes the nodes to fill the cache for. 836 * 837 * @throws NullPointerException if {@code nodes} is {@code null}. 838 * @throws IOException if reading fails. 839 */ 840 private void fillCache( final Node[] nodes ) throws IOException 841 { 842 if ( nodes == null ) 843 { 844 throw new NullPointerException( "nodes" ); 845 } 846 847 // Calculate the amount of bytes needed to be available in the cache 848 // and flush the cache if nodes would not fit. 849 long neededBytes = 0L; 850 for ( int i = nodes.length - 1; i >= 0; i-- ) 851 { 852 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX ) 853 { // Node's block needs to be read. 854 neededBytes += this.getBlockSize(); 855 } 856 } 857 858 if ( this.nextCacheIndex + neededBytes > this.getCache().length ) 859 { // Cache cannot hold the needed blocks so needs flushing. 860 this.flush(); 861 } 862 863 // Associate each node with cache memory for nodes not already 864 // associated with cache memory and fill these nodes' cache memory. 865 for ( int i = nodes.length - 1; i >= 0; i-- ) 866 { 867 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX && 868 this.nextCacheIndex < this.getCache().length ) 869 { // Node is not associated with any cache memory and can be read. 870 871 // Update the length field of the node for the block checking 872 // for a possible end of file condition. 873 final long pos = nodes[i].block * this.getBlockSize(); 874 if ( pos > this.getLength() ) 875 { // Node is behind the end of the file. 876 nodes[i].length = FileOperations.EOF; 877 continue; 878 } 879 else if ( pos + this.getBlockSize() > this.getLength() ) 880 { 881 final long delta = this.getLength() - pos; 882 883 assert delta <= Integer.MAX_VALUE : 884 "Unexpected implementation limit reached."; 885 886 nodes[i].length = (int) delta; 887 } 888 else 889 { 890 nodes[i].length = this.getBlockSize(); 891 } 892 893 // Associated the node with cache memory. 894 nodes[i].cacheIndex = this.nextCacheIndex; 895 this.nextCacheIndex += this.getBlockSize(); 896 897 // Read the node's block into cache. 898 int read = FileOperations.EOF; 899 int totalRead = 0; 900 int toRead = nodes[i].length; 901 this.getFileOperations().setFilePointer( pos ); 902 903 do 904 { 905 read = this.getFileOperations().read( 906 this.getCache(), nodes[i].cacheIndex + totalRead, 907 toRead ); 908 909 assert read != FileOperations.EOF : 910 "Unexpected end of file."; 911 912 totalRead += read; 913 toRead -= read; 914 915 } 916 while ( totalRead < nodes[i].length ); 917 } 918 } 919 } 920 921 /** Defragments the cache. */ 922 private void defragmentCache() 923 { 924 int defragIndex = 0; 925 926 // Step through the cached blocks and defragment the cache. 927 for ( final Iterator it = this.root.entrySet().iterator(); 928 it.hasNext(); ) 929 { 930 final Map.Entry entry = (Map.Entry) it.next(); 931 final long block = ( (Long) entry.getKey() ).longValue(); 932 final Node current = (Node) entry.getValue(); 933 934 // Skip any end of file nodes and nodes not associated with memory. 935 if ( current.length == FileOperations.EOF || 936 current.cacheIndex == Node.NO_CACHEINDEX ) 937 { 938 continue; 939 } 940 941 assert current.block == block : "Unexpected cache state."; 942 943 System.arraycopy( this.getCache(), current.cacheIndex, 944 this.getDefragCache(), defragIndex, 945 this.getBlockSize() ); 946 947 current.cacheIndex = defragIndex; 948 defragIndex += this.getBlockSize(); 949 } 950 951 System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0, 952 this.getCache().length ); 953 954 } 955 956 /** 957 * Gets the cache node for a given block. 958 * 959 * @param block the block to return the corresponding cache node for. 960 * 961 * @return the cache node for {@code block}. 962 */ 963 private Node getCacheNode( final long block ) 964 { 965 final Long key = new Long( block ); 966 Node node = (Node) this.root.get( key ); 967 if ( node == null ) 968 { 969 node = new Node(); 970 node.block = block; 971 this.root.put( key, node ); 972 } 973 974 return node; 975 } 976 977 /** 978 * Checks that the instance is not closed. 979 * 980 * @throws IOException if the instance is closed. 981 */ 982 private void assertNotClosed() throws IOException 983 { 984 if ( this.closed ) 985 { 986 throw new IOException( this.getAlreadyClosedMessage( 987 this.getLocale() ) ); 988 989 } 990 } 991 992 //------------------------------------------------CoalescingFileOperations-- 993 //--Messages---------------------------------------------------------------- 994 995// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 996 // This section is managed by jdtaus-container-mojo. 997 998 /** 999 * Gets the text of message <code>readBypassesCache</code>. 1000 * <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> 1001 * <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> 1002 * 1003 * @param locale The locale of the message instance to return. 1004 * @param blockSize The current block size in use. 1005 * @param cacheBlocks The current number of blocks in use. 1006 * @param uncachedBytes The number of bytes bypassing caching. 1007 * 1008 * @return Information about a misconfigured cache. 1009 */ 1010 private String getReadBypassesCacheMessage( final Locale locale, 1011 final java.lang.Number blockSize, 1012 final java.lang.Number cacheBlocks, 1013 final java.lang.Number uncachedBytes ) 1014 { 1015 return ContainerFactory.getContainer(). 1016 getMessage( this, "readBypassesCache", locale, 1017 new Object[] 1018 { 1019 blockSize, 1020 cacheBlocks, 1021 uncachedBytes 1022 }); 1023 1024 } 1025 1026 /** 1027 * Gets the text of message <code>writeBypassesCache</code>. 1028 * <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> 1029 * <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> 1030 * 1031 * @param locale The locale of the message instance to return. 1032 * @param blockSize The current block size in use. 1033 * @param cacheBlocks The current number of blocks in use. 1034 * @param uncachedBytes The number of bytes bypassing caching. 1035 * 1036 * @return Information about a misconfigured cache. 1037 */ 1038 private String getWriteBypassesCacheMessage( final Locale locale, 1039 final java.lang.Number blockSize, 1040 final java.lang.Number cacheBlocks, 1041 final java.lang.Number uncachedBytes ) 1042 { 1043 return ContainerFactory.getContainer(). 1044 getMessage( this, "writeBypassesCache", locale, 1045 new Object[] 1046 { 1047 blockSize, 1048 cacheBlocks, 1049 uncachedBytes 1050 }); 1051 1052 } 1053 1054 /** 1055 * Gets the text of message <code>alreadyClosed</code>. 1056 * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote> 1057 * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote> 1058 * 1059 * @param locale The locale of the message instance to return. 1060 * 1061 * @return Message stating that an instance is already closed. 1062 */ 1063 private String getAlreadyClosedMessage( final Locale locale ) 1064 { 1065 return ContainerFactory.getContainer(). 1066 getMessage( this, "alreadyClosed", locale, null ); 1067 1068 } 1069 1070// </editor-fold>//GEN-END:jdtausMessages 1071 1072 //----------------------------------------------------------------Messages-- 1073}