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.memory;
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.lang.reflect.ParameterizedType;
30  import java.lang.reflect.Type;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.FileChannel;
33  import java.util.Comparator;
34  import java.util.LinkedList;
35  import java.util.concurrent.ConcurrentLinkedQueue;
36  import java.util.concurrent.locks.ReentrantLock;
37  
38  import net.sf.ehcache.Cache;
39  import net.sf.ehcache.config.CacheConfiguration;
40  
41  import org.apache.directory.mavibot.btree.Addition;
42  import org.apache.directory.mavibot.btree.BTreeHeader;
43  import org.apache.directory.mavibot.btree.Deletion;
44  import org.apache.directory.mavibot.btree.Modification;
45  import org.apache.directory.mavibot.btree.Tuple;
46  import org.apache.directory.mavibot.btree.TupleCursor;
47  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
48  import org.apache.directory.mavibot.btree.serializer.BufferHandler;
49  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
50  import org.apache.directory.mavibot.btree.serializer.LongSerializer;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  
55  /**
56   * The B+Tree MVCC data structure.
57   * 
58   * @param <K> The type for the keys
59   * @param <V> The type for the stored values
60   *
61   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
62   */
63  public class BTree<K, V> implements Closeable
64  {
65      /** The LoggerFactory used by this class */
66      protected static final Logger LOG = LoggerFactory.getLogger( BTree.class );
67  
68      /** The Header for a managed BTree */
69      private BTreeHeader btreeHeader;
70  
71      /** Default page size (number of entries per node) */
72      public static final int DEFAULT_PAGE_SIZE = 16;
73  
74      /** Default size of the buffer used to write data on disk. Around 1Mb */
75      public static final int DEFAULT_WRITE_BUFFER_SIZE = 4096 * 250;
76  
77      /** The default journal name */
78      public static final String DEFAULT_JOURNAL = "mavibot.log";
79  
80      /** The default data file suffix */
81      public static final String DATA_SUFFIX = ".db";
82  
83      /** The default journal file suffix */
84      public static final String JOURNAL_SUFFIX = ".log";
85  
86      /** Comparator used to index entries. */
87      private Comparator<K> comparator;
88  
89      /** The current rootPage */
90      protected volatile Page<K, V> rootPage;
91  
92      /** The list of read transactions being executed */
93      private ConcurrentLinkedQueue<Transaction<K, V>> readTransactions;
94  
95      /** The size of the buffer used to write data in disk */
96      private int writeBufferSize;
97  
98      /** The type to use to create the keys */
99      protected Class<?> keyType;
100 
101     /** The Key serializer used for this tree.*/
102     private ElementSerializer<K> keySerializer;
103 
104     /** The Value serializer used for this tree. */
105     private ElementSerializer<V> valueSerializer;
106 
107     /** The associated file. If null, this is an in-memory btree  */
108     private File file;
109 
110     /** The BTree type : either in-memory, persistent or managed */
111     private BTreeTypeEnum type;
112 
113     /** A flag used to tell the BTree that the journal is activated */
114     private boolean withJournal;
115 
116     /** The associated journal. If null, this is an in-memory btree  */
117     private File journal;
118 
119     /** A lock used to protect the write operation against concurrent access */
120     private ReentrantLock writeLock;
121 
122     /** The thread responsible for the cleanup of timed out reads */
123     private Thread readTransactionsThread;
124 
125     /** Define a default delay for a read transaction. This is 10 seconds */
126     public static final long DEFAULT_READ_TIMEOUT = 10 * 1000L;
127 
128     /** The read transaction timeout */
129     private long readTimeOut = DEFAULT_READ_TIMEOUT;
130 
131     private File envDir;
132 
133     private FileChannel journalChannel = null;
134 
135     /** The cache associated with this BTree */
136     private Cache cache;
137 
138     /** The cache size, default to 1000 elements */
139     private int cacheSize = DEFAULT_CACHE_SIZE;
140 
141     /** The default number of pages to keep in memory */
142     private static final int DEFAULT_CACHE_SIZE = 1000;
143 
144 
145     /**
146      * Create a thread that is responsible of cleaning the transactions when
147      * they hit the timeout
148      */
149     private void createTransactionManager()
150     {
151         Runnable readTransactionTask = new Runnable()
152         {
153             public void run()
154             {
155                 try
156                 {
157                     Transaction<K, V> transaction = null;
158 
159                     while ( !Thread.currentThread().isInterrupted() )
160                     {
161                         long timeoutDate = System.currentTimeMillis() - readTimeOut;
162                         long t0 = System.currentTimeMillis();
163                         int nbTxns = 0;
164 
165                         // Loop on all the transactions from the queue
166                         while ( ( transaction = readTransactions.peek() ) != null )
167                         {
168                             nbTxns++;
169 
170                             if ( transaction.isClosed() )
171                             {
172                                 // The transaction is already closed, remove it from the queue
173                                 readTransactions.poll();
174                                 continue;
175                             }
176 
177                             // Check if the transaction has timed out
178                             if ( transaction.getCreationDate() < timeoutDate )
179                             {
180                                 transaction.close();
181                                 readTransactions.poll();
182                                 continue;
183                             }
184 
185                             // We need to stop now
186                             break;
187                         }
188 
189                         long t1 = System.currentTimeMillis();
190 
191                         if ( nbTxns > 0 )
192                         {
193                             System.out.println( "Processing old txn : " + nbTxns + ", " + ( t1 - t0 ) + "ms" );
194                         }
195 
196                         // Wait until we reach the timeout
197                         Thread.sleep( readTimeOut );
198                     }
199                 }
200                 catch ( InterruptedException ie )
201                 {
202                     //System.out.println( "Interrupted" );
203                 }
204                 catch ( Exception e )
205                 {
206                     throw new RuntimeException( e );
207                 }
208             }
209         };
210 
211         readTransactionsThread = new Thread( readTransactionTask );
212         readTransactionsThread.setDaemon( true );
213         readTransactionsThread.start();
214     }
215 
216 
217     /**
218      * Creates a new BTree, with no initialization. 
219      */
220     public BTree()
221     {
222         btreeHeader = new BTreeHeader();
223         type = BTreeTypeEnum.IN_MEMORY;
224     }
225 
226 
227     /**
228      * Creates a new in-memory BTree using the BTreeConfiguration to initialize the 
229      * BTree
230      * 
231      * @param comparator The comparator to use
232      */
233     public BTree( BTreeConfiguration<K, V> configuration ) throws IOException
234     {
235         String name = configuration.getName();
236 
237         if ( name == null )
238         {
239             throw new IllegalArgumentException( "BTree name cannot be null" );
240         }
241 
242         String filePath = configuration.getFilePath();
243 
244         if ( filePath != null )
245         {
246             envDir = new File( filePath );
247         }
248 
249         btreeHeader = new BTreeHeader();
250         btreeHeader.setName( name );
251         btreeHeader.setPageSize( configuration.getPageSize() );
252 
253         keySerializer = configuration.getKeySerializer();
254         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
255 
256         valueSerializer = configuration.getValueSerializer();
257         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
258 
259         comparator = keySerializer.getComparator();
260         readTimeOut = configuration.getReadTimeOut();
261         writeBufferSize = configuration.getWriteBufferSize();
262         btreeHeader.setAllowDuplicates( configuration.isAllowDuplicates() );
263         type = configuration.getType();
264         cacheSize = configuration.getCacheSize();
265 
266         if ( comparator == null )
267         {
268             throw new IllegalArgumentException( "Comparator should not be null" );
269         }
270 
271         // Create the first root page, with revision 0L. It will be empty
272         // and increment the revision at the same time
273         rootPage = new Leaf<K, V>( this );
274 
275         // Now, initialize the BTree
276         init();
277     }
278 
279 
280     /**
281      * Creates a new in-memory BTree with a default page size and key/value serializers.
282      * 
283      * @param comparator The comparator to use
284      */
285     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer )
286         throws IOException
287     {
288         this( name, keySerializer, valueSerializer, false );
289     }
290 
291 
292     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer,
293         boolean allowDuplicates )
294         throws IOException
295     {
296         this( name, null, keySerializer, valueSerializer, DEFAULT_PAGE_SIZE, allowDuplicates, DEFAULT_CACHE_SIZE );
297     }
298 
299 
300     /**
301      * Creates a new in-memory BTree with a default page size and key/value serializers.
302      * 
303      * @param comparator The comparator to use
304      */
305     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer, int pageSize )
306         throws IOException
307     {
308         this( name, null, keySerializer, valueSerializer, pageSize );
309     }
310 
311 
312     /**
313      * Creates a new BTree with a default page size and a comparator, with an associated file.
314      * @param comparator The comparator to use
315      * @param serializer The serializer to use
316      */
317     public BTree( String name, String path, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer )
318         throws IOException
319     {
320         this( name, path, keySerializer, valueSerializer, DEFAULT_PAGE_SIZE );
321     }
322 
323 
324     /**
325      * 
326      * Creates a new instance of BTree with the given name and store it under the given dataDir if provided.
327      *
328      * @param name the name of the BTree
329      * @param dataDir the name of the data directory with absolute path
330      * @param keySerializer key serializer
331      * @param valueSerializer value serializer
332      * @param pageSize size of the page
333      * @throws IOException
334      */
335     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
336         ElementSerializer<V> valueSerializer,
337         int pageSize )
338         throws IOException
339     {
340         this( name, dataDir, keySerializer, valueSerializer, pageSize, false, DEFAULT_CACHE_SIZE );
341     }
342 
343 
344     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
345         ElementSerializer<V> valueSerializer,
346         int pageSize, boolean allowDuplicates )
347         throws IOException
348     {
349         this( name, dataDir, keySerializer, valueSerializer, pageSize, allowDuplicates, DEFAULT_CACHE_SIZE );
350     }
351 
352 
353     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
354         ElementSerializer<V> valueSerializer,
355         int pageSize, boolean allowDuplicates, int cacheSize )
356         throws IOException
357     {
358         btreeHeader = new BTreeHeader();
359         btreeHeader.setName( name );
360 
361         if ( dataDir != null )
362         {
363             envDir = new File( dataDir );
364         }
365 
366         setPageSize( pageSize );
367         writeBufferSize = DEFAULT_WRITE_BUFFER_SIZE;
368 
369         this.cacheSize = cacheSize;
370 
371         this.keySerializer = keySerializer;
372 
373         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
374 
375         this.valueSerializer = valueSerializer;
376 
377         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
378 
379         comparator = keySerializer.getComparator();
380 
381         btreeHeader.setAllowDuplicates( allowDuplicates );
382 
383         // Create the first root page, with revision 0L. It will be empty
384         // and increment the revision at the same time
385         rootPage = new Leaf<K, V>( this );
386 
387         // Now, call the init() method
388         init();
389     }
390 
391 
392     /**
393      * Initialize the BTree.
394      * 
395      * @throws IOException If we get some exception while initializing the BTree
396      */
397     public void init() throws IOException
398     {
399         // if not in-memory then default to persist mode instead of managed
400         if ( envDir != null )
401         {
402             if ( !envDir.exists() )
403             {
404                 boolean created = envDir.mkdirs();
405                 if ( !created )
406                 {
407                     throw new IllegalStateException( "Could not create the directory " + envDir + " for storing data" );
408                 }
409             }
410 
411             this.file = new File( envDir, btreeHeader.getName() + DATA_SUFFIX );
412 
413             this.journal = new File( envDir, file.getName() + JOURNAL_SUFFIX );
414             type = BTreeTypeEnum.PERSISTENT;
415         }
416 
417         // Create the queue containing the pending read transactions
418         readTransactions = new ConcurrentLinkedQueue<Transaction<K, V>>();
419 
420         // We will extract the Type to use for keys, using the comparator for that
421         Class<?> comparatorClass = comparator.getClass();
422         Type[] types = comparatorClass.getGenericInterfaces();
423 
424         if ( types[0] instanceof Class )
425         {
426             keyType = ( Class<?> ) types[0];
427         }
428         else
429         {
430             Type[] argumentTypes = ( ( ParameterizedType ) types[0] ).getActualTypeArguments();
431 
432             if ( ( argumentTypes != null ) && ( argumentTypes.length > 0 ) && ( argumentTypes[0] instanceof Class<?> ) )
433             {
434                 keyType = ( Class<?> ) argumentTypes[0];
435             }
436         }
437 
438         writeLock = new ReentrantLock();
439 
440         // Check the files and create them if missing
441         // Create the queue containing the modifications, if it's not a in-memory btree
442         if ( type == BTreeTypeEnum.PERSISTENT )
443         {
444             if ( file.length() > 0 )
445             {
446                 // We have some existing file, load it 
447                 load( file );
448             }
449 
450             withJournal = true;
451 
452             FileOutputStream stream = new FileOutputStream( journal );
453             journalChannel = stream.getChannel();
454 
455             // If the journal is not empty, we have to read it
456             // and to apply all the modifications to the current file
457             if ( journal.length() > 0 )
458             {
459                 applyJournal();
460             }
461         }
462         else if ( type == null )
463         {
464             type = BTreeTypeEnum.IN_MEMORY;
465         }
466 
467         // Initialize the caches
468         CacheConfiguration cacheConfiguration = new CacheConfiguration();
469         cacheConfiguration.setName( "pages" );
470         cacheConfiguration.setEternal( true );
471         cacheConfiguration.setOverflowToDisk( false );
472         cacheConfiguration.setCacheLoaderTimeoutMillis( 0 );
473         cacheConfiguration.setMaxElementsInMemory( cacheSize );
474         cacheConfiguration.setMemoryStoreEvictionPolicy( "LRU" );
475 
476         cache = new Cache( cacheConfiguration );
477         cache.initialise();
478 
479         // Initialize the txnManager thread
480         //FIXME we should NOT create a new transaction manager thread for each BTree
481         //createTransactionManager();
482     }
483 
484 
485     /**
486      * Return the cache we use in this BTree
487      */
488     /* No qualifier */Cache getCache()
489     {
490         return cache;
491     }
492 
493 
494     /**
495      * Close the BTree, cleaning up all the data structure
496      */
497     public void close() throws IOException
498     {
499         // Stop the readTransaction thread
500         // readTransactionsThread.interrupt();
501         // readTransactions.clear();
502 
503         if ( type == BTreeTypeEnum.PERSISTENT )
504         {
505             // Flush the data
506             flush();
507             journalChannel.close();
508         }
509 
510         rootPage = null;
511     }
512 
513 
514     /**
515      * @return the btreeOffset
516      */
517     /* No qualifier*/long getBtreeOffset()
518     {
519         return btreeHeader.getBTreeOffset();
520     }
521 
522 
523     /**
524      * @param btreeOffset the btreeOffset to set
525      */
526     /* No qualifier*/void setBtreeOffset( long btreeOffset )
527     {
528         btreeHeader.setBTreeOffset( btreeOffset );
529     }
530 
531 
532     /**
533      * @return the rootPageOffset
534      */
535     /* No qualifier*/long getRootPageOffset()
536     {
537         return btreeHeader.getRootPageOffset();
538     }
539 
540 
541     /**
542      * @param rootPageOffset the rootPageOffset to set
543      */
544     /* No qualifier*/void setRootPageOffset( long rootPageOffset )
545     {
546         btreeHeader.setRootPageOffset( rootPageOffset );
547     }
548 
549 
550     /**
551      * @return the nextBTreeOffset
552      */
553     /* No qualifier*/long getNextBTreeOffset()
554     {
555         return btreeHeader.getNextBTreeOffset();
556     }
557 
558 
559     /**
560      * @param nextBTreeOffset the nextBTreeOffset to set
561      */
562     /* No qualifier*/void setNextBTreeOffset( long nextBTreeOffset )
563     {
564         btreeHeader.setNextBTreeOffset( nextBTreeOffset );
565     }
566 
567 
568     /**
569      * Gets the number which is a power of 2 immediately above the given positive number.
570      */
571     private int getPowerOf2( int size )
572     {
573         int newSize = --size;
574         newSize |= newSize >> 1;
575         newSize |= newSize >> 2;
576         newSize |= newSize >> 4;
577         newSize |= newSize >> 8;
578         newSize |= newSize >> 16;
579         newSize++;
580 
581         return newSize;
582     }
583 
584 
585     /**
586      * Set the maximum number of elements we can store in a page. This must be a
587      * number greater than 1, and a power of 2. The default page size is 16.
588      * <br/>
589      * If the provided size is below 2, we will default to DEFAULT_PAGE_SIZE.<br/>
590      * If the provided size is not a power of 2, we will select the closest power of 2
591      * higher than the given number<br/>
592      * 
593      * @param pageSize The requested page size
594      */
595     public void setPageSize( int pageSize )
596     {
597         if ( pageSize <= 2 )
598         {
599             btreeHeader.setPageSize( DEFAULT_PAGE_SIZE );
600         }
601         else
602         {
603             btreeHeader.setPageSize( getPowerOf2( pageSize ) );
604         }
605     }
606 
607 
608     /**
609      * Set the new root page for this tree. Used for debug purpose only. The revision
610      * will always be 0;
611      * 
612      * @param root the new root page.
613      */
614     /* No qualifier */void setRoot( Page<K, V> root )
615     {
616         rootPage = root;
617     }
618 
619 
620     /**
621      * @return the pageSize
622      */
623     public int getPageSize()
624     {
625         return btreeHeader.getPageSize();
626     }
627 
628 
629     /**
630      * Generates a new revision number. It's only used by the Page instances.
631      * 
632      * @return a new incremental revision number
633      */
634     /** No qualifier */
635     long generateRevision()
636     {
637         return btreeHeader.incrementRevision();
638     }
639 
640 
641     /**
642      * Insert an entry in the BTree.
643      * <p>
644      * We will replace the value if the provided key already exists in the
645      * btree.
646      *
647      * @param key Inserted key
648      * @param value Inserted value
649      * @return Existing value, if any.
650      * @throws IOException TODO
651      */
652     public V insert( K key, V value ) throws IOException
653     {
654         long revision = generateRevision();
655 
656         V existingValue = null;
657 
658         try
659         {
660             // Commented atm, we will have to play around the idea of transactions later
661             writeLock.lock();
662 
663             InsertResult<K, V> result = insert( key, value, revision );
664 
665             if ( result instanceof ModifyResult )
666             {
667                 existingValue = ( ( ModifyResult<K, V> ) result ).getModifiedValue();
668             }
669         }
670         finally
671         {
672             // See above
673             writeLock.unlock();
674         }
675 
676         return existingValue;
677     }
678 
679 
680     /**
681      * Delete the entry which key is given as a parameter. If the entry exists, it will
682      * be removed from the tree, the old tuple will be returned. Otherwise, null is returned.
683      * 
684      * @param key The key for the entry we try to remove
685      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
686      */
687     public Tuple<K, V> delete( K key ) throws IOException
688     {
689         if ( key == null )
690         {
691             throw new IllegalArgumentException( "Key must not be null" );
692         }
693 
694         long revision = generateRevision();
695 
696         Tuple<K, V> deleted = delete( key, revision );
697 
698         return deleted;
699     }
700 
701 
702     /**
703      * Delete the value from an entry associated with the given key. If the value
704      * If the value is present, it will be deleted first, later if there are no other 
705      * values associated with this key(which can happen when duplicates are enabled), 
706      * we will remove the key from the tree.
707      * 
708      * @param key The key for the entry we try to remove
709      * @param value The value to delete (can be null)
710      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
711      */
712     public Tuple<K, V> delete( K key, V value ) throws IOException
713     {
714         if ( key == null )
715         {
716             throw new IllegalArgumentException( "Key must not be null" );
717         }
718 
719         if ( value == null )
720         {
721             throw new IllegalArgumentException( "Value must not be null" );
722         }
723 
724         long revision = generateRevision();
725 
726         Tuple<K, V> deleted = delete( key, value, revision );
727 
728         return deleted;
729     }
730 
731 
732     /**
733      * Delete the entry which key is given as a parameter. If the entry exists, it will
734      * be removed from the tree, the old tuple will be returned. Otherwise, null is returned.
735      * 
736      * @param key The key for the entry we try to remove
737      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
738      */
739     private Tuple<K, V> delete( K key, long revision ) throws IOException
740     {
741         return delete( key, null, revision );
742     }
743 
744 
745     /**
746      * 
747      * Deletes the given <key,value> pair if both key and value match. If the given value is null
748      * and there is no null value associated with the given key then the entry with the given key
749      * will be removed.
750      *
751      * @param key The key to be removed 
752      * @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)
753      * @param revision The revision to be associated with this operation
754      * @return
755      * @throws IOException
756      */
757     private Tuple<K, V> delete( K key, V value, long revision ) throws IOException
758     {
759         writeLock.lock();
760 
761         try
762         {
763             // If the key exists, the existing value will be replaced. We store it
764             // to return it to the caller.
765             Tuple<K, V> tuple = null;
766 
767             // Try to delete the entry starting from the root page. Here, the root
768             // page may be either a Node or a Leaf
769             DeleteResult<K, V> result = rootPage.delete( revision, key, value, null, -1 );
770 
771             if ( result instanceof NotPresentResult )
772             {
773                 // Key not found.
774                 return null;
775             }
776 
777             // Keep the oldRootPage so that we can later access it
778             Page<K, V> oldRootPage = rootPage;
779 
780             if ( result instanceof RemoveResult )
781             {
782                 // The element was found, and removed
783                 RemoveResult<K, V> removeResult = ( RemoveResult<K, V> ) result;
784 
785                 Page<K, V> modifiedPage = removeResult.getModifiedPage();
786 
787                 // This is a new root
788                 rootPage = modifiedPage;
789                 tuple = removeResult.getRemovedElement();
790             }
791 
792             if ( withJournal )
793             {
794                 // Inject the modification into the modification queue
795                 writeToJournal( new Deletion<K, V>( key ) );
796             }
797 
798             // Decrease the number of elements in the current tree if the deletion is successful
799             if ( tuple != null )
800             {
801                 btreeHeader.decrementNbElems();
802             }
803 
804             // Return the value we have found if it was modified
805             return tuple;
806         }
807         finally
808         {
809             // See above
810             writeLock.unlock();
811         }
812     }
813 
814 
815     /**
816      * Find a value in the tree, given its key. If the key is not found,
817      * it will throw a KeyNotFoundException. <br/>
818      * Note that we can get a null value stored, or many values.
819      * 
820      * @param key The key we are looking at
821      * @return The found value, or null if the key is not present in the tree
822      * @throws KeyNotFoundException If the key is not found in the BTree
823      * @throws IOException TODO
824      */
825     public V get( K key ) throws IOException, KeyNotFoundException
826     {
827         return rootPage.get( key );
828     }
829 
830 
831     /**
832      * @see Page#getValues(Object)
833      */
834     public DuplicateKeyVal<V> getValues( K key ) throws IOException, KeyNotFoundException
835     {
836         return rootPage.getValues( key );
837     }
838 
839 
840     /**
841      * Find a value in the tree, given its key, at a specific revision. If the key is not found,
842      * it will throw a KeyNotFoundException. <br/>
843      * Note that we can get a null value stored, or many values.
844      * 
845      * @param revision The revision for which we want to find a key
846      * @param key The key we are looking at
847      * @return The found value, or null if the key is not present in the tree
848      * @throws KeyNotFoundException If the key is not found in the BTree
849      * @throws IOException If there was an issue while fetching data from the disk
850      */
851     public V get( long revision, K key ) throws IOException, KeyNotFoundException
852     {
853         // Fetch the root page for this revision
854         Page<K, V> revisionRootPage = getRootPage( revision );
855 
856         return revisionRootPage.get( key );
857     }
858 
859 
860     /**
861      * Checks if the given key exists.
862      *  
863      * @param key The key we are looking at
864      * @return true if the key is present, false otherwise
865      * @throws IOException If we have an error while trying to access the page
866      */
867     public boolean hasKey( K key ) throws IOException
868     {
869         if ( key == null )
870         {
871             return false;
872         }
873 
874         return rootPage.hasKey( key );
875     }
876 
877 
878     /**
879      * Checks if the given key exists for a given revision.
880      *  
881      * @param revision The revision for which we want to find a key
882      * @param key The key we are looking at
883      * @return true if the key is present, false otherwise
884      * @throws IOException If we have an error while trying to access the page
885      * @throws KeyNotFoundException If the key is not found in the BTree
886      */
887     public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException
888     {
889         if ( key == null )
890         {
891             return false;
892         }
893 
894         // Fetch the root page for this revision
895         Page<K, V> revisionRootPage = getRootPage( revision );
896 
897         return revisionRootPage.hasKey( key );
898     }
899 
900 
901     /**
902      * Checks if the BTree contains the given key with the given value.
903      * 
904      * @param key The key we are looking for
905      * @param value The value associated with the given key
906      * @return true if the key and value are associated with each other, false otherwise
907      */
908     public boolean contains( K key, V value ) throws IOException
909     {
910         return rootPage.contains( key, value );
911     }
912 
913 
914     /**
915      * Checks if the BTree contains the given key with the given value for a given revision
916      * 
917      * @param revision The revision we would like to browse
918      * @param key The key we are looking for
919      * @param value The value associated with the given key
920      * @return true if the key and value are associated with each other, false otherwise
921      * @throws KeyNotFoundException If the key is not found in the BTree
922      */
923     public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException
924     {
925         // Fetch the root page for this revision
926         Page<K, V> revisionRootPage = getRootPage( revision );
927 
928         return revisionRootPage.contains( key, value );
929     }
930 
931 
932     /**
933      * Creates a cursor starting at the beginning of the tree
934      * 
935      * @return A cursor on the btree
936      * @throws IOException
937      */
938     public TupleCursor<K, V> browse() throws IOException
939     {
940         Transaction<K, V> transaction = beginReadTransaction();
941 
942         // Fetch the root page for this revision
943         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
944 
945         TupleCursor<K, V> cursor = rootPage.browse( transaction, stack );
946 
947         return cursor;
948     }
949 
950 
951     /**
952      * Creates a cursor starting at the beginning of the tree, for a given revision
953      * 
954      * @param revision The revision we would like to browse
955      * @return A cursor on the btree
956      * @throws IOException If we had an issue while fetching data from the disk
957      * @throws KeyNotFoundException If the key is not found in the BTree
958      */
959     public TupleCursor<K, V> browse( long revision ) throws IOException, KeyNotFoundException
960     {
961         Transaction<K, V> transaction = beginReadTransaction();
962 
963         // Fetch the root page for this revision
964         Page<K, V> revisionRootPage = getRootPage( revision );
965 
966         // And get the cursor
967         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
968         TupleCursor<K, V> cursor = revisionRootPage.browse( transaction, stack );
969 
970         return cursor;
971     }
972 
973 
974     /**
975      * Creates a cursor starting on the given key
976      * 
977      * @param key The key which is the starting point. If the key is not found,
978      * then the cursor will always return null.
979      * @return A cursor on the btree
980      * @throws IOException
981      */
982     public TupleCursor<K, V> browseFrom( K key ) throws IOException
983     {
984         Transaction<K, V> transaction = beginReadTransaction();
985 
986         // Fetch the root page for this revision
987         TupleCursor<K, V> cursor = rootPage.browse( key, transaction, new LinkedList<ParentPos<K, V>>() );
988 
989         return cursor;
990     }
991 
992 
993     /**
994      * Creates a cursor starting on the given key at the given revision
995      * 
996      * @param The revision we are looking for
997      * @param key The key which is the starting point. If the key is not found,
998      * then the cursor will always return null.
999      * @return A cursor on the btree
1000      * @throws IOException If wxe had an issue reading the BTree from disk
1001      * @throws KeyNotFoundException  If we can't find a rootPage for this revision
1002      */
1003     public TupleCursor<K, V> browseFrom( long revision, K key ) throws IOException, KeyNotFoundException
1004     {
1005         Transaction<K, V> transaction = beginReadTransaction();
1006 
1007         // Fetch the rootPage for this revision
1008         Page<K, V> revisionRootPage = getRootPage( revision );
1009 
1010         // And get the cursor
1011         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
1012         TupleCursor<K, V> cursor = revisionRootPage.browse( key, transaction, stack );
1013 
1014         return cursor;
1015     }
1016 
1017 
1018     /**
1019      * Insert an entry in the BTree.
1020      * <p>
1021      * We will replace the value if the provided key already exists in the
1022      * btree.
1023      * <p>
1024      * The revision number is the revision to use to insert the data.
1025      *
1026      * @param key Inserted key
1027      * @param value Inserted value
1028      * @param revision The revision to use
1029      * @return an instance of the InsertResult.
1030      */
1031     /*No qualifier*/InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
1032     {
1033         if ( key == null )
1034         {
1035             throw new IllegalArgumentException( "Key must not be null" );
1036         }
1037 
1038         // If the key exists, the existing value will be replaced. We store it
1039         // to return it to the caller.
1040         V modifiedValue = null;
1041 
1042         // Try to insert the new value in the tree at the right place,
1043         // starting from the root page. Here, the root page may be either
1044         // a Node or a Leaf
1045         InsertResult<K, V> result = rootPage.insert( revision, key, value );
1046 
1047         if ( result instanceof ModifyResult )
1048         {
1049             ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result );
1050 
1051             Page<K, V> modifiedPage = modifyResult.getModifiedPage();
1052 
1053             // The root has just been modified, we haven't split it
1054             // Get it and make it the current root page
1055             rootPage = modifiedPage;
1056 
1057             modifiedValue = modifyResult.getModifiedValue();
1058         }
1059         else
1060         {
1061             // We have split the old root, create a new one containing
1062             // only the pivotal we got back
1063             SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result );
1064 
1065             K pivot = splitResult.getPivot();
1066             Page<K, V> leftPage = splitResult.getLeftPage();
1067             Page<K, V> rightPage = splitResult.getRightPage();
1068             Page<K, V> newRootPage = null;
1069 
1070             // Create the new rootPage
1071             newRootPage = new Node<K, V>( this, revision, pivot, leftPage, rightPage );
1072 
1073             rootPage = newRootPage;
1074         }
1075 
1076         // Inject the modification into the modification queue
1077         if ( withJournal )
1078         {
1079             writeToJournal( new Addition<K, V>( key, value ) );
1080         }
1081 
1082         // Increase the number of element in the current tree if the insertion is successful
1083         // and does not replace an element
1084         if ( modifiedValue == null )
1085         {
1086             btreeHeader.incrementNbElems();
1087         }
1088 
1089         // Return the value we have found if it was modified
1090         return result;
1091     }
1092 
1093 
1094     /**
1095      * Starts a Read Only transaction. If the transaction is not closed, it will be 
1096      * automatically closed after the timeout
1097      * @return The created transaction
1098      */
1099     private Transaction<K, V> beginReadTransaction()
1100     {
1101         Transaction<K, V> readTransaction = new Transaction<K, V>( rootPage, btreeHeader.getRevision() - 1,
1102             System.currentTimeMillis() );
1103 
1104         readTransactions.add( readTransaction );
1105 
1106         return readTransaction;
1107     }
1108 
1109 
1110     /**
1111      * @return the type for the keys
1112      */
1113     /* No qualifier*/Class<?> getKeyType()
1114     {
1115         return keyType;
1116     }
1117 
1118 
1119     /**
1120      * @return the comparator
1121      */
1122     public Comparator<K> getComparator()
1123     {
1124         return comparator;
1125     }
1126 
1127 
1128     /**
1129      * @param comparator the comparator to set
1130      */
1131     public void setComparator( Comparator<K> comparator )
1132     {
1133         this.comparator = comparator;
1134     }
1135 
1136 
1137     /**
1138      * @param keySerializer the Key serializer to set
1139      */
1140     public void setKeySerializer( ElementSerializer<K> keySerializer )
1141     {
1142         this.keySerializer = keySerializer;
1143         this.comparator = keySerializer.getComparator();
1144         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
1145     }
1146 
1147 
1148     /**
1149      * @param valueSerializer the Value serializer to set
1150      */
1151     public void setValueSerializer( ElementSerializer<V> valueSerializer )
1152     {
1153         this.valueSerializer = valueSerializer;
1154         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
1155     }
1156 
1157 
1158     /**
1159      * Write the data in the ByteBuffer, and eventually on disk if needed.
1160      * 
1161      * @param channel The channel we want to write to
1162      * @param bb The ByteBuffer we want to feed
1163      * @param buffer The data to inject
1164      * @throws IOException If the write failed
1165      */
1166     private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException
1167     {
1168         int size = buffer.length;
1169         int pos = 0;
1170 
1171         // Loop until we have written all the data
1172         do
1173         {
1174             if ( bb.remaining() >= size )
1175             {
1176                 // No flush, as the ByteBuffer is big enough
1177                 bb.put( buffer, pos, size );
1178                 size = 0;
1179             }
1180             else
1181             {
1182                 // Flush the data on disk, reinitialize the ByteBuffer
1183                 int len = bb.remaining();
1184                 size -= len;
1185                 bb.put( buffer, pos, len );
1186                 pos += len;
1187 
1188                 bb.flip();
1189 
1190                 channel.write( bb );
1191 
1192                 bb.clear();
1193             }
1194         }
1195         while ( size > 0 );
1196     }
1197 
1198 
1199     /**
1200      * Flush the latest revision to disk
1201      * @param file The file into which the data will be written
1202      */
1203     public void flush( File file ) throws IOException
1204     {
1205         File parentFile = file.getParentFile();
1206         File baseDirectory = null;
1207 
1208         if ( parentFile != null )
1209         {
1210             baseDirectory = new File( file.getParentFile().getAbsolutePath() );
1211         }
1212         else
1213         {
1214             baseDirectory = new File( "." );
1215         }
1216 
1217         // Create a temporary file in the same directory to flush the current btree
1218         File tmpFileFD = File.createTempFile( "mavibot", null, baseDirectory );
1219         FileOutputStream stream = new FileOutputStream( tmpFileFD );
1220         FileChannel ch = stream.getChannel();
1221 
1222         // Create a buffer containing 200 4Kb pages (around 1Mb)
1223         ByteBuffer bb = ByteBuffer.allocateDirect( writeBufferSize );
1224 
1225         TupleCursor<K, V> cursor = browse();
1226 
1227         if ( keySerializer == null )
1228         {
1229             throw new RuntimeException( "Cannot flush the btree without a Key serializer" );
1230         }
1231 
1232         if ( valueSerializer == null )
1233         {
1234             throw new RuntimeException( "Cannot flush the btree without a Value serializer" );
1235         }
1236 
1237         // Write the number of elements first
1238         bb.putLong( btreeHeader.getNbElems() );
1239 
1240         while ( cursor.hasNext() )
1241         {
1242             Tuple<K, V> tuple = cursor.next();
1243 
1244             byte[] keyBuffer = keySerializer.serialize( tuple.getKey() );
1245 
1246             writeBuffer( ch, bb, keyBuffer );
1247 
1248             byte[] valueBuffer = valueSerializer.serialize( tuple.getValue() );
1249 
1250             writeBuffer( ch, bb, valueBuffer );
1251         }
1252 
1253         // Write the buffer if needed
1254         if ( bb.position() > 0 )
1255         {
1256             bb.flip();
1257             ch.write( bb );
1258         }
1259 
1260         // Flush to the disk for real
1261         ch.force( true );
1262         ch.close();
1263 
1264         // Rename the current file to save a backup
1265         File backupFile = File.createTempFile( "mavibot", null, baseDirectory );
1266         file.renameTo( backupFile );
1267 
1268         // Rename the temporary file to the initial file
1269         tmpFileFD.renameTo( file );
1270 
1271         // We can now delete the backup file
1272         backupFile.delete();
1273     }
1274 
1275 
1276     /** 
1277      * Inject all the modification from the journal into the btree
1278      * 
1279      * @throws IOException If we had some issue while reading the journal
1280      */
1281     private void applyJournal() throws IOException
1282     {
1283         long revision = generateRevision();
1284 
1285         if ( !journal.exists() )
1286         {
1287             throw new IOException( "The journal does not exist" );
1288         }
1289 
1290         FileChannel channel =
1291             new RandomAccessFile( journal, "rw" ).getChannel();
1292         ByteBuffer buffer = ByteBuffer.allocate( 65536 );
1293 
1294         BufferHandler bufferHandler = new BufferHandler( channel, buffer );
1295 
1296         // Loop on all the elements, store them in lists atm
1297         try
1298         {
1299             while ( true )
1300             {
1301                 // Read the type 
1302                 byte[] type = bufferHandler.read( 1 );
1303 
1304                 if ( type[0] == Modification.ADDITION )
1305                 {
1306                     // Read the key
1307                     K key = keySerializer.deserialize( bufferHandler );
1308 
1309                     //keys.add( key );
1310 
1311                     // Read the value
1312                     V value = valueSerializer.deserialize( bufferHandler );
1313 
1314                     //values.add( value );
1315 
1316                     // Inject the data in the tree. (to be replaced by a bulk load)
1317                     insert( key, value, revision );
1318                 }
1319                 else
1320                 {
1321                     // Read the key
1322                     K key = keySerializer.deserialize( bufferHandler );
1323 
1324                     // Remove the key from the tree
1325                     delete( key, revision );
1326                 }
1327             }
1328         }
1329         catch ( EOFException eofe )
1330         {
1331             eofe.printStackTrace();
1332             // Done reading the journal. truncate it
1333             journalChannel.truncate( 0 );
1334         }
1335     }
1336 
1337 
1338     /**
1339      * Read the data from the disk into this BTree. All the existing data in the 
1340      * BTree are kept, the read data will be associated with a new revision.
1341      * 
1342      * @param file
1343      * @throws IOException
1344      */
1345     public void load( File file ) throws IOException
1346     {
1347         long revision = generateRevision();
1348 
1349         if ( !file.exists() )
1350         {
1351             throw new IOException( "The file does not exist" );
1352         }
1353 
1354         FileChannel channel =
1355             new RandomAccessFile( file, "rw" ).getChannel();
1356         ByteBuffer buffer = ByteBuffer.allocate( 65536 );
1357 
1358         BufferHandler bufferHandler = new BufferHandler( channel, buffer );
1359 
1360         long nbElems = LongSerializer.deserialize( bufferHandler.read( 8 ) );
1361         btreeHeader.setNbElems( nbElems );
1362 
1363         // Prepare a list of keys and values read from the disk
1364         //List<K> keys = new ArrayList<K>();
1365         //List<V> values = new ArrayList<V>();
1366 
1367         // desactivate the journal while we load the file
1368         boolean isJournalActivated = withJournal;
1369 
1370         withJournal = false;
1371 
1372         // Loop on all the elements, store them in lists atm
1373         for ( long i = 0; i < nbElems; i++ )
1374         {
1375             // Read the key
1376             K key = keySerializer.deserialize( bufferHandler );
1377 
1378             //keys.add( key );
1379 
1380             // Read the value
1381             V value = valueSerializer.deserialize( bufferHandler );
1382 
1383             //values.add( value );
1384 
1385             // Inject the data in the tree. (to be replaced by a bulk load)
1386             insert( key, value, revision );
1387         }
1388 
1389         // Restore the withJournal value
1390         withJournal = isJournalActivated;
1391 
1392         // Now, process the lists to create the btree
1393         // TODO... BulkLoad
1394     }
1395 
1396 
1397     /**
1398      * Get the rootPzge associated to a give revision.
1399      * 
1400      * @param revision The revision we are looking for
1401      * @return The rootPage associated to this revision
1402      * @throws IOException If we had an issue while accessing the underlying file
1403      * @throws KeyNotFoundException If the revision does not exist for this Btree
1404      */
1405     private Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException
1406     {
1407         // Atm, the in-memory BTree does not support searches in many revisions
1408         return rootPage;
1409     }
1410 
1411 
1412     /**
1413      * Flush the latest revision to disk. We will replace the current file by the new one, as
1414      * we flush in a temporary file.
1415      */
1416     public void flush() throws IOException
1417     {
1418         if ( type == BTreeTypeEnum.PERSISTENT )
1419         {
1420             // Then flush the file
1421             flush( file );
1422             journalChannel.truncate( 0 );
1423         }
1424     }
1425 
1426 
1427     /**
1428      * @return the readTimeOut
1429      */
1430     public long getReadTimeOut()
1431     {
1432         return readTimeOut;
1433     }
1434 
1435 
1436     /**
1437      * @param readTimeOut the readTimeOut to set
1438      */
1439     public void setReadTimeOut( long readTimeOut )
1440     {
1441         this.readTimeOut = readTimeOut;
1442     }
1443 
1444 
1445     /**
1446      * @return the name
1447      */
1448     public String getName()
1449     {
1450         return btreeHeader.getName();
1451     }
1452 
1453 
1454     /**
1455      * @param name the name to set
1456      */
1457     public void setName( String name )
1458     {
1459         btreeHeader.setName( name );
1460     }
1461 
1462 
1463     /**
1464      * @return the file
1465      */
1466     public File getFile()
1467     {
1468         return file;
1469     }
1470 
1471 
1472     /**
1473      * @return the journal
1474      */
1475     public File getJournal()
1476     {
1477         return journal;
1478     }
1479 
1480 
1481     /**
1482      * @return the writeBufferSize
1483      */
1484     public int getWriteBufferSize()
1485     {
1486         return writeBufferSize;
1487     }
1488 
1489 
1490     /**
1491      * @param writeBufferSize the writeBufferSize to set
1492      */
1493     public void setWriteBufferSize( int writeBufferSize )
1494     {
1495         this.writeBufferSize = writeBufferSize;
1496     }
1497 
1498 
1499     /**
1500      * @return true if the BTree is fully in memory
1501      */
1502     public boolean isInMemory()
1503     {
1504         return type == BTreeTypeEnum.IN_MEMORY;
1505     }
1506 
1507 
1508     /**
1509      * @return true if the BTree is persisted on disk
1510      */
1511     public boolean isPersistent()
1512     {
1513         return type == BTreeTypeEnum.IN_MEMORY;
1514     }
1515 
1516 
1517     /**
1518      * Create a ValueHolder depending on the kind of holder we want.
1519      * 
1520      * @param value The value to store
1521      * @return The value holder
1522      */
1523     /* no qualifier */ElementHolder<V, K, V> createValueHolder( V value )
1524     {
1525         if ( isAllowDuplicates() )
1526         {
1527             return new MultipleMemoryHolder<K, V>( this, value );
1528         }
1529         else
1530         {
1531             return new MemoryHolder( this, value );
1532         }
1533     }
1534 
1535 
1536     /**
1537      * Create a ValueHolder depending on the kind of holder we want.
1538      * 
1539      * @param value The value to store
1540      * @return The value holder
1541      */
1542     /* no qualifier */ElementHolder<Page<K, V>, K, V> createPageHolder( Page<K, V> value )
1543     {
1544         return new MemoryHolder( this, value );
1545     }
1546 
1547 
1548     /**
1549      * @return the keySerializer
1550      */
1551     public ElementSerializer<K> getKeySerializer()
1552     {
1553         return keySerializer;
1554     }
1555 
1556 
1557     /**
1558      * @return the keySerializer FQCN
1559      */
1560     public String getKeySerializerFQCN()
1561     {
1562         return btreeHeader.getKeySerializerFQCN();
1563     }
1564 
1565 
1566     /**
1567      * @return the valueSerializer
1568      */
1569     public ElementSerializer<V> getValueSerializer()
1570     {
1571         return valueSerializer;
1572     }
1573 
1574 
1575     /**
1576      * @return the valueSerializer FQCN
1577      */
1578     public String getValueSerializerFQCN()
1579     {
1580         return btreeHeader.getValueSerializerFQCN();
1581     }
1582 
1583 
1584     /** 
1585      * @return The current BTree revision
1586      */
1587     public long getRevision()
1588     {
1589         return btreeHeader.getRevision();
1590     }
1591 
1592 
1593     /**
1594      * @param revision the revision to set
1595      */
1596     /* No qualifier */void setRevision( long revision )
1597     {
1598         btreeHeader.setRevision( revision );
1599     }
1600 
1601 
1602     /** 
1603      * @return The current number of elements in the BTree
1604      */
1605     public long getNbElems()
1606     {
1607         return btreeHeader.getNbElems();
1608     }
1609 
1610 
1611     /**
1612      * @param nbElems the nbElems to set
1613      */
1614     /* No qualifier */void setNbElems( long nbElems )
1615     {
1616         btreeHeader.setNbElems( nbElems );
1617     }
1618 
1619 
1620     /**
1621      * @return true if this BTree allow duplicate values
1622      */
1623     public boolean isAllowDuplicates()
1624     {
1625         return btreeHeader.isAllowDuplicates();
1626     }
1627 
1628 
1629     /* No qualifier */void setAllowDuplicates( boolean allowDuplicates )
1630     {
1631         btreeHeader.setAllowDuplicates( allowDuplicates );
1632     }
1633 
1634 
1635     private void writeToJournal( Modification<K, V> modification )
1636         throws IOException
1637     {
1638         if ( modification instanceof Addition )
1639         {
1640             byte[] keyBuffer = keySerializer.serialize( modification.getKey() );
1641             ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 );
1642             bb.put( Modification.ADDITION );
1643             bb.put( keyBuffer );
1644             bb.flip();
1645 
1646             journalChannel.write( bb );
1647 
1648             byte[] valueBuffer = valueSerializer.serialize( modification.getValue() );
1649             bb = ByteBuffer.allocateDirect( valueBuffer.length );
1650             bb.put( valueBuffer );
1651             bb.flip();
1652 
1653             journalChannel.write( bb );
1654         }
1655         else if ( modification instanceof Deletion )
1656         {
1657             byte[] keyBuffer = keySerializer.serialize( modification.getKey() );
1658             ByteBuffer bb = ByteBuffer.allocateDirect( keyBuffer.length + 1 );
1659             bb.put( Modification.DELETION );
1660             bb.put( keyBuffer );
1661             bb.flip();
1662 
1663             journalChannel.write( bb );
1664         }
1665 
1666         // Flush to the disk for real
1667         journalChannel.force( true );
1668     }
1669 
1670 
1671     /**
1672      * @see Object#toString()
1673      */
1674     public String toString()
1675     {
1676         StringBuilder sb = new StringBuilder();
1677 
1678         switch ( type )
1679         {
1680             case IN_MEMORY:
1681                 sb.append( "In-memory " );
1682                 break;
1683 
1684             case PERSISTENT:
1685                 sb.append( "Persistent " );
1686                 break;
1687 
1688         }
1689 
1690         sb.append( "BTree" );
1691         sb.append( "[" ).append( btreeHeader.getName() ).append( "]" );
1692         sb.append( "( pageSize:" ).append( btreeHeader.getPageSize() );
1693 
1694         if ( rootPage != null )
1695         {
1696             sb.append( ", nbEntries:" ).append( btreeHeader.getNbElems() );
1697         }
1698         else
1699         {
1700             sb.append( ", nbEntries:" ).append( 0 );
1701         }
1702 
1703         sb.append( ", comparator:" );
1704 
1705         if ( comparator == null )
1706         {
1707             sb.append( "null" );
1708         }
1709         else
1710         {
1711             sb.append( comparator.getClass().getSimpleName() );
1712         }
1713 
1714         sb.append( ", DuplicatesAllowed: " ).append( btreeHeader.isAllowDuplicates() );
1715 
1716         if ( type == BTreeTypeEnum.PERSISTENT )
1717         {
1718             try
1719             {
1720                 sb.append( ", file : " );
1721 
1722                 if ( file != null )
1723                 {
1724                     sb.append( file.getCanonicalPath() );
1725                 }
1726                 else
1727                 {
1728                     sb.append( "Unknown" );
1729                 }
1730 
1731                 sb.append( ", journal : " );
1732 
1733                 if ( journal != null )
1734                 {
1735                     sb.append( journal.getCanonicalPath() );
1736                 }
1737                 else
1738                 {
1739                     sb.append( "Unkown" );
1740                 }
1741             }
1742             catch ( IOException ioe )
1743             {
1744                 // There is little we can do here...
1745             }
1746         }
1747 
1748         sb.append( ") : \n" );
1749         sb.append( rootPage.dumpPage( "" ) );
1750 
1751         return sb.toString();
1752     }
1753 }