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