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