001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.mavibot.btree; 021 022 023import java.io.Closeable; 024import java.io.IOException; 025import java.nio.ByteBuffer; 026import java.nio.channels.FileChannel; 027import java.util.concurrent.ConcurrentLinkedQueue; 028 029import org.apache.commons.collections.map.LRUMap; 030import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034 035/** 036 * The B+Tree MVCC data structure. 037 * 038 * @param <K> The type for the keys 039 * @param <V> The type for the stored values 040 * 041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 042 */ 043public class PersistedBTree<K, V> extends AbstractBTree<K, V> implements Closeable 044{ 045 /** The LoggerFactory used by this class */ 046 protected static final Logger LOG = LoggerFactory.getLogger( PersistedBTree.class ); 047 048 protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" ); 049 050 /** The cache associated with this B-tree */ 051 protected LRUMap cache; 052 053 /** The default number of pages to keep in memory */ 054 public static final int DEFAULT_CACHE_SIZE = 1000; 055 056 /** The cache size, default to 1000 elements */ 057 protected int cacheSize = DEFAULT_CACHE_SIZE; 058 059 /** The number of stored Values before we switch to a B-tree */ 060 private static final int DEFAULT_VALUE_THRESHOLD_UP = 8; 061 062 /** The number of stored Values before we switch back to an array */ 063 private static final int DEFAULT_VALUE_THRESHOLD_LOW = 1; 064 065 /** The configuration for the array <-> B-tree switch */ 066 /*No qualifier*/static int valueThresholdUp = DEFAULT_VALUE_THRESHOLD_UP; 067 /*No qualifier*/static int valueThresholdLow = DEFAULT_VALUE_THRESHOLD_LOW; 068 069 /** The BtreeInfo offset */ 070 private long btreeInfoOffset = RecordManager.NO_PAGE; 071 072 /** The internal recordManager */ 073 private RecordManager recordManager; 074 075 076 /** 077 * Creates a new BTree, with no initialization. 078 */ 079 /* no qualifier */PersistedBTree() 080 { 081 setType( BTreeTypeEnum.PERSISTED ); 082 } 083 084 085 /** 086 * Creates a new persisted B-tree using the BTreeConfiguration to initialize the 087 * BTree 088 * 089 * @param configuration The configuration to use 090 */ 091 /* no qualifier */PersistedBTree( PersistedBTreeConfiguration<K, V> configuration ) 092 { 093 super(); 094 String name = configuration.getName(); 095 096 if ( name == null ) 097 { 098 throw new IllegalArgumentException( "BTree name cannot be null" ); 099 } 100 101 setName( name ); 102 setPageSize( configuration.getPageSize() ); 103 setKeySerializer( configuration.getKeySerializer() ); 104 setValueSerializer( configuration.getValueSerializer() ); 105 setAllowDuplicates( configuration.isAllowDuplicates() ); 106 setType( configuration.getBtreeType() ); 107 108 readTimeOut = configuration.getReadTimeOut(); 109 writeBufferSize = configuration.getWriteBufferSize(); 110 cacheSize = configuration.getCacheSize(); 111 112 if ( keySerializer.getComparator() == null ) 113 { 114 throw new IllegalArgumentException( "Comparator should not be null" ); 115 } 116 117 // Create the first root page, with revision 0L. It will be empty 118 // and increment the revision at the same time 119 Page<K, V> rootPage = new PersistedLeaf<K, V>( this ); 120 121 // Create a B-tree header, and initialize it 122 BTreeHeader<K, V> btreeHeader = new BTreeHeader<K, V>(); 123 btreeHeader.setRootPage( rootPage ); 124 btreeHeader.setBtree( this ); 125 126 switch ( btreeType ) 127 { 128 case BTREE_OF_BTREES: 129 case COPIED_PAGES_BTREE: 130 // We will create a new cache and a new readTransactions map 131 init( null ); 132 currentBtreeHeader = btreeHeader; 133 break; 134 135 case PERSISTED_SUB: 136 init( ( PersistedBTree<K, V> ) configuration.getParentBTree() ); 137 btreeRevisions.put( 0L, btreeHeader ); 138 currentBtreeHeader = btreeHeader; 139 break; 140 141 default: 142 // We will create a new cache and a new readTransactions map 143 init( null ); 144 btreeRevisions.put( 0L, btreeHeader ); 145 currentBtreeHeader = btreeHeader; 146 break; 147 } 148 } 149 150 151 /** 152 * Initialize the BTree. 153 * 154 * @throws IOException If we get some exception while initializing the BTree 155 */ 156 public void init( BTree<K, V> parentBTree ) 157 { 158 if ( parentBTree == null ) 159 { 160 // This is not a subBtree, we have to initialize the cache 161 162 // Create the queue containing the pending read transactions 163 readTransactions = new ConcurrentLinkedQueue<ReadTransaction<K, V>>(); 164 165 if ( cacheSize < 1 ) 166 { 167 cacheSize = DEFAULT_CACHE_SIZE; 168 } 169 170 cache = new LRUMap( cacheSize ); 171 } 172 else 173 { 174 this.cache = ( ( PersistedBTree<K, V> ) parentBTree ).getCache(); 175 this.readTransactions = ( ( PersistedBTree<K, V> ) parentBTree ).getReadTransactions(); 176 } 177 178 // Initialize the txnManager thread 179 //FIXME we should NOT create a new transaction manager thread for each BTree 180 //createTransactionManager(); 181 } 182 183 184 /** 185 * Return the cache we use in this BTree 186 */ 187 /* No qualifier */LRUMap getCache() 188 { 189 return cache; 190 } 191 192 193 /** 194 * Return the cache we use in this BTree 195 */ 196 /* No qualifier */ConcurrentLinkedQueue<ReadTransaction<K, V>> getReadTransactions() 197 { 198 return readTransactions; 199 } 200 201 202 /** 203 * Close the BTree, cleaning up all the data structure 204 */ 205 public void close() throws IOException 206 { 207 // Stop the readTransaction thread 208 // readTransactionsThread.interrupt(); 209 // readTransactions.clear(); 210 211 // Clean the cache 212 cache.clear(); 213 } 214 215 216 /** 217 * @return the btreeOffset 218 */ 219 /* No qualifier*/long getBtreeOffset() 220 { 221 return getBTreeHeader( getName() ).getBTreeHeaderOffset(); 222 } 223 224 225 /** 226 * @param btreeOffset the B-tree header Offset to set 227 */ 228 /* No qualifier*/void setBtreeHeaderOffset( long btreeHeaderOffset ) 229 { 230 getBTreeHeader( getName() ).setBTreeHeaderOffset( btreeHeaderOffset ); 231 } 232 233 234 /** 235 * @return the rootPageOffset 236 */ 237 /* No qualifier*/long getRootPageOffset() 238 { 239 return getBTreeHeader( getName() ).getRootPageOffset(); 240 } 241 242 243 /** 244 * Gets the RecordManager for a managed BTree 245 * 246 * @return The recordManager if the B-tree is managed 247 */ 248 /* No qualifier */RecordManager getRecordManager() 249 { 250 return recordManager; 251 } 252 253 254 /** 255 * Inject a RecordManager for a managed BTree 256 * 257 * @param recordManager The injected RecordManager 258 */ 259 /* No qualifier */void setRecordManager( RecordManager recordManager ) 260 { 261 // The RecordManager is also the TransactionManager 262 transactionManager = recordManager; 263 this.recordManager = recordManager; 264 } 265 266 267 /** 268 * 269 * Deletes the given <key,value> pair if both key and value match. If the given value is null 270 * and there is no null value associated with the given key then the entry with the given key 271 * will be removed. 272 * 273 * @param key The key to be removed 274 * @param value The value to be removed (can be null, and when no null value exists the key will be removed irrespective of the value) 275 * @param revision The revision to be associated with this operation 276 * @return 277 * @throws IOException 278 */ 279 /* no qualifier */Tuple<K, V> delete( K key, V value, long revision ) throws IOException 280 { 281 // We have to start a new transaction, which will be committed or rollbacked 282 // locally. This will duplicate the current BtreeHeader during this phase. 283 if ( revision == -1L ) 284 { 285 revision = currentRevision.get() + 1; 286 } 287 288 try 289 { 290 // Try to delete the entry starting from the root page. Here, the root 291 // page may be either a Node or a Leaf 292 DeleteResult<K, V> result = processDelete( key, value, revision ); 293 294 // Check that we have found the element to delete 295 if ( result instanceof NotPresentResult ) 296 { 297 // We haven't found the element in the B-tree, just get out 298 // without updating the recordManager 299 300 return null; 301 } 302 303 // The element was found, and removed 304 AbstractDeleteResult<K, V> deleteResult = ( AbstractDeleteResult<K, V> ) result; 305 306 Tuple<K, V> tuple = deleteResult.getRemovedElement(); 307 308 // If the B-tree is managed, we have to update the rootPage on disk 309 // Update the RecordManager header 310 311 // Return the value we have found if it was modified 312 return tuple; 313 } 314 catch ( IOException ioe ) 315 { 316 // if we've got an error, we have to rollback 317 throw ioe; 318 } 319 } 320 321 322 /** 323 * Insert the tuple into the B-tree rootPage, get back the new rootPage 324 */ 325 private DeleteResult<K, V> processDelete( K key, V value, long revision ) throws IOException 326 { 327 // Get the current B-tree header, and delete the value from it 328 BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); 329 330 // Try to delete the entry starting from the root page. Here, the root 331 // page may be either a Node or a Leaf 332 DeleteResult<K, V> result = btreeHeader.getRootPage().delete( key, value, revision ); 333 334 if ( result instanceof NotPresentResult ) 335 { 336 // Key not found. 337 return result; 338 } 339 340 // Create a new BTreeHeader 341 BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy(); 342 343 // Inject the old B-tree header into the pages to be freed 344 // if we are deleting an element from a management BTree 345 if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) 346 { 347 PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); 348 349 for ( PageIO pageIo : pageIos ) 350 { 351 recordManager.freedPages.add( pageIo ); 352 } 353 } 354 355 // The element was found, and removed 356 AbstractDeleteResult<K, V> removeResult = ( AbstractDeleteResult<K, V> ) result; 357 358 // This is a new root 359 Page<K, V> newRootPage = removeResult.getModifiedPage(); 360 361 // Write the modified page on disk 362 // Note that we don't use the holder, the new root page will 363 // remain in memory. 364 writePage( newRootPage, revision ); 365 366 // Decrease the number of elements in the current tree 367 newBtreeHeader.decrementNbElems(); 368 newBtreeHeader.setRootPage( newRootPage ); 369 newBtreeHeader.setRevision( revision ); 370 371 // Write down the data on disk 372 long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); 373 374 // Update the B-tree of B-trees with this new offset, if we are not already doing so 375 switch ( btreeType ) 376 { 377 case PERSISTED: 378 // We have a new B-tree header to inject into the B-tree of btrees 379 recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); 380 381 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); 382 383 // Store the new revision 384 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 385 386 break; 387 388 case PERSISTED_SUB: 389 // Sub-B-trees are only updating the CopiedPage B-tree 390 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); 391 392 //btreeRevisions.put( revision, newBtreeHeader ); 393 394 currentRevision.set( revision ); 395 396 break; 397 398 case BTREE_OF_BTREES: 399 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters 400 recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); 401 402 // We can free the copied pages 403 recordManager.freePages( this, revision, result.getCopiedPages() ); 404 405 // Store the new revision 406 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 407 408 break; 409 410 case COPIED_PAGES_BTREE: 411 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters 412 recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset ); 413 414 // We can free the copied pages 415 recordManager.freePages( this, revision, result.getCopiedPages() ); 416 417 // Store the new revision 418 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 419 420 break; 421 422 default: 423 // Nothing to do for sub-btrees 424 break; 425 } 426 427 // Return the value we have found if it was modified 428 return result; 429 } 430 431 432 /** 433 * Insert an entry in the BTree. 434 * <p> 435 * We will replace the value if the provided key already exists in the 436 * btree. 437 * <p> 438 * The revision number is the revision to use to insert the data. 439 * 440 * @param key Inserted key 441 * @param value Inserted value 442 * @param revision The revision to use 443 * @return an instance of the InsertResult. 444 */ 445 /* no qualifier */InsertResult<K, V> insert( K key, V value, long revision ) throws IOException 446 { 447 // We have to start a new transaction, which will be committed or rollbacked 448 // locally. This will duplicate the current BtreeHeader during this phase. 449 if ( revision == -1L ) 450 { 451 revision = currentRevision.get() + 1; 452 } 453 454 try 455 { 456 // Try to insert the new value in the tree at the right place, 457 // starting from the root page. Here, the root page may be either 458 // a Node or a Leaf 459 InsertResult<K, V> result = processInsert( key, value, revision ); 460 461 // Return the value we have found if it was modified 462 return result; 463 } 464 catch ( IOException ioe ) 465 { 466 throw ioe; 467 } 468 } 469 470 471 private BTreeHeader<K, V> getBTreeHeader( String name ) 472 { 473 switch ( btreeType ) 474 { 475 case PERSISTED_SUB: 476 return getBtreeHeader(); 477 478 case BTREE_OF_BTREES: 479 return recordManager.getNewBTreeHeader( RecordManager.BTREE_OF_BTREES_NAME ); 480 481 case COPIED_PAGES_BTREE: 482 return recordManager.getNewBTreeHeader( RecordManager.COPIED_PAGE_BTREE_NAME ); 483 484 default: 485 return recordManager.getBTreeHeader( name ); 486 } 487 } 488 489 490 private BTreeHeader<K, V> getNewBTreeHeader( String name ) 491 { 492 if ( btreeType == BTreeTypeEnum.PERSISTED_SUB ) 493 { 494 return getBtreeHeader(); 495 } 496 497 BTreeHeader<K, V> btreeHeader = recordManager.getNewBTreeHeader( getName() ); 498 499 return btreeHeader; 500 } 501 502 503 /** 504 * Insert the tuple into the B-tree rootPage, get back the new rootPage 505 */ 506 private InsertResult<K, V> processInsert( K key, V value, long revision ) throws IOException 507 { 508 // Get the current B-tree header, and insert the value into it 509 BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); 510 InsertResult<K, V> result = btreeHeader.getRootPage().insert( key, value, revision ); 511 512 if ( result instanceof ExistsResult ) 513 { 514 return result; 515 } 516 517 // Create a new BTreeHeader 518 BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy(); 519 520 // Inject the old B-tree header into the pages to be freed 521 // if we are inserting an element in a management BTree 522 if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) 523 { 524 PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); 525 526 for ( PageIO pageIo : pageIos ) 527 { 528 recordManager.freedPages.add( pageIo ); 529 } 530 } 531 532 Page<K, V> newRootPage; 533 534 if ( result instanceof ModifyResult ) 535 { 536 ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result ); 537 538 newRootPage = modifyResult.getModifiedPage(); 539 540 // Increment the counter if we have inserted a new value 541 if ( modifyResult.getModifiedValue() == null ) 542 { 543 newBtreeHeader.incrementNbElems(); 544 } 545 } 546 else 547 { 548 // We have split the old root, create a new one containing 549 // only the pivotal we got back 550 SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result ); 551 552 K pivot = splitResult.getPivot(); 553 Page<K, V> leftPage = splitResult.getLeftPage(); 554 Page<K, V> rightPage = splitResult.getRightPage(); 555 556 // If the B-tree is managed, we have to write the two pages that were created 557 // and to keep a track of the two offsets for the upper node 558 PageHolder<K, V> holderLeft = writePage( leftPage, revision ); 559 560 PageHolder<K, V> holderRight = writePage( rightPage, revision ); 561 562 // Create the new rootPage 563 newRootPage = new PersistedNode<K, V>( this, revision, pivot, holderLeft, holderRight ); 564 565 // Always increment the counter : we have added a new value 566 newBtreeHeader.incrementNbElems(); 567 } 568 569 // Write the new root page on disk 570 LOG_PAGES.debug( "Writing the new rootPage revision {} for {}", revision, name ); 571 writePage( newRootPage, revision ); 572 573 // Update the new B-tree header 574 newBtreeHeader.setRootPage( newRootPage ); 575 newBtreeHeader.setRevision( revision ); 576 577 // Write down the data on disk 578 long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); 579 580 // Update the B-tree of B-trees with this new offset, if we are not already doing so 581 switch ( btreeType ) 582 { 583 case PERSISTED: 584 // We have a new B-tree header to inject into the B-tree of btrees 585 recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); 586 587 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); 588 589 // Store the new revision 590 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 591 592 break; 593 594 case PERSISTED_SUB: 595 // Sub-B-trees are only updating the CopiedPage B-tree 596 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); 597 598 // Store the new revision 599 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 600 601 currentRevision.set( revision ); 602 603 break; 604 605 case BTREE_OF_BTREES: 606 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters 607 recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); 608 609 // We can free the copied pages 610 recordManager.freePages( this, revision, result.getCopiedPages() ); 611 612 // Store the new revision 613 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 614 615 break; 616 617 case COPIED_PAGES_BTREE: 618 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters 619 recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset ); 620 621 // We can free the copied pages 622 recordManager.freePages( this, revision, result.getCopiedPages() ); 623 624 // Store the new revision 625 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); 626 627 break; 628 629 default: 630 // Nothing to do for sub-btrees 631 break; 632 } 633 634 // Get the new root page and make it the current root page 635 return result; 636 } 637 638 639 /** 640 * Write the data in the ByteBuffer, and eventually on disk if needed. 641 * 642 * @param channel The channel we want to write to 643 * @param bb The ByteBuffer we want to feed 644 * @param buffer The data to inject 645 * @throws IOException If the write failed 646 */ 647 private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException 648 { 649 int size = buffer.length; 650 int pos = 0; 651 652 // Loop until we have written all the data 653 do 654 { 655 if ( bb.remaining() >= size ) 656 { 657 // No flush, as the ByteBuffer is big enough 658 bb.put( buffer, pos, size ); 659 size = 0; 660 } 661 else 662 { 663 // Flush the data on disk, reinitialize the ByteBuffer 664 int len = bb.remaining(); 665 size -= len; 666 bb.put( buffer, pos, len ); 667 pos += len; 668 669 bb.flip(); 670 671 channel.write( bb ); 672 673 bb.clear(); 674 } 675 } 676 while ( size > 0 ); 677 } 678 679 680 /** 681 * Write a page either in the pending pages if the transaction is started, 682 * or directly on disk. 683 */ 684 private PageHolder<K, V> writePage( Page<K, V> modifiedPage, long revision ) throws IOException 685 { 686 PageHolder<K, V> pageHolder = recordManager.writePage( this, modifiedPage, revision ); 687 688 return pageHolder; 689 } 690 691 692 /** 693 * Get the rootPzge associated to a give revision. 694 * 695 * @param revision The revision we are looking for 696 * @return The rootPage associated to this revision 697 * @throws IOException If we had an issue while accessing the underlying file 698 * @throws KeyNotFoundException If the revision does not exist for this Btree 699 */ 700 public Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException 701 { 702 return recordManager.getRootPage( this, revision ); 703 } 704 705 706 /** 707 * Get the current rootPage 708 * 709 * @return The rootPage 710 */ 711 public Page<K, V> getRootPage() 712 { 713 return getBTreeHeader( getName() ).getRootPage(); 714 } 715 716 717 /* no qualifier */void setRootPage( Page<K, V> root ) 718 { 719 getBTreeHeader( getName() ).setRootPage( root ); 720 } 721 722 723 /** 724 * @return the btreeInfoOffset 725 */ 726 public long getBtreeInfoOffset() 727 { 728 return btreeInfoOffset; 729 } 730 731 732 /** 733 * @param btreeInfoOffset the btreeInfoOffset to set 734 */ 735 public void setBtreeInfoOffset( long btreeInfoOffset ) 736 { 737 this.btreeInfoOffset = btreeInfoOffset; 738 } 739 740 741 /** 742 * {@inheritDoc} 743 */ 744 protected ReadTransaction<K, V> beginReadTransaction() 745 { 746 BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); 747 748 ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( recordManager, btreeHeader, readTransactions ); 749 750 readTransactions.add( readTransaction ); 751 752 return readTransaction; 753 } 754 755 756 /** 757 * {@inheritDoc} 758 */ 759 protected ReadTransaction<K, V> beginReadTransaction( long revision ) 760 { 761 BTreeHeader<K, V> btreeHeader = getBtreeHeader( revision ); 762 763 if ( btreeHeader != null ) 764 { 765 ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( recordManager, btreeHeader, 766 readTransactions ); 767 768 readTransactions.add( readTransaction ); 769 770 return readTransaction; 771 } 772 else 773 { 774 return null; 775 } 776 } 777 778 779 /** 780 * @see Object#toString() 781 */ 782 public String toString() 783 { 784 StringBuilder sb = new StringBuilder(); 785 786 sb.append( "Managed BTree" ); 787 sb.append( "[" ).append( getName() ).append( "]" ); 788 sb.append( "( pageSize:" ).append( getPageSize() ); 789 790 if ( getBTreeHeader( getName() ).getRootPage() != null ) 791 { 792 sb.append( ", nbEntries:" ).append( getBTreeHeader( getName() ).getNbElems() ); 793 } 794 else 795 { 796 sb.append( ", nbEntries:" ).append( 0 ); 797 } 798 799 sb.append( ", comparator:" ); 800 801 if ( keySerializer.getComparator() == null ) 802 { 803 sb.append( "null" ); 804 } 805 else 806 { 807 sb.append( keySerializer.getComparator().getClass().getSimpleName() ); 808 } 809 810 sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() ); 811 812 sb.append( ") : \n" ); 813 sb.append( getBTreeHeader( getName() ).getRootPage().dumpPage( "" ) ); 814 815 return sb.toString(); 816 } 817}