1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
57
58
59
60
61
62
63 public class BTree<K, V> implements Closeable
64 {
65
66 protected static final Logger LOG = LoggerFactory.getLogger( BTree.class );
67
68
69 private BTreeHeader btreeHeader;
70
71
72 public static final int DEFAULT_PAGE_SIZE = 16;
73
74
75 public static final int DEFAULT_WRITE_BUFFER_SIZE = 4096 * 250;
76
77
78 public static final String DEFAULT_JOURNAL = "mavibot.log";
79
80
81 public static final String DATA_SUFFIX = ".db";
82
83
84 public static final String JOURNAL_SUFFIX = ".log";
85
86
87 private Comparator<K> comparator;
88
89
90 protected volatile Page<K, V> rootPage;
91
92
93 private ConcurrentLinkedQueue<Transaction<K, V>> readTransactions;
94
95
96 private int writeBufferSize;
97
98
99 protected Class<?> keyType;
100
101
102 private ElementSerializer<K> keySerializer;
103
104
105 private ElementSerializer<V> valueSerializer;
106
107
108 private File file;
109
110
111 private BTreeTypeEnum type;
112
113
114 private boolean withJournal;
115
116
117 private File journal;
118
119
120 private ReentrantLock writeLock;
121
122
123 private Thread readTransactionsThread;
124
125
126 public static final long DEFAULT_READ_TIMEOUT = 10 * 1000L;
127
128
129 private long readTimeOut = DEFAULT_READ_TIMEOUT;
130
131 private File envDir;
132
133 private FileChannel journalChannel = null;
134
135
136 private Cache cache;
137
138
139 private int cacheSize = DEFAULT_CACHE_SIZE;
140
141
142 private static final int DEFAULT_CACHE_SIZE = 1000;
143
144
145
146
147
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
166 while ( ( transaction = readTransactions.peek() ) != null )
167 {
168 nbTxns++;
169
170 if ( transaction.isClosed() )
171 {
172
173 readTransactions.poll();
174 continue;
175 }
176
177
178 if ( transaction.getCreationDate() < timeoutDate )
179 {
180 transaction.close();
181 readTransactions.poll();
182 continue;
183 }
184
185
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
197 Thread.sleep( readTimeOut );
198 }
199 }
200 catch ( InterruptedException ie )
201 {
202
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
219
220 public BTree()
221 {
222 btreeHeader = new BTreeHeader();
223 type = BTreeTypeEnum.IN_MEMORY;
224 }
225
226
227
228
229
230
231
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
272
273 rootPage = new Leaf<K, V>( this );
274
275
276 init();
277 }
278
279
280
281
282
283
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
302
303
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
314
315
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
327
328
329
330
331
332
333
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
384
385 rootPage = new Leaf<K, V>( this );
386
387
388 init();
389 }
390
391
392
393
394
395
396
397 public void init() throws IOException
398 {
399
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
418 readTransactions = new ConcurrentLinkedQueue<Transaction<K, V>>();
419
420
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
441
442 if ( type == BTreeTypeEnum.PERSISTENT )
443 {
444 if ( file.length() > 0 )
445 {
446
447 load( file );
448 }
449
450 withJournal = true;
451
452 FileOutputStream stream = new FileOutputStream( journal );
453 journalChannel = stream.getChannel();
454
455
456
457 if ( journal.length() > 0 )
458 {
459 applyJournal();
460 }
461 }
462 else if ( type == null )
463 {
464 type = BTreeTypeEnum.IN_MEMORY;
465 }
466
467
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
480
481
482 }
483
484
485
486
487
488
489 {
490 return cache;
491 }
492
493
494
495
496
497 public void close() throws IOException
498 {
499
500
501
502
503 if ( type == BTreeTypeEnum.PERSISTENT )
504 {
505
506 flush();
507 journalChannel.close();
508 }
509
510 rootPage = null;
511 }
512
513
514
515
516
517
518 {
519 return btreeHeader.getBTreeOffset();
520 }
521
522
523
524
525
526
527 {
528 btreeHeader.setBTreeOffset( btreeOffset );
529 }
530
531
532
533
534
535
536 {
537 return btreeHeader.getRootPageOffset();
538 }
539
540
541
542
543
544
545 {
546 btreeHeader.setRootPageOffset( rootPageOffset );
547 }
548
549
550
551
552
553
554 {
555 return btreeHeader.getNextBTreeOffset();
556 }
557
558
559
560
561
562
563 {
564 btreeHeader.setNextBTreeOffset( nextBTreeOffset );
565 }
566
567
568
569
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
587
588
589
590
591
592
593
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
610
611
612
613
614
615 {
616 rootPage = root;
617 }
618
619
620
621
622
623 public int getPageSize()
624 {
625 return btreeHeader.getPageSize();
626 }
627
628
629
630
631
632
633
634
635 long generateRevision()
636 {
637 return btreeHeader.incrementRevision();
638 }
639
640
641
642
643
644
645
646
647
648
649
650
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
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
673 writeLock.unlock();
674 }
675
676 return existingValue;
677 }
678
679
680
681
682
683
684
685
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
704
705
706
707
708
709
710
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
734
735
736
737
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
748
749
750
751
752
753
754
755
756
757 private Tuple<K, V> delete( K key, V value, long revision ) throws IOException
758 {
759 writeLock.lock();
760
761 try
762 {
763
764
765 Tuple<K, V> tuple = null;
766
767
768
769 DeleteResult<K, V> result = rootPage.delete( revision, key, value, null, -1 );
770
771 if ( result instanceof NotPresentResult )
772 {
773
774 return null;
775 }
776
777
778 Page<K, V> oldRootPage = rootPage;
779
780 if ( result instanceof RemoveResult )
781 {
782
783 RemoveResult<K, V> removeResult = ( RemoveResult<K, V> ) result;
784
785 Page<K, V> modifiedPage = removeResult.getModifiedPage();
786
787
788 rootPage = modifiedPage;
789 tuple = removeResult.getRemovedElement();
790 }
791
792 if ( withJournal )
793 {
794
795 writeToJournal( new Deletion<K, V>( key ) );
796 }
797
798
799 if ( tuple != null )
800 {
801 btreeHeader.decrementNbElems();
802 }
803
804
805 return tuple;
806 }
807 finally
808 {
809
810 writeLock.unlock();
811 }
812 }
813
814
815
816
817
818
819
820
821
822
823
824
825 public V get( K key ) throws IOException, KeyNotFoundException
826 {
827 return rootPage.get( key );
828 }
829
830
831
832
833
834 public DuplicateKeyVal<V> getValues( K key ) throws IOException, KeyNotFoundException
835 {
836 return rootPage.getValues( key );
837 }
838
839
840
841
842
843
844
845
846
847
848
849
850
851 public V get( long revision, K key ) throws IOException, KeyNotFoundException
852 {
853
854 Page<K, V> revisionRootPage = getRootPage( revision );
855
856 return revisionRootPage.get( key );
857 }
858
859
860
861
862
863
864
865
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
880
881
882
883
884
885
886
887 public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException
888 {
889 if ( key == null )
890 {
891 return false;
892 }
893
894
895 Page<K, V> revisionRootPage = getRootPage( revision );
896
897 return revisionRootPage.hasKey( key );
898 }
899
900
901
902
903
904
905
906
907
908 public boolean contains( K key, V value ) throws IOException
909 {
910 return rootPage.contains( key, value );
911 }
912
913
914
915
916
917
918
919
920
921
922
923 public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException
924 {
925
926 Page<K, V> revisionRootPage = getRootPage( revision );
927
928 return revisionRootPage.contains( key, value );
929 }
930
931
932
933
934
935
936
937
938 public TupleCursor<K, V> browse() throws IOException
939 {
940 Transaction<K, V> transaction = beginReadTransaction();
941
942
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
953
954
955
956
957
958
959 public TupleCursor<K, V> browse( long revision ) throws IOException, KeyNotFoundException
960 {
961 Transaction<K, V> transaction = beginReadTransaction();
962
963
964 Page<K, V> revisionRootPage = getRootPage( revision );
965
966
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
976
977
978
979
980
981
982 public TupleCursor<K, V> browseFrom( K key ) throws IOException
983 {
984 Transaction<K, V> transaction = beginReadTransaction();
985
986
987 TupleCursor<K, V> cursor = rootPage.browse( key, transaction, new LinkedList<ParentPos<K, V>>() );
988
989 return cursor;
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003 public TupleCursor<K, V> browseFrom( long revision, K key ) throws IOException, KeyNotFoundException
1004 {
1005 Transaction<K, V> transaction = beginReadTransaction();
1006
1007
1008 Page<K, V> revisionRootPage = getRootPage( revision );
1009
1010
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
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032 {
1033 if ( key == null )
1034 {
1035 throw new IllegalArgumentException( "Key must not be null" );
1036 }
1037
1038
1039
1040 V modifiedValue = null;
1041
1042
1043
1044
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
1054
1055 rootPage = modifiedPage;
1056
1057 modifiedValue = modifyResult.getModifiedValue();
1058 }
1059 else
1060 {
1061
1062
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
1071 newRootPage = new Node<K, V>( this, revision, pivot, leftPage, rightPage );
1072
1073 rootPage = newRootPage;
1074 }
1075
1076
1077 if ( withJournal )
1078 {
1079 writeToJournal( new Addition<K, V>( key, value ) );
1080 }
1081
1082
1083
1084 if ( modifiedValue == null )
1085 {
1086 btreeHeader.incrementNbElems();
1087 }
1088
1089
1090 return result;
1091 }
1092
1093
1094
1095
1096
1097
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
1112
1113
1114 {
1115 return keyType;
1116 }
1117
1118
1119
1120
1121
1122 public Comparator<K> getComparator()
1123 {
1124 return comparator;
1125 }
1126
1127
1128
1129
1130
1131 public void setComparator( Comparator<K> comparator )
1132 {
1133 this.comparator = comparator;
1134 }
1135
1136
1137
1138
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
1150
1151 public void setValueSerializer( ElementSerializer<V> valueSerializer )
1152 {
1153 this.valueSerializer = valueSerializer;
1154 btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
1155 }
1156
1157
1158
1159
1160
1161
1162
1163
1164
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
1172 do
1173 {
1174 if ( bb.remaining() >= size )
1175 {
1176
1177 bb.put( buffer, pos, size );
1178 size = 0;
1179 }
1180 else
1181 {
1182
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
1201
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
1218 File tmpFileFD = File.createTempFile( "mavibot", null, baseDirectory );
1219 FileOutputStream stream = new FileOutputStream( tmpFileFD );
1220 FileChannel ch = stream.getChannel();
1221
1222
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
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
1254 if ( bb.position() > 0 )
1255 {
1256 bb.flip();
1257 ch.write( bb );
1258 }
1259
1260
1261 ch.force( true );
1262 ch.close();
1263
1264
1265 File backupFile = File.createTempFile( "mavibot", null, baseDirectory );
1266 file.renameTo( backupFile );
1267
1268
1269 tmpFileFD.renameTo( file );
1270
1271
1272 backupFile.delete();
1273 }
1274
1275
1276
1277
1278
1279
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
1297 try
1298 {
1299 while ( true )
1300 {
1301
1302 byte[] type = bufferHandler.read( 1 );
1303
1304 if ( type[0] == Modification.ADDITION )
1305 {
1306
1307 K key = keySerializer.deserialize( bufferHandler );
1308
1309
1310
1311
1312 V value = valueSerializer.deserialize( bufferHandler );
1313
1314
1315
1316
1317 insert( key, value, revision );
1318 }
1319 else
1320 {
1321
1322 K key = keySerializer.deserialize( bufferHandler );
1323
1324
1325 delete( key, revision );
1326 }
1327 }
1328 }
1329 catch ( EOFException eofe )
1330 {
1331 eofe.printStackTrace();
1332
1333 journalChannel.truncate( 0 );
1334 }
1335 }
1336
1337
1338
1339
1340
1341
1342
1343
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
1364
1365
1366
1367
1368 boolean isJournalActivated = withJournal;
1369
1370 withJournal = false;
1371
1372
1373 for ( long i = 0; i < nbElems; i++ )
1374 {
1375
1376 K key = keySerializer.deserialize( bufferHandler );
1377
1378
1379
1380
1381 V value = valueSerializer.deserialize( bufferHandler );
1382
1383
1384
1385
1386 insert( key, value, revision );
1387 }
1388
1389
1390 withJournal = isJournalActivated;
1391
1392
1393
1394 }
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405 private Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException
1406 {
1407
1408 return rootPage;
1409 }
1410
1411
1412
1413
1414
1415
1416 public void flush() throws IOException
1417 {
1418 if ( type == BTreeTypeEnum.PERSISTENT )
1419 {
1420
1421 flush( file );
1422 journalChannel.truncate( 0 );
1423 }
1424 }
1425
1426
1427
1428
1429
1430 public long getReadTimeOut()
1431 {
1432 return readTimeOut;
1433 }
1434
1435
1436
1437
1438
1439 public void setReadTimeOut( long readTimeOut )
1440 {
1441 this.readTimeOut = readTimeOut;
1442 }
1443
1444
1445
1446
1447
1448 public String getName()
1449 {
1450 return btreeHeader.getName();
1451 }
1452
1453
1454
1455
1456
1457 public void setName( String name )
1458 {
1459 btreeHeader.setName( name );
1460 }
1461
1462
1463
1464
1465
1466 public File getFile()
1467 {
1468 return file;
1469 }
1470
1471
1472
1473
1474
1475 public File getJournal()
1476 {
1477 return journal;
1478 }
1479
1480
1481
1482
1483
1484 public int getWriteBufferSize()
1485 {
1486 return writeBufferSize;
1487 }
1488
1489
1490
1491
1492
1493 public void setWriteBufferSize( int writeBufferSize )
1494 {
1495 this.writeBufferSize = writeBufferSize;
1496 }
1497
1498
1499
1500
1501
1502 public boolean isInMemory()
1503 {
1504 return type == BTreeTypeEnum.IN_MEMORY;
1505 }
1506
1507
1508
1509
1510
1511 public boolean isPersistent()
1512 {
1513 return type == BTreeTypeEnum.IN_MEMORY;
1514 }
1515
1516
1517
1518
1519
1520
1521
1522
1523
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
1538
1539
1540
1541
1542
1543 {
1544 return new MemoryHolder( this, value );
1545 }
1546
1547
1548
1549
1550
1551 public ElementSerializer<K> getKeySerializer()
1552 {
1553 return keySerializer;
1554 }
1555
1556
1557
1558
1559
1560 public String getKeySerializerFQCN()
1561 {
1562 return btreeHeader.getKeySerializerFQCN();
1563 }
1564
1565
1566
1567
1568
1569 public ElementSerializer<V> getValueSerializer()
1570 {
1571 return valueSerializer;
1572 }
1573
1574
1575
1576
1577
1578 public String getValueSerializerFQCN()
1579 {
1580 return btreeHeader.getValueSerializerFQCN();
1581 }
1582
1583
1584
1585
1586
1587 public long getRevision()
1588 {
1589 return btreeHeader.getRevision();
1590 }
1591
1592
1593
1594
1595
1596
1597 {
1598 btreeHeader.setRevision( revision );
1599 }
1600
1601
1602
1603
1604
1605 public long getNbElems()
1606 {
1607 return btreeHeader.getNbElems();
1608 }
1609
1610
1611
1612
1613
1614
1615 {
1616 btreeHeader.setNbElems( nbElems );
1617 }
1618
1619
1620
1621
1622
1623 public boolean isAllowDuplicates()
1624 {
1625 return btreeHeader.isAllowDuplicates();
1626 }
1627
1628
1629
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
1667 journalChannel.force( true );
1668 }
1669
1670
1671
1672
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
1745 }
1746 }
1747
1748 sb.append( ") : \n" );
1749 sb.append( rootPage.dumpPage( "" ) );
1750
1751 return sb.toString();
1752 }
1753 }