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.EOFException;
25  import java.io.File;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.RandomAccessFile;
29  import java.nio.ByteBuffer;
30  import java.nio.channels.FileChannel;
31  import java.util.concurrent.ConcurrentLinkedQueue;
32  
33  import org.apache.directory.mavibot.btree.exception.InitializationException;
34  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
35  import org.apache.directory.mavibot.btree.exception.MissingSerializerException;
36  import org.apache.directory.mavibot.btree.serializer.BufferHandler;
37  import org.apache.directory.mavibot.btree.serializer.LongSerializer;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  
42  /**
43   * The B+Tree MVCC data structure.
44   *
45   * @param <K> The type for the keys
46   * @param <V> The type for the stored values
47   *
48   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
49   */
50  /* No qualifier */class InMemoryBTree<K, V> extends AbstractBTree<K, V> implements Closeable
51  {
52      /** The LoggerFactory used by this class */
53      protected static final Logger LOG = LoggerFactory.getLogger( InMemoryBTree.class );
54  
55      /** The default journal name */
56      public static final String DEFAULT_JOURNAL = "mavibot.log";
57  
58      /** The default data file suffix */
59      public static final String DATA_SUFFIX = ".db";
60  
61      /** The default journal file suffix */
62      public static final String JOURNAL_SUFFIX = ".log";
63  
64      /** The type to use to create the keys */
65      /** The associated file. If null, this is an in-memory btree  */
66      private File file;
67  
68      /** A flag used to tell the BTree that the journal is activated */
69      private boolean withJournal;
70  
71      /** The associated journal. If null, this is an in-memory btree  */
72      private File journal;
73  
74      /** The directory where the journal will be stored */
75      private File envDir;
76  
77      /** The Journal channel */
78      private FileChannel journalChannel = null;
79  
80  
81      /**
82       * Creates a new BTree, with no initialization.
83       */
84      /* no qualifier */InMemoryBTree()
85      {
86          super();
87          setType( BTreeTypeEnum.IN_MEMORY );
88      }
89  
90  
91      /**
92       * Creates a new in-memory BTree using the BTreeConfiguration to initialize the
93       * BTree
94       *
95       * @param configuration The configuration to use
96       */
97      /* no qualifier */InMemoryBTree( InMemoryBTreeConfiguration<K, V> configuration )
98      {
99          super();
100         String btreeName = configuration.getName();
101 
102         if ( btreeName == null )
103         {
104             throw new IllegalArgumentException( "BTree name cannot be null" );
105         }
106 
107         String filePath = configuration.getFilePath();
108 
109         if ( filePath != null )
110         {
111             envDir = new File( filePath );
112         }
113 
114         // Store the configuration in the B-tree
115         setName( btreeName );
116         setPageSize( configuration.getPageSize() );
117         setKeySerializer( configuration.getKeySerializer() );
118         setValueSerializer( configuration.getValueSerializer() );
119         setAllowDuplicates( configuration.isAllowDuplicates() );
120         setType( configuration.getType() );
121 
122         readTimeOut = configuration.getReadTimeOut();
123         writeBufferSize = configuration.getWriteBufferSize();
124 
125         if ( keySerializer.getComparator() == null )
126         {
127             throw new IllegalArgumentException( "Comparator should not be null" );
128         }
129 
130         // Create the B-tree header
131         BTreeHeader<K, V> newBtreeHeader = new BTreeHeader<K, V>();
132 
133         // Create the first root page, with revision 0L. It will be empty
134         // and increment the revision at the same time
135         newBtreeHeader.setBTreeHeaderOffset( 0L );
136         newBtreeHeader.setRevision( 0L );
137         newBtreeHeader.setNbElems( 0L );
138         newBtreeHeader.setRootPage( new InMemoryLeaf<K, V>( this ) );
139         newBtreeHeader.setRootPageOffset( 0L );
140 
141         btreeRevisions.put( 0L, newBtreeHeader );
142         currentBtreeHeader = newBtreeHeader;
143 
144         // Now, initialize the BTree
145         try
146         {
147             init();
148         }
149         catch ( IOException ioe )
150         {
151             throw new InitializationException( ioe.getMessage() );
152         }
153     }
154 
155 
156     /**
157      * Initialize the BTree.
158      *
159      * @throws IOException If we get some exception while initializing the BTree
160      */
161     private void init() throws IOException
162     {
163         // if not in-memory then default to persist mode instead of managed
164         if ( envDir != null )
165         {
166             if ( !envDir.exists() )
167             {
168                 boolean created = envDir.mkdirs();
169 
170                 if ( !created )
171                 {
172                     throw new IllegalStateException( "Could not create the directory " + envDir + " for storing data" );
173                 }
174             }
175 
176             this.file = new File( envDir, getName() + DATA_SUFFIX );
177 
178             this.journal = new File( envDir, file.getName() + JOURNAL_SUFFIX );
179             setType( BTreeTypeEnum.BACKED_ON_DISK );
180         }
181 
182         // Create the queue containing the pending read transactions
183         readTransactions = new ConcurrentLinkedQueue<ReadTransaction<K, V>>();
184 
185         // Create the transaction manager
186         transactionManager = new InMemoryTransactionManager();
187 
188         // Check the files and create them if missing
189         // Create the queue containing the modifications, if it's not a in-memory btree
190         if ( getType() == BTreeTypeEnum.BACKED_ON_DISK )
191         {
192             if ( file.length() > 0 )
193             {
194                 // We have some existing file, load it
195                 load( file );
196             }
197 
198             withJournal = true;
199 
200             FileOutputStream stream = new FileOutputStream( journal );
201             journalChannel = stream.getChannel();
202 
203             // If the journal is not empty, we have to read it
204             // and to apply all the modifications to the current file
205             if ( journal.length() > 0 )
206             {
207                 applyJournal();
208             }
209         }
210         else
211         {
212             setType( BTreeTypeEnum.IN_MEMORY );
213 
214             // This is a new Btree, we have to store the BtreeHeader
215             BTreeHeader<K, V> btreeHeader = new BTreeHeader<K, V>();
216             btreeHeader.setRootPage( new InMemoryLeaf<K, V>( this ) );
217             btreeHeader.setBtree( this );
218             storeRevision( btreeHeader );
219         }
220 
221         // Initialize the txnManager thread
222         //FIXME we should NOT create a new transaction manager thread for each BTree
223         //createTransactionManager();
224     }
225 
226 
227     /**
228      * {@inheritDoc}
229      */
230     protected ReadTransaction<K, V> beginReadTransaction()
231     {
232         BTreeHeader<K, V> btreeHeader = getBtreeHeader();
233 
234         ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( btreeHeader, readTransactions );
235 
236         readTransactions.add( readTransaction );
237 
238         return readTransaction;
239     }
240 
241 
242     /**
243      * {@inheritDoc}
244      */
245     protected ReadTransaction<K, V> beginReadTransaction( long revision )
246     {
247         BTreeHeader<K, V> btreeHeader = getBtreeHeader( revision );
248 
249         if ( btreeHeader != null )
250         {
251             ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( btreeHeader, readTransactions );
252 
253             readTransactions.add( readTransaction );
254 
255             return readTransaction;
256         }
257         else
258         {
259             return null;
260         }
261     }
262 
263 
264     /**
265      * Close the BTree, cleaning up all the data structure
266      */
267     public void close() throws IOException
268     {
269         // Stop the readTransaction thread
270         // readTransactionsThread.interrupt();
271         // readTransactions.clear();
272 
273         if ( getType() == BTreeTypeEnum.BACKED_ON_DISK )
274         {
275             // Flush the data
276             flush();
277             journalChannel.close();
278         }
279     }
280 
281 
282     /**
283      *
284      * Deletes the given <key,value> pair if both key and value match. If the given value is null
285      * and there is no null value associated with the given key then the entry with the given key
286      * will be removed.
287      *
288      * @param key The key to be removed
289      * @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)
290      * @param revision The revision to be associated with this operation
291      * @return
292      * @throws IOException
293      */
294     protected Tuple<K, V> delete( K key, V value, long revision ) throws IOException
295     {
296         if ( revision == -1L )
297         {
298             revision = currentRevision.get() + 1;
299         }
300 
301         BTreeHeader<K, V> oldBtreeHeader = getBtreeHeader();
302         BTreeHeader<K, V> newBtreeHeader = createNewBtreeHeader( oldBtreeHeader, revision );
303         newBtreeHeader.setBtree( this );
304 
305         // If the key exists, the existing value will be replaced. We store it
306         // to return it to the caller.
307         Tuple<K, V> tuple = null;
308 
309         // Try to delete the entry starting from the root page. Here, the root
310         // page may be either a Node or a Leaf
311         DeleteResult<K, V> result = getRootPage().delete( key, value, revision );
312 
313         if ( result instanceof NotPresentResult )
314         {
315             // Key not found.
316             return null;
317         }
318 
319         // Keep the oldRootPage so that we can later access it
320         //Page<K, V> oldRootPage = rootPage;
321 
322         if ( result instanceof RemoveResult )
323         {
324             // The element was found, and removed
325             RemoveResult<K, V> removeResult = ( RemoveResult<K, V> ) result;
326 
327             Page<K, V> modifiedPage = removeResult.getModifiedPage();
328 
329             // This is a new root
330             newBtreeHeader.setRootPage( modifiedPage );
331             tuple = removeResult.getRemovedElement();
332         }
333 
334         if ( withJournal )
335         {
336             // Inject the modification into the modification queue
337             writeToJournal( new Deletion<K, V>( key ) );
338         }
339 
340         // Decrease the number of elements in the current tree if the deletion is successful
341         if ( tuple != null )
342         {
343             newBtreeHeader.decrementNbElems();
344         }
345 
346         storeRevision( newBtreeHeader );
347 
348         // Return the value we have found if it was modified
349         if ( oldBtreeHeader.getNbUsers() == 0 )
350         {
351             btreeRevisions.remove( oldBtreeHeader.getRevision() );
352         }
353 
354         return tuple;
355     }
356 
357 
358     /**
359      * Insert an entry in the BTree.
360      * <p>
361      * We will replace the value if the provided key already exists in the
362      * btree.
363      * <p>
364      * The revision number is the revision to use to insert the data.
365      *
366      * @param key Inserted key
367      * @param value Inserted value
368      * @param revision The revision to use
369      * @return an instance of the InsertResult.
370      */
371     /* no qualifier */InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
372     {
373         // We have to start a new transaction, which will be committed or rollbacked
374         // locally. This will duplicate the current BtreeHeader during this phase.
375         if ( revision == -1L )
376         {
377             revision = currentRevision.get() + 1;
378         }
379 
380         BTreeHeader<K, V> oldBtreeHeader = getBtreeHeader();
381         BTreeHeader<K, V> newBtreeHeader = createNewBtreeHeader( oldBtreeHeader, revision );
382         newBtreeHeader.setBtree( this );
383 
384         // If the key exists, the existing value will be replaced. We store it
385         // to return it to the caller.
386         V modifiedValue = null;
387 
388         // Try to insert the new value in the tree at the right place,
389         // starting from the root page. Here, the root page may be either
390         // a Node or a Leaf
391         InsertResult<K, V> result = newBtreeHeader.getRootPage().insert( key, value, revision );
392 
393         if ( result instanceof ExistsResult )
394         {
395             return result;
396         }
397 
398         if ( result instanceof ModifyResult )
399         {
400             ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result );
401 
402             Page<K, V> modifiedPage = modifyResult.getModifiedPage();
403 
404             // The root has just been modified, we haven't split it
405             // Get it and make it the current root page
406             newBtreeHeader.setRootPage( modifiedPage );
407 
408             modifiedValue = modifyResult.getModifiedValue();
409         }
410         else
411         {
412             // We have split the old root, create a new one containing
413             // only the pivotal we got back
414             SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result );
415 
416             K pivot = splitResult.getPivot();
417             Page<K, V> leftPage = splitResult.getLeftPage();
418             Page<K, V> rightPage = splitResult.getRightPage();
419 
420             // Create the new rootPage
421             newBtreeHeader.setRootPage( new InMemoryNode<K, V>( this, revision, pivot, leftPage, rightPage ) );
422         }
423 
424         // Inject the modification into the modification queue
425         if ( withJournal )
426         {
427             writeToJournal( new Addition<K, V>( key, value ) );
428         }
429 
430         // Increase the number of element in the current tree if the insertion is successful
431         // and does not replace an element
432         if ( modifiedValue == null )
433         {
434             newBtreeHeader.incrementNbElems();
435         }
436 
437         storeRevision( newBtreeHeader );
438 
439         if ( oldBtreeHeader.getNbUsers() == 0 )
440         {
441             long oldRevision = oldBtreeHeader.getRevision();
442 
443             if ( oldRevision < newBtreeHeader.getRevision() )
444             {
445                 btreeRevisions.remove( oldBtreeHeader.getRevision() );
446             }
447         }
448 
449         // Return the value we have found if it was modified
450         return result;
451     }
452 
453 
454     /**
455      * Write the data in the ByteBuffer, and eventually on disk if needed.
456      *
457      * @param channel The channel we want to write to
458      * @param bb The ByteBuffer we want to feed
459      * @param buffer The data to inject
460      * @throws IOException If the write failed
461      */
462     private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException
463     {
464         int size = buffer.length;
465         int pos = 0;
466 
467         // Loop until we have written all the data
468         do
469         {
470             if ( bb.remaining() >= size )
471             {
472                 // No flush, as the ByteBuffer is big enough
473                 bb.put( buffer, pos, size );
474                 size = 0;
475             }
476             else
477             {
478                 // Flush the data on disk, reinitialize the ByteBuffer
479                 int len = bb.remaining();
480                 size -= len;
481                 bb.put( buffer, pos, len );
482                 pos += len;
483 
484                 bb.flip();
485 
486                 channel.write( bb );
487 
488                 bb.clear();
489             }
490         }
491         while ( size > 0 );
492     }
493 
494 
495     /**
496      * Flush the latest revision to disk
497      * @param file The file into which the data will be written
498      */
499     public void flush( File file ) throws IOException
500     {
501         File parentFile = file.getParentFile();
502         File baseDirectory = null;
503 
504         if ( parentFile != null )
505         {
506             baseDirectory = new File( file.getParentFile().getAbsolutePath() );
507         }
508         else
509         {
510             baseDirectory = new File( "." );
511         }
512 
513         // Create a temporary file in the same directory to flush the current btree
514         File tmpFileFD = File.createTempFile( "mavibot", null, baseDirectory );
515         FileOutputStream stream = new FileOutputStream( tmpFileFD );
516         FileChannel ch = stream.getChannel();
517 
518         // Create a buffer containing 200 4Kb pages (around 1Mb)
519         ByteBuffer bb = ByteBuffer.allocateDirect( writeBufferSize );
520 
521         try
522         {
523             TupleCursor<K, V> cursor = browse();
524 
525             if ( keySerializer == null )
526             {
527                 throw new MissingSerializerException( "Cannot flush the btree without a Key serializer" );
528             }
529 
530             if ( valueSerializer == null )
531             {
532                 throw new MissingSerializerException( "Cannot flush the btree without a Value serializer" );
533             }
534 
535             // Write the number of elements first
536             bb.putLong( getBtreeHeader().getNbElems() );
537 
538             while ( cursor.hasNext() )
539             {
540                 Tuple<K, V> tuple = cursor.next();
541 
542                 byte[] keyBuffer = keySerializer.serialize( tuple.getKey() );
543 
544                 writeBuffer( ch, bb, keyBuffer );
545 
546                 byte[] valueBuffer = valueSerializer.serialize( tuple.getValue() );
547 
548                 writeBuffer( ch, bb, valueBuffer );
549             }
550 
551             // Write the buffer if needed
552             if ( bb.position() > 0 )
553             {
554                 bb.flip();
555                 ch.write( bb );
556             }
557 
558             // Flush to the disk for real
559             ch.force( true );
560             ch.close();
561         }
562         catch ( KeyNotFoundException knfe )
563         {
564             knfe.printStackTrace();
565             throw new IOException( knfe.getMessage() );
566         }
567 
568         // Rename the current file to save a backup
569         File backupFile = File.createTempFile( "mavibot", null, baseDirectory );
570         file.renameTo( backupFile );
571 
572         // Rename the temporary file to the initial file
573         tmpFileFD.renameTo( file );
574 
575         // We can now delete the backup file
576         backupFile.delete();
577     }
578 
579 
580     /**
581      * Inject all the modification from the journal into the btree
582      *
583      * @throws IOException If we had some issue while reading the journal
584      */
585     private void applyJournal() throws IOException
586     {
587         if ( !journal.exists() )
588         {
589             throw new IOException( "The journal does not exist" );
590         }
591 
592         FileChannel channel =
593             new RandomAccessFile( journal, "rw" ).getChannel();
594         ByteBuffer buffer = ByteBuffer.allocate( 65536 );
595 
596         BufferHandler bufferHandler = new BufferHandler( channel, buffer );
597 
598         // Loop on all the elements, store them in lists atm
599         try
600         {
601             while ( true )
602             {
603                 // Read the type
604                 byte[] type = bufferHandler.read( 1 );
605 
606                 if ( type[0] == Modification.ADDITION )
607                 {
608                     // Read the key
609                     K key = keySerializer.deserialize( bufferHandler );
610 
611                     //keys.add( key );
612 
613                     // Read the value
614                     V value = valueSerializer.deserialize( bufferHandler );
615 
616                     //values.add( value );
617 
618                     // Inject the data in the tree. (to be replaced by a bulk load)
619                     insert( key, value, getBtreeHeader().getRevision() );
620                 }
621                 else
622                 {
623                     // Read the key
624                     K key = keySerializer.deserialize( bufferHandler );
625 
626                     // Remove the key from the tree
627                     delete( key, getBtreeHeader().getRevision() );
628                 }
629             }
630         }
631         catch ( EOFException eofe )
632         {
633             eofe.printStackTrace();
634             // Done reading the journal. truncate it
635             journalChannel.truncate( 0 );
636         }
637     }
638 
639 
640     /**
641      * Read the data from the disk into this BTree. All the existing data in the
642      * BTree are kept, the read data will be associated with a new revision.
643      *
644      * @param file
645      * @throws IOException
646      */
647     public void load( File file ) throws IOException
648     {
649         if ( !file.exists() )
650         {
651             throw new IOException( "The file does not exist" );
652         }
653 
654         FileChannel channel =
655             new RandomAccessFile( file, "rw" ).getChannel();
656         ByteBuffer buffer = ByteBuffer.allocate( 65536 );
657 
658         BufferHandler bufferHandler = new BufferHandler( channel, buffer );
659 
660         long nbElems = LongSerializer.deserialize( bufferHandler.read( 8 ) );
661 
662         // desactivate the journal while we load the file
663         boolean isJournalActivated = withJournal;
664 
665         withJournal = false;
666 
667         // Loop on all the elements, store them in lists atm
668         for ( long i = 0; i < nbElems; i++ )
669         {
670             // Read the key
671             K key = keySerializer.deserialize( bufferHandler );
672 
673             // Read the value
674             V value = valueSerializer.deserialize( bufferHandler );
675 
676             // Inject the data in the tree. (to be replaced by a bulk load)
677             insert( key, value, getBtreeHeader().getRevision() );
678         }
679 
680         // Restore the withJournal value
681         withJournal = isJournalActivated;
682 
683         // Now, process the lists to create the btree
684         // TODO... BulkLoad
685     }
686 
687 
688     /**
689      * Get the rootPage associated to a give revision.
690      *
691      * @param revision The revision we are looking for
692      * @return The rootPage associated to this revision
693      * @throws IOException If we had an issue while accessing the underlying file
694      * @throws KeyNotFoundException If the revision does not exist for this Btree
695      */
696     public Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException
697     {
698         // Atm, the in-memory BTree does not support searches in many revisions
699         return getBtreeHeader().getRootPage();
700     }
701 
702 
703     /**
704      * Get the current rootPage
705      *
706      * @return The rootPage
707      */
708     public Page<K, V> getRootPage()
709     {
710         return getBtreeHeader().getRootPage();
711     }
712 
713 
714     /* no qualifier */void setRootPage( Page<K, V> root )
715     {
716         getBtreeHeader().setRootPage( root );
717     }
718 
719 
720     /**
721      * Flush the latest revision to disk. We will replace the current file by the new one, as
722      * we flush in a temporary file.
723      */
724     public void flush() throws IOException
725     {
726         if ( getType() == BTreeTypeEnum.BACKED_ON_DISK )
727         {
728             // Then flush the file
729             flush( file );
730             journalChannel.truncate( 0 );
731         }
732     }
733 
734 
735     /**
736      * @return the file
737      */
738     public File getFile()
739     {
740         return file;
741     }
742 
743 
744     /**
745      * Set the file path where the journal will be stored
746      * 
747      * @param filePath The file path
748      */
749     public void setFilePath( String filePath )
750     {
751         if ( filePath != null )
752         {
753             envDir = new File( filePath );
754         }
755     }
756 
757 
758     /**
759      * @return the journal
760      */
761     public File getJournal()
762     {
763         return journal;
764     }
765 
766 
767     /**
768      * @return true if the BTree is fully in memory
769      */
770     public boolean isInMemory()
771     {
772         return getType() == BTreeTypeEnum.IN_MEMORY;
773     }
774 
775 
776     /**
777      * @return true if the BTree is persisted on disk
778      */
779     public boolean isPersistent()
780     {
781         return getType() == BTreeTypeEnum.IN_MEMORY;
782     }
783 
784 
785     private void writeToJournal( Modification<K, V> modification )
786         throws IOException
787     {
788         if ( modification instanceof Addition )
789         {
790             byte[] keyBuffer = keySerializer.serialize( modification.getKey() );
791             ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 );
792             bb.put( Modification.ADDITION );
793             bb.put( keyBuffer );
794             bb.flip();
795 
796             journalChannel.write( bb );
797 
798             byte[] valueBuffer = valueSerializer.serialize( modification.getValue() );
799             bb = ByteBuffer.allocateDirect( valueBuffer.length );
800             bb.put( valueBuffer );
801             bb.flip();
802 
803             journalChannel.write( bb );
804         }
805         else if ( modification instanceof Deletion )
806         {
807             byte[] keyBuffer = keySerializer.serialize( modification.getKey() );
808             ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 );
809             bb.put( Modification.DELETION );
810             bb.put( keyBuffer );
811             bb.flip();
812 
813             journalChannel.write( bb );
814         }
815 
816         // Flush to the disk for real
817         journalChannel.force( true );
818     }
819 
820 
821     /**
822      * Create a new B-tree header to be used for update operations
823      * @param revision The reclaimed revision
824      */
825     private BTreeHeader<K, V> createNewBtreeHeader( BTreeHeader<K, V> btreeHeader, long revision )
826     {
827         BTreeHeader<K, V> newBtreeHeader = new BTreeHeader<K, V>();
828 
829         newBtreeHeader.setBTreeHeaderOffset( btreeHeader.getBTreeHeaderOffset() );
830         newBtreeHeader.setRevision( revision );
831         newBtreeHeader.setNbElems( btreeHeader.getNbElems() );
832         newBtreeHeader.setRootPage( btreeHeader.getRootPage() );
833 
834         return newBtreeHeader;
835     }
836 
837 
838     /**
839      * @see Object#toString()
840      */
841     public String toString()
842     {
843         StringBuilder sb = new StringBuilder();
844 
845         switch ( getType() )
846         {
847             case IN_MEMORY:
848                 sb.append( "In-memory " );
849                 break;
850 
851             case BACKED_ON_DISK:
852                 sb.append( "Persistent " );
853                 break;
854 
855             default:
856                 sb.append( "Wrong type... " );
857                 break;
858         }
859 
860         sb.append( "BTree" );
861         sb.append( "[" ).append( getName() ).append( "]" );
862         sb.append( "( pageSize:" ).append( getPageSize() );
863 
864         if ( getBtreeHeader().getRootPage() != null )
865         {
866             sb.append( ", nbEntries:" ).append( getBtreeHeader().getNbElems() );
867         }
868         else
869         {
870             sb.append( ", nbEntries:" ).append( 0 );
871         }
872 
873         sb.append( ", comparator:" );
874 
875         if ( keySerializer.getComparator() == null )
876         {
877             sb.append( "null" );
878         }
879         else
880         {
881             sb.append( keySerializer.getComparator().getClass().getSimpleName() );
882         }
883 
884         sb.append( ", DuplicatesAllowed: " ).append( isAllowDuplicates() );
885 
886         if ( getType() == BTreeTypeEnum.BACKED_ON_DISK )
887         {
888             try
889             {
890                 sb.append( ", file : " );
891 
892                 if ( file != null )
893                 {
894                     sb.append( file.getCanonicalPath() );
895                 }
896                 else
897                 {
898                     sb.append( "Unknown" );
899                 }
900 
901                 sb.append( ", journal : " );
902 
903                 if ( journal != null )
904                 {
905                     sb.append( journal.getCanonicalPath() );
906                 }
907                 else
908                 {
909                     sb.append( "Unkown" );
910                 }
911             }
912             catch ( IOException ioe )
913             {
914                 // There is little we can do here...
915             }
916         }
917 
918         sb.append( ") : \n" );
919         sb.append( getRootPage().dumpPage( "" ) );
920 
921         return sb.toString();
922     }
923 }