View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.mavibot.btree;
21  
22  
23  import java.io.Closeable;
24  import java.io.IOException;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.FileChannel;
27  import java.util.concurrent.ConcurrentLinkedQueue;
28  
29  import org.apache.commons.collections.map.LRUMap;
30  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  
35  /**
36   * The B+Tree MVCC data structure.
37   *
38   * @param <K> The type for the keys
39   * @param <V> The type for the stored values
40   *
41   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
42   */
43  public class PersistedBTree<K, V> extends AbstractBTree<K, V> implements Closeable
44  {
45      /** The LoggerFactory used by this class */
46      protected static final Logger LOG = LoggerFactory.getLogger( PersistedBTree.class );
47  
48      protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" );
49  
50      /** The cache associated with this B-tree */
51      protected LRUMap cache;
52  
53      /** The default number of pages to keep in memory */
54      public static final int DEFAULT_CACHE_SIZE = 1000;
55  
56      /** The cache size, default to 1000 elements */
57      protected int cacheSize = DEFAULT_CACHE_SIZE;
58  
59      /** The number of stored Values before we switch to a B-tree */
60      private static final int DEFAULT_VALUE_THRESHOLD_UP = 8;
61  
62      /** The number of stored Values before we switch back to an array */
63      private static final int DEFAULT_VALUE_THRESHOLD_LOW = 1;
64  
65      /** The configuration for the array <-> B-tree switch */
66      /*No qualifier*/static int valueThresholdUp = DEFAULT_VALUE_THRESHOLD_UP;
67      /*No qualifier*/static int valueThresholdLow = DEFAULT_VALUE_THRESHOLD_LOW;
68  
69      /** The BtreeInfo offset */
70      private long btreeInfoOffset;
71      
72      /** The internal recordManager */
73      private RecordManager recordManager;
74  
75      /**
76       * Creates a new BTree, with no initialization.
77       */
78      /* no qualifier */PersistedBTree()
79      {
80          setType( BTreeTypeEnum.PERSISTED );
81      }
82  
83  
84      /**
85       * Creates a new persisted B-tree using the BTreeConfiguration to initialize the
86       * BTree
87       *
88       * @param configuration The configuration to use
89       */
90      /* no qualifier */PersistedBTree( PersistedBTreeConfiguration<K, V> configuration )
91      {
92          super();
93          String name = configuration.getName();
94  
95          if ( name == null )
96          {
97              throw new IllegalArgumentException( "BTree name cannot be null" );
98          }
99  
100         setName( name );
101         setPageSize( configuration.getPageSize() );
102         setKeySerializer( configuration.getKeySerializer() );
103         setValueSerializer( configuration.getValueSerializer() );
104         setAllowDuplicates( configuration.isAllowDuplicates() );
105         setType( configuration.getBtreeType() );
106 
107         readTimeOut = configuration.getReadTimeOut();
108         writeBufferSize = configuration.getWriteBufferSize();
109         cacheSize = configuration.getCacheSize();
110 
111         if ( keySerializer.getComparator() == null )
112         {
113             throw new IllegalArgumentException( "Comparator should not be null" );
114         }
115 
116         // Create the first root page, with revision 0L. It will be empty
117         // and increment the revision at the same time
118         Page<K, V> rootPage = new PersistedLeaf<K, V>( this );
119 
120         // Create a B-tree header, and initialize it
121         BTreeHeader<K, V> btreeHeader = new BTreeHeader<K, V>();
122         btreeHeader.setRootPage( rootPage );
123         btreeHeader.setBtree( this );
124 
125         switch ( btreeType )
126         {
127             case BTREE_OF_BTREES :
128             case COPIED_PAGES_BTREE :
129                 // We will create a new cache and a new readTransactions map 
130                 init( null );
131                 currentBtreeHeader = btreeHeader;
132                 break;
133 
134             case PERSISTED_SUB :
135                 init( ( PersistedBTree<K, V> ) configuration.getParentBTree() );
136                 btreeRevisions.put( 0L, btreeHeader );
137                 currentBtreeHeader = btreeHeader;
138                 break;
139                 
140             default :
141                 // We will create a new cache and a new readTransactions map 
142                 init( null );
143                 btreeRevisions.put( 0L, btreeHeader );
144                 currentBtreeHeader = btreeHeader;
145                 break;
146         }
147     }
148 
149 
150     /**
151      * Initialize the BTree.
152      *
153      * @throws IOException If we get some exception while initializing the BTree
154      */
155     public void init( BTree<K, V> parentBTree )
156     {
157         if ( parentBTree == null )
158         {
159             // This is not a subBtree, we have to initialize the cache
160 
161             // Create the queue containing the pending read transactions
162             readTransactions = new ConcurrentLinkedQueue<ReadTransaction<K, V>>();
163 
164             if ( cacheSize < 1 )
165             {
166                 cacheSize = 1;
167             }
168             
169             cache = new LRUMap( cacheSize );
170         }
171         else
172         {
173             this.cache = ((PersistedBTree<K, V>)parentBTree).getCache();
174             this.readTransactions = ((PersistedBTree<K, V>)parentBTree).getReadTransactions();
175         }
176 
177         // Initialize the txnManager thread
178         //FIXME we should NOT create a new transaction manager thread for each BTree
179         //createTransactionManager();
180     }
181 
182 
183     /**
184      * Return the cache we use in this BTree
185      */
186     /* No qualifier */LRUMap getCache()
187     {
188         return cache;
189     }
190 
191 
192     /**
193      * Return the cache we use in this BTree
194      */
195     /* No qualifier */ConcurrentLinkedQueue<ReadTransaction<K, V>> getReadTransactions()
196     {
197         return readTransactions;
198     }
199 
200 
201     /**
202      * Close the BTree, cleaning up all the data structure
203      */
204     public void close() throws IOException
205     {
206         // Stop the readTransaction thread
207         // readTransactionsThread.interrupt();
208         // readTransactions.clear();
209 
210         // Clean the cache
211         cache.clear();
212     }
213 
214 
215     /**
216      * @return the btreeOffset
217      */
218     /* No qualifier*/long getBtreeOffset()
219     {
220         return getBTreeHeader( getName() ).getBTreeHeaderOffset();
221     }
222 
223 
224     /**
225      * @param btreeOffset the B-tree header Offset to set
226      */
227     /* No qualifier*/void setBtreeHeaderOffset( long btreeHeaderOffset )
228     {
229         getBTreeHeader( getName() ).setBTreeHeaderOffset( btreeHeaderOffset );
230     }
231 
232 
233     /**
234      * @return the rootPageOffset
235      */
236     /* No qualifier*/long getRootPageOffset()
237     {
238         return getBTreeHeader( getName() ).getRootPageOffset();
239     }
240 
241 
242     /**
243      * Gets the RecordManager for a managed BTree
244      *
245      * @return The recordManager if the B-tree is managed
246      */
247     /* No qualifier */RecordManager getRecordManager()
248     {
249         return recordManager;
250     }
251 
252 
253     /**
254      * Inject a RecordManager for a managed BTree
255      *
256      * @param recordManager The injected RecordManager
257      */
258     /* No qualifier */void setRecordManager( RecordManager recordManager )
259     {
260         // The RecordManager is also the TransactionManager
261         transactionManager = recordManager;
262         this.recordManager = recordManager;
263     }
264 
265 
266     /**
267      *
268      * Deletes the given <key,value> pair if both key and value match. If the given value is null
269      * and there is no null value associated with the given key then the entry with the given key
270      * will be removed.
271      *
272      * @param key The key to be removed
273      * @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)
274      * @param revision The revision to be associated with this operation
275      * @return
276      * @throws IOException
277      */
278     /* no qualifier */ Tuple<K, V> delete( K key, V value, long revision ) throws IOException
279     {
280         // We have to start a new transaction, which will be committed or rollbacked
281         // locally. This will duplicate the current BtreeHeader during this phase.
282         if ( revision == -1L )
283         {
284             revision = currentRevision.get() + 1;
285         }
286 
287         try
288         {
289             // Try to delete the entry starting from the root page. Here, the root
290             // page may be either a Node or a Leaf
291             DeleteResult<K, V> result = processDelete( key, value, revision );
292 
293             // Check that we have found the element to delete
294             if ( result instanceof NotPresentResult )
295             {
296                 // We haven't found the element in the B-tree, just get out
297                 // without updating the recordManager
298 
299                 return null;
300             }
301 
302             // The element was found, and removed
303             AbstractDeleteResult<K, V> deleteResult = ( AbstractDeleteResult<K, V> ) result;
304 
305             Tuple<K, V> tuple = deleteResult.getRemovedElement();
306 
307             // If the B-tree is managed, we have to update the rootPage on disk
308             // Update the RecordManager header
309 
310             // Return the value we have found if it was modified
311             return tuple;
312         }
313         catch ( IOException ioe )
314         {
315             // if we've got an error, we have to rollback
316             throw ioe;
317         }
318     }
319 
320 
321     /**
322      * Insert the tuple into the B-tree rootPage, get back the new rootPage
323      */
324     private DeleteResult<K, V> processDelete( K key, V value, long revision ) throws IOException
325     {
326         // Get the current B-tree header, and delete the value from it
327         BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() );
328 
329         // Try to delete the entry starting from the root page. Here, the root
330         // page may be either a Node or a Leaf
331         DeleteResult<K, V> result = btreeHeader.getRootPage().delete( key, value, revision);
332 
333         if ( result instanceof NotPresentResult )
334         {
335             // Key not found.
336             return result;
337         }
338 
339         // Create a new BTreeHeader
340         BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy();
341 
342         // Inject the old B-tree header into the pages to be freed
343         // if we are deleting an element from a management BTree
344         if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) )
345         {
346             PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L );
347 
348             for ( PageIO pageIo : pageIos )
349             {
350                 recordManager.freedPages.add( pageIo );
351             }
352         }
353 
354         // The element was found, and removed
355         AbstractDeleteResult<K, V> removeResult = ( AbstractDeleteResult<K, V> ) result;
356 
357         // This is a new root
358         Page<K, V> newRootPage = removeResult.getModifiedPage();
359 
360         // Write the modified page on disk
361         // Note that we don't use the holder, the new root page will
362         // remain in memory.
363         PageHolder<K, V> holder = writePage( newRootPage, revision );
364 
365         // Decrease the number of elements in the current tree
366         newBtreeHeader.decrementNbElems();
367         newBtreeHeader.setRootPage( newRootPage );
368         newBtreeHeader.setRevision( revision );
369 
370         // Write down the data on disk
371         long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader );
372 
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 
397                 break;
398 
399             case BTREE_OF_BTREES :
400                 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters
401                 recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L );
402 
403                 // We can free the copied pages
404                 recordManager.freePages( this, revision, result.getCopiedPages() );
405 
406                 // Store the new revision
407                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
408 
409                 break;
410 
411             case COPIED_PAGES_BTREE :
412                 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters
413                 recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset );
414 
415                 // We can free the copied pages
416                 recordManager.freePages( this, revision, result.getCopiedPages() );
417 
418                 // Store the new revision
419                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
420 
421                 break;
422 
423             default:
424                 // Nothing to do for sub-btrees
425                 break;
426         }
427 
428         // Return the value we have found if it was modified
429         return result;
430     }
431 
432 
433     /**
434      * Insert an entry in the BTree.
435      * <p>
436      * We will replace the value if the provided key already exists in the
437      * btree.
438      * <p>
439      * The revision number is the revision to use to insert the data.
440      *
441      * @param key Inserted key
442      * @param value Inserted value
443      * @param revision The revision to use
444      * @return an instance of the InsertResult.
445      */
446     /* no qualifier */InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
447     {
448         // We have to start a new transaction, which will be committed or rollbacked
449         // locally. This will duplicate the current BtreeHeader during this phase.
450         if ( revision == -1L )
451         {
452             revision = currentRevision.get() + 1;
453         }
454 
455         try
456         {
457             // Try to insert the new value in the tree at the right place,
458             // starting from the root page. Here, the root page may be either
459             // a Node or a Leaf
460             InsertResult<K, V> result = processInsert( key, value, revision );
461 
462             // Return the value we have found if it was modified
463             return result;
464         }
465         catch ( IOException ioe )
466         {
467             throw ioe;
468         }
469     }
470 
471     
472     private BTreeHeader<K, V> getBTreeHeader( String name )
473     {
474         switch ( btreeType )
475         {
476             case PERSISTED_SUB : 
477                 return getBtreeHeader();
478                 
479             case BTREE_OF_BTREES : 
480                 return recordManager.getNewBTreeHeader( RecordManager.BTREE_OF_BTREES_NAME );
481                     
482             case COPIED_PAGES_BTREE : 
483                 return recordManager.getNewBTreeHeader( RecordManager.COPIED_PAGE_BTREE_NAME );
484                 
485             default : 
486                 return recordManager.getBTreeHeader( getName() );
487         }
488     }
489 
490     
491     private BTreeHeader<K, V> getNewBTreeHeader( String name )
492     {
493         if ( btreeType == BTreeTypeEnum.PERSISTED_SUB )
494         {
495             return getBtreeHeader();
496         }
497         
498         BTreeHeader<K, V> btreeHeader = recordManager.getNewBTreeHeader( getName() );
499 
500         return btreeHeader;
501     }
502 
503     
504     /**
505      * Insert the tuple into the B-tree rootPage, get back the new rootPage
506      */
507     private InsertResult<K, V> processInsert( K key, V value, long revision ) throws IOException
508     {
509         // Get the current B-tree header, and insert the value into it
510         BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() );
511         InsertResult<K, V> result = btreeHeader.getRootPage().insert( key, value, revision );
512 
513         if ( result instanceof ExistsResult )
514         {
515             return result;
516         }
517 
518         // Create a new BTreeHeader
519         BTreeHeader<K, V> newBtreeHeader = btreeHeader.copy();
520 
521         // Inject the old B-tree header into the pages to be freed
522         // if we are inserting an element in a management BTree
523         if ( ( btreeType == BTreeTypeEnum.BTREE_OF_BTREES ) || ( btreeType == BTreeTypeEnum.COPIED_PAGES_BTREE ) )
524         {
525             PageIO[] pageIos = recordManager.readPageIOs( btreeHeader.getBTreeHeaderOffset(), -1L );
526 
527             for ( PageIO pageIo : pageIos )
528             {
529                 recordManager.freedPages.add( pageIo );
530             }
531         }
532 
533         Page<K, V> newRootPage;
534 
535         if ( result instanceof ModifyResult )
536         {
537             ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result );
538 
539             newRootPage = modifyResult.getModifiedPage();
540 
541             // Increment the counter if we have inserted a new value
542             if ( modifyResult.getModifiedValue() == null )
543             {
544                 newBtreeHeader.incrementNbElems();
545             }
546         }
547         else
548         {
549             // We have split the old root, create a new one containing
550             // only the pivotal we got back
551             SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result );
552 
553             K pivot = splitResult.getPivot();
554             Page<K, V> leftPage = splitResult.getLeftPage();
555             Page<K, V> rightPage = splitResult.getRightPage();
556 
557             // If the B-tree is managed, we have to write the two pages that were created
558             // and to keep a track of the two offsets for the upper node
559             PageHolder<K, V> holderLeft = writePage( leftPage, revision );
560 
561             PageHolder<K, V> holderRight = writePage( rightPage, revision );
562 
563             // Create the new rootPage
564             newRootPage = new PersistedNode<K, V>( this, revision, pivot, holderLeft, holderRight );
565 
566             // Always increment the counter : we have added a new value
567             newBtreeHeader.incrementNbElems();
568         }
569 
570         // Write the new root page on disk
571         LOG_PAGES.debug( "Writing the new rootPage revision {} for {}", revision, name );
572         writePage( newRootPage, revision );
573 
574         // Update the new B-tree header
575         newBtreeHeader.setRootPage( newRootPage );
576         newBtreeHeader.setRevision( revision );
577 
578         // Write down the data on disk
579         long newBtreeHeaderOffset = recordManager.writeBtreeHeader( this, newBtreeHeader );
580 
581         // Update the B-tree of B-trees with this new offset, if we are not already doing so
582         switch ( btreeType )
583         {
584             case PERSISTED :
585                 // We have a new B-tree header to inject into the B-tree of btrees
586                 recordManager.addInBtreeOfBtrees( getName(), revision, newBtreeHeaderOffset );
587 
588                 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() );
589 
590                 // Store the new revision
591                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
592 
593                 break;
594 
595             case PERSISTED_SUB :
596                 // Sub-B-trees are only updating the CopiedPage B-tree
597                 recordManager.addInCopiedPagesBtree( getName(), revision, result.getCopiedPages() );
598                 
599                 // Store the new revision
600                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
601 
602                 currentRevision.set( revision );
603 
604                 break;
605 
606             case BTREE_OF_BTREES :
607                 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters
608                 recordManager.updateRecordManagerHeader( newBtreeHeaderOffset, -1L );
609 
610                 // We can free the copied pages
611                 recordManager.freePages( this, revision, result.getCopiedPages() );
612 
613                 // Store the new revision
614                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
615 
616                 break;
617 
618             case COPIED_PAGES_BTREE :
619                 // The B-tree of B-trees or the copiedPages B-tree has been updated, update the RMheader parameters
620                 recordManager.updateRecordManagerHeader( -1L, newBtreeHeaderOffset );
621 
622                 // We can free the copied pages
623                 recordManager.freePages( this, revision, result.getCopiedPages() );
624 
625                 // Store the new revision
626                 storeRevision( newBtreeHeader, recordManager.isKeepRevisions() );
627 
628                 break;
629 
630             default:
631                 // Nothing to do for sub-btrees
632                 break;
633         }
634 
635         // Get the new root page and make it the current root page
636         return result;
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      * Get the rootPzge associated to a give revision.
693      *
694      * @param revision The revision we are looking for
695      * @return The rootPage associated to this revision
696      * @throws IOException If we had an issue while accessing the underlying file
697      * @throws KeyNotFoundException If the revision does not exist for this Btree
698      */
699     public Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException
700     {
701         return recordManager.getRootPage( this, revision );
702     }
703 
704 
705     /**
706      * Get the current rootPage
707      *
708      * @return The rootPage
709      */
710     public Page<K, V> getRootPage()
711     {
712         return getBTreeHeader( getName() ).getRootPage();
713     }
714 
715 
716     /* no qualifier */void setRootPage( Page<K, V> root )
717     {
718         getBTreeHeader( getName() ).setRootPage( root );
719     }
720 
721 
722     /**
723      * @return the btreeInfoOffset
724      */
725     public long getBtreeInfoOffset()
726     {
727         return btreeInfoOffset;
728     }
729 
730 
731     /**
732      * @param btreeInfoOffset the btreeInfoOffset to set
733      */
734     public void setBtreeInfoOffset( long btreeInfoOffset )
735     {
736         this.btreeInfoOffset = btreeInfoOffset;
737     }
738 
739 
740     /**
741      * {@inheritDoc}
742      */
743     protected ReadTransaction<K, V> beginReadTransaction()
744     {
745         BTreeHeader<K, V> btreeHeader = getBTreeHeader( getName() );
746 
747         ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( recordManager, btreeHeader, readTransactions );
748 
749         readTransactions.add( readTransaction );
750 
751         return readTransaction;
752     }
753     
754     
755     /**
756      * {@inheritDoc}
757      */
758     protected ReadTransaction<K, V> beginReadTransaction( long revision )
759     {
760         BTreeHeader<K, V> btreeHeader = getBtreeHeader( revision );
761 
762         if ( btreeHeader != null )
763         {
764             ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>(  recordManager, btreeHeader, readTransactions );
765 
766             readTransactions.add( readTransaction );
767 
768             return readTransaction;
769         }
770         else
771         {
772             return null;
773         }
774     }
775     
776 
777     /**
778      * @see Object#toString()
779      */
780     public String toString()
781     {
782         StringBuilder sb = new StringBuilder();
783 
784         sb.append( "Managed BTree" );
785         sb.append( "[" ).append( getName() ).append( "]" );
786         sb.append( "( pageSize:" ).append( getPageSize() );
787 
788         if ( getBTreeHeader( getName() ).getRootPage() != null )
789         {
790             sb.append( ", nbEntries:" ).append( getBTreeHeader( getName() ).getNbElems() );
791         }
792         else
793         {
794             sb.append( ", nbEntries:" ).append( 0 );
795         }
796 
797         sb.append( ", comparator:" );
798 
799         if ( keySerializer.getComparator() == null )
800         {
801             sb.append( "null" );
802         }
803         else
804         {
805             sb.append( keySerializer.getComparator().getClass().getSimpleName() );
806         }
807 
808         sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() );
809 
810         sb.append( ") : \n" );
811         sb.append( getBTreeHeader( getName() ).getRootPage().dumpPage( "" ) );
812 
813         return sb.toString();
814     }
815 }