| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.mavibot.btree; |
| |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.FileChannel; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| |
| import org.apache.commons.collections.map.LRUMap; |
| import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * The B+Tree MVCC data structure. |
| * |
| * @param <K> The type for the keys |
| * @param <V> The type for the stored values |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class PersistedBTree<K, V> extends AbstractBTree<K, V> implements Closeable |
| { |
| /** The LoggerFactory used by this class */ |
| protected static final Logger LOG = LoggerFactory.getLogger( PersistedBTree.class ); |
| |
| protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" ); |
| |
| /** The cache associated with this B-tree */ |
| protected LRUMap cache; |
| |
| /** The default number of pages to keep in memory */ |
| public static final int DEFAULT_CACHE_SIZE = 1000; |
| |
| /** The cache size, default to 1000 elements */ |
| protected int cacheSize = DEFAULT_CACHE_SIZE; |
| |
| /** The number of stored Values before we switch to a B-tree */ |
| private static final int DEFAULT_VALUE_THRESHOLD_UP = 8; |
| |
| /** The number of stored Values before we switch back to an array */ |
| private static final int DEFAULT_VALUE_THRESHOLD_LOW = 1; |
| |
| /** The configuration for the array <-> B-tree switch */ |
| /*No qualifier*/static int valueThresholdUp = DEFAULT_VALUE_THRESHOLD_UP; |
| /*No qualifier*/static int valueThresholdLow = DEFAULT_VALUE_THRESHOLD_LOW; |
| |
| /** The BtreeInfo offset */ |
| private long btreeInfoOffset; |
| |
| /** The internal recordManager */ |
| private RecordManager recordManager; |
| |
| |
| /** |
| * Creates a new BTree, with no initialization. |
| */ |
| /* no qualifier */PersistedBTree() |
| { |
| setType( BTreeTypeEnum.PERSISTED ); |
| } |
| |
| |
| /** |
| * Creates a new persisted B-tree using the BTreeConfiguration to initialize the |
| * BTree |
| * |
| * @param configuration The configuration to use |
| */ |
| /* no qualifier */PersistedBTree( PersistedBTreeConfiguration<K, V> configuration ) |
| { |
| super(); |
| String name = configuration.getName(); |
| |
| if ( name == null ) |
| { |
| throw new IllegalArgumentException( "BTree name cannot be null" ); |
| } |
| |
| setName( name ); |
| setPageSize( configuration.getPageSize() ); |
| setKeySerializer( configuration.getKeySerializer() ); |
| setValueSerializer( configuration.getValueSerializer() ); |
| setAllowDuplicates( configuration.isAllowDuplicates() ); |
| setType( configuration.getBtreeType() ); |
| |
| readTimeOut = configuration.getReadTimeOut(); |
| writeBufferSize = configuration.getWriteBufferSize(); |
| cacheSize = configuration.getCacheSize(); |
| |
| if ( keySerializer.getComparator() == null ) |
| { |
| throw new IllegalArgumentException( "Comparator should not be null" ); |
| } |
| |
| // Create the first root page, with revision 0L. It will be empty |
| // and increment the revision at the same time |
| Page<K, V> rootPage = new PersistedLeaf<K, V>( this ); |
| |
| // Create a B-tree header, and initialize it |
| BTreeHeader<K, V> btreeHeader = new BTreeHeader<K, V>(); |
| btreeHeader.setRootPage( rootPage ); |
| btreeHeader.setBtree( this ); |
| |
| switch ( btreeType ) |
| { |
| case BTREE_OF_BTREES : |
| // We will create a new cache and a new readTransactions map |
| init( null ); |
| currentBtreeHeader = btreeHeader; |
| break; |
| |
| case PERSISTED_SUB: |
| init( ( PersistedBTree<K, V> ) configuration.getParentBTree() ); |
| btreeRevisions.put( 0L, btreeHeader ); |
| currentBtreeHeader = btreeHeader; |
| break; |
| |
| default: |
| // We will create a new cache and a new readTransactions map |
| init( null ); |
| btreeRevisions.put( 0L, btreeHeader ); |
| currentBtreeHeader = btreeHeader; |
| break; |
| } |
| } |
| |
| |
| /** |
| * Initialize the BTree. |
| * |
| * @throws IOException If we get some exception while initializing the BTree |
| */ |
| public void init( BTree<K, V> parentBTree ) |
| { |
| if ( parentBTree == null ) |
| { |
| // This is not a subBtree, we have to initialize the cache |
| |
| // Create the queue containing the pending read transactions |
| readTransactions = new ConcurrentLinkedQueue<ReadTransaction<K, V>>(); |
| |
| if ( cacheSize < 1 ) |
| { |
| cacheSize = 1; |
| } |
| |
| cache = new LRUMap( cacheSize ); |
| } |
| else |
| { |
| this.cache = ( ( PersistedBTree<K, V> ) parentBTree ).getCache(); |
| this.readTransactions = ( ( PersistedBTree<K, V> ) parentBTree ).getReadTransactions(); |
| } |
| |
| // Initialize the txnManager thread |
| //FIXME we should NOT create a new transaction manager thread for each BTree |
| //createTransactionManager(); |
| } |
| |
| |
| /** |
| * Return the cache we use in this BTree |
| */ |
| /* No qualifier */LRUMap getCache() |
| { |
| return cache; |
| } |
| |
| |
| /** |
| * Return the cache we use in this BTree |
| */ |
| /* No qualifier */ConcurrentLinkedQueue<ReadTransaction<K, V>> getReadTransactions() |
| { |
| return readTransactions; |
| } |
| |
| |
| /** |
| * Close the BTree, cleaning up all the data structure |
| */ |
| public void close() throws IOException |
| { |
| // Stop the readTransaction thread |
| // readTransactionsThread.interrupt(); |
| // readTransactions.clear(); |
| |
| // Clean the cache |
| cache.clear(); |
| } |
| |
| |
| /** |
| * @return the btreeOffset |
| */ |
| /* No qualifier*/long getBtreeOffset() |
| { |
| return getBTreeHeader( getName() ).getBTreeHeaderOffset(); |
| } |
| |
| |
| /** |
| * @param btreeOffset the B-tree header Offset to set |
| */ |
| /* No qualifier*/void setBtreeHeaderOffset( long btreeHeaderOffset ) |
| { |
| getBTreeHeader( getName() ).setBTreeHeaderOffset( btreeHeaderOffset ); |
| } |
| |
| |
| /** |
| * @return the rootPageOffset |
| */ |
| /* No qualifier*/long getRootPageOffset() |
| { |
| return getBTreeHeader( getName() ).getRootPageOffset(); |
| } |
| |
| |
| /** |
| * Gets the RecordManager for a managed BTree |
| * |
| * @return The recordManager if the B-tree is managed |
| */ |
| /* No qualifier */RecordManager getRecordManager() |
| { |
| return recordManager; |
| } |
| |
| |
| /** |
| * Inject a RecordManager for a managed BTree |
| * |
| * @param recordManager The injected RecordManager |
| */ |
| /* No qualifier */void setRecordManager( RecordManager recordManager ) |
| { |
| // The RecordManager is also the TransactionManager |
| transactionManager = recordManager; |
| this.recordManager = recordManager; |
| } |
| |
| |
| /** |
| * |
| * Deletes the given <key,value> pair if both key and value match. If the given value is null |
| * and there is no null value associated with the given key then the entry with the given key |
| * will be removed. |
| * |
| * @param key The key to be removed |
| * @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) |
| * @param revision The revision to be associated with this operation |
| * @return |
| * @throws IOException |
| */ |
| /* no qualifier */Tuple<K, V> delete( K key, V value, long revision ) throws IOException |
| { |
| // We have to start a new transaction, which will be committed or rollbacked |
| // locally. This will duplicate the current BtreeHeader during this phase. |
| if ( revision == -1L ) |
| { |
| revision = currentRevision.get() + 1; |
| } |
| |
| try |
| { |
| // Try to delete the entry starting from the root page. Here, the root |
| // page may be either a Node or a Leaf |
| DeleteResult<K, V> result = processDelete( key, value, revision ); |
| |
| // Check that we have found the element to delete |
| if ( result instanceof NotPresentResult ) |
| { |
| // We haven't found the element in the B-tree, just get out |
| // without updating the recordManager |
| |
| return null; |
| } |
| |
| // The element was found, and removed |
| AbstractDeleteResult<K, V> deleteResult = ( AbstractDeleteResult<K, V> ) result; |
| |
| Tuple<K, V> tuple = deleteResult.getRemovedElement(); |
| |
| // If the B-tree is managed, we have to update the rootPage on disk |
| // Update the RecordManager header |
| |
| // Return the value we have found if it was modified |
| return tuple; |
| } |
| catch ( IOException ioe ) |
| { |
| // if we've got an error, we have to rollback |
| throw ioe; |
| } |
| } |
| |
| |
| /** |
| * Insert the tuple into the B-tree rootPage, get back the new rootPage |
| */ |
| private DeleteResult<K, V> processDelete( K key, V value, long revision ) throws IOException |
| { |
| // Get the current B-tree header, and delete the value from it |
| BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); |
| |
| // Try to delete the entry starting from the root page. Here, the root |
| // page may be either a Node or a Leaf |
| DeleteResult<K, V> result = btreeHeader.getRootPage().delete( key, value, revision ); |
| |
| if ( result instanceof NotPresentResult ) |
| { |
| // Key not found. |
| return result; |
| } |
| |
| // Create a new BTreeHeader |
| BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy(); |
| |
| // Inject the old B-tree header into the pages to be freed |
| // if we are deleting an element from a management BTree |
| if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.PERSISTED_SUB ) ) |
| { |
| PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| recordManager.freedPages.add( pageIo ); |
| } |
| } |
| |
| // The element was found, and removed |
| AbstractDeleteResult<K, V> removeResult = ( AbstractDeleteResult<K, V> ) result; |
| |
| // This is a new root |
| Page<K, V> newRootPage = removeResult.getModifiedPage(); |
| |
| // Write the modified page on disk |
| // Note that we don't use the holder, the new root page will |
| // remain in memory. |
| PageHolder<K, V> holder = writePage( newRootPage, revision ); |
| |
| // Decrease the number of elements in the current tree |
| newBtreeHeader.decrementNbElems(); |
| newBtreeHeader.setRootPage( newRootPage ); |
| newBtreeHeader.setRevision( revision ); |
| |
| // Write down the data on disk |
| long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); |
| |
| // Update the B-tree of B-trees with this new offset, if we are not already doing so |
| switch ( btreeType ) |
| { |
| case PERSISTED: |
| // We have a new B-tree header to inject into the B-tree of btrees |
| recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); |
| |
| recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); |
| |
| // Store the new revision |
| storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); |
| |
| break; |
| |
| case PERSISTED_SUB: |
| // Sub-B-trees are only updating the CopiedPage B-tree |
| recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); |
| |
| //btreeRevisions.put( revision, newBtreeHeader ); |
| |
| currentRevision.set( revision ); |
| |
| break; |
| |
| case BTREE_OF_BTREES: |
| // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters |
| recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); |
| |
| // We can free the copied pages |
| recordManager.freePages( this, revision, result.getCopiedPages() ); |
| |
| // Store the new revision |
| storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); |
| |
| break; |
| |
| default: |
| // Nothing to do for sub-btrees |
| break; |
| } |
| |
| // Return the value we have found if it was modified |
| return result; |
| } |
| |
| |
| /** |
| * Insert an entry in the BTree. |
| * <p> |
| * We will replace the value if the provided key already exists in the |
| * btree. |
| * <p> |
| * The revision number is the revision to use to insert the data. |
| * |
| * @param key Inserted key |
| * @param value Inserted value |
| * @param revision The revision to use |
| * @return an instance of the InsertResult. |
| */ |
| /* no qualifier */InsertResult<K, V> insert( K key, V value, long revision ) throws IOException |
| { |
| // We have to start a new transaction, which will be committed or rollbacked |
| // locally. This will duplicate the current BtreeHeader during this phase. |
| if ( revision == -1L ) |
| { |
| revision = currentRevision.get() + 1; |
| } |
| |
| try |
| { |
| // Try to insert the new value in the tree at the right place, |
| // starting from the root page. Here, the root page may be either |
| // a Node or a Leaf |
| InsertResult<K, V> result = processInsert( key, value, revision ); |
| |
| // Return the value we have found if it was modified |
| return result; |
| } |
| catch ( IOException ioe ) |
| { |
| throw ioe; |
| } |
| } |
| |
| |
| private BTreeHeader<K, V> getBTreeHeader( String name ) |
| { |
| switch ( btreeType ) |
| { |
| case PERSISTED_SUB : |
| return getBtreeHeader(); |
| |
| case BTREE_OF_BTREES : |
| return recordManager.getNewBTreeHeader( RecordManager.BTREE_OF_BTREES_NAME ); |
| |
| default : |
| return recordManager.getBTreeHeader( getName() ); |
| } |
| } |
| |
| |
| private BTreeHeader<K, V> getNewBTreeHeader( String name ) |
| { |
| if ( btreeType == BTreeTypeEnum.PERSISTED_SUB ) |
| { |
| return getBtreeHeader(); |
| } |
| |
| BTreeHeader<K, V> btreeHeader = recordManager.getNewBTreeHeader( getName() ); |
| |
| return btreeHeader; |
| } |
| |
| |
| /** |
| * Insert the tuple into the B-tree rootPage, get back the new rootPage |
| */ |
| private InsertResult<K, V> processInsert( K key, V value, long revision ) throws IOException |
| { |
| // Get the current B-tree header, and insert the value into it |
| BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); |
| InsertResult<K, V> result = btreeHeader.getRootPage().insert( key, value, revision ); |
| |
| if ( result instanceof ExistsResult ) |
| { |
| return result; |
| } |
| |
| // Create a new BTreeHeader |
| BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy(); |
| |
| // Inject the old B-tree header into the pages to be freed |
| // if we are inserting an element in a management BTree |
| if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.PERSISTED_SUB ) ) |
| { |
| PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L ); |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| recordManager.freedPages.add( pageIo ); |
| } |
| } |
| |
| Page<K, V> newRootPage; |
| |
| if ( result instanceof ModifyResult ) |
| { |
| ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result ); |
| |
| newRootPage = modifyResult.getModifiedPage(); |
| |
| // Increment the counter if we have inserted a new value |
| if ( modifyResult.getModifiedValue() == null ) |
| { |
| newBtreeHeader.incrementNbElems(); |
| } |
| } |
| else |
| { |
| // We have split the old root, create a new one containing |
| // only the pivotal we got back |
| SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result ); |
| |
| K pivot = splitResult.getPivot(); |
| Page<K, V> leftPage = splitResult.getLeftPage(); |
| Page<K, V> rightPage = splitResult.getRightPage(); |
| |
| // If the B-tree is managed, we have to write the two pages that were created |
| // and to keep a track of the two offsets for the upper node |
| PageHolder<K, V> holderLeft = writePage( leftPage, revision ); |
| |
| PageHolder<K, V> holderRight = writePage( rightPage, revision ); |
| |
| // Create the new rootPage |
| newRootPage = new PersistedNode<K, V>( this, revision, pivot, holderLeft, holderRight ); |
| |
| // Always increment the counter : we have added a new value |
| newBtreeHeader.incrementNbElems(); |
| } |
| |
| // Write the new root page on disk |
| LOG_PAGES.debug( "Writing the new rootPage revision {} for {}", revision, name ); |
| writePage( newRootPage, revision ); |
| |
| // Update the new B-tree header |
| newBtreeHeader.setRootPage( newRootPage ); |
| newBtreeHeader.setRevision( revision ); |
| |
| // Write down the data on disk |
| long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader ); |
| |
| // Update the B-tree of B-trees with this new offset, if we are not already doing so |
| switch ( btreeType ) |
| { |
| case PERSISTED: |
| // We have a new B-tree header to inject into the B-tree of btrees |
| recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset ); |
| |
| recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); |
| |
| // Store the new revision |
| storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); |
| |
| break; |
| |
| case PERSISTED_SUB: |
| // Sub-B-trees are only updating the CopiedPage B-tree |
| recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() ); |
| |
| // Store the new revision |
| storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); |
| |
| currentRevision.set( revision ); |
| |
| break; |
| |
| case BTREE_OF_BTREES: |
| // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters |
| recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L ); |
| |
| // We can free the copied pages |
| recordManager.freePages( this, revision, result.getCopiedPages() ); |
| |
| // Store the new revision |
| storeRevision( newBtreeHeader, recordManager.isKeepRevisions() ); |
| |
| break; |
| |
| default: |
| // Nothing to do for sub-btrees |
| break; |
| } |
| |
| // Get the new root page and make it the current root page |
| return result; |
| } |
| |
| |
| /** |
| * Write the data in the ByteBuffer, and eventually on disk if needed. |
| * |
| * @param channel The channel we want to write to |
| * @param bb The ByteBuffer we want to feed |
| * @param buffer The data to inject |
| * @throws IOException If the write failed |
| */ |
| private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException |
| { |
| int size = buffer.length; |
| int pos = 0; |
| |
| // Loop until we have written all the data |
| do |
| { |
| if ( bb.remaining() >= size ) |
| { |
| // No flush, as the ByteBuffer is big enough |
| bb.put( buffer, pos, size ); |
| size = 0; |
| } |
| else |
| { |
| // Flush the data on disk, reinitialize the ByteBuffer |
| int len = bb.remaining(); |
| size -= len; |
| bb.put( buffer, pos, len ); |
| pos += len; |
| |
| bb.flip(); |
| |
| channel.write( bb ); |
| |
| bb.clear(); |
| } |
| } |
| while ( size > 0 ); |
| } |
| |
| |
| /** |
| * Write a page either in the pending pages if the transaction is started, |
| * or directly on disk. |
| */ |
| private PageHolder<K, V> writePage( Page<K, V> modifiedPage, long revision ) throws IOException |
| { |
| PageHolder<K, V> pageHolder = recordManager.writePage( this, modifiedPage, revision ); |
| |
| return pageHolder; |
| } |
| |
| |
| /** |
| * Get the rootPzge associated to a give revision. |
| * |
| * @param revision The revision we are looking for |
| * @return The rootPage associated to this revision |
| * @throws IOException If we had an issue while accessing the underlying file |
| * @throws KeyNotFoundException If the revision does not exist for this Btree |
| */ |
| public Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException |
| { |
| return recordManager.getRootPage( this, revision ); |
| } |
| |
| |
| /** |
| * Get the current rootPage |
| * |
| * @return The rootPage |
| */ |
| public Page<K, V> getRootPage() |
| { |
| return getBTreeHeader( getName() ).getRootPage(); |
| } |
| |
| |
| /* no qualifier */void setRootPage( Page<K, V> root ) |
| { |
| getBTreeHeader( getName() ).setRootPage( root ); |
| } |
| |
| |
| /** |
| * @return the btreeInfoOffset |
| */ |
| public long getBtreeInfoOffset() |
| { |
| return btreeInfoOffset; |
| } |
| |
| |
| /** |
| * @param btreeInfoOffset the btreeInfoOffset to set |
| */ |
| public void setBtreeInfoOffset( long btreeInfoOffset ) |
| { |
| this.btreeInfoOffset = btreeInfoOffset; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected ReadTransaction<K, V> beginReadTransaction() |
| { |
| BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() ); |
| |
| ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( recordManager, btreeHeader, readTransactions ); |
| |
| readTransactions.add( readTransaction ); |
| |
| return readTransaction; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected ReadTransaction<K, V> beginReadTransaction( long revision ) |
| { |
| BTreeHeader<K, V> btreeHeader = getBtreeHeader( revision ); |
| |
| if ( btreeHeader != null ) |
| { |
| ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( recordManager, btreeHeader, |
| readTransactions ); |
| |
| readTransactions.add( readTransaction ); |
| |
| return readTransaction; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "Managed BTree" ); |
| sb.append( "[" ).append( getName() ).append( "]" ); |
| sb.append( "( pageSize:" ).append( getPageSize() ); |
| |
| if ( getBTreeHeader( getName() ).getRootPage() != null ) |
| { |
| sb.append( ", nbEntries:" ).append( getBTreeHeader( getName() ).getNbElems() ); |
| } |
| else |
| { |
| sb.append( ", nbEntries:" ).append( 0 ); |
| } |
| |
| sb.append( ", comparator:" ); |
| |
| if ( keySerializer.getComparator() == null ) |
| { |
| sb.append( "null" ); |
| } |
| else |
| { |
| sb.append( keySerializer.getComparator().getClass().getSimpleName() ); |
| } |
| |
| sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() ); |
| |
| sb.append( ") : \n" ); |
| sb.append( getBTreeHeader( getName() ).getRootPage().dumpPage( "" ) ); |
| |
| return sb.toString(); |
| } |
| } |