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;
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
50
51
52
53
54
55
56 public class BTree<K, V>
57 {
58
59 protected static final Logger LOG = LoggerFactory.getLogger( BTree.class );
60
61
62 private BTreeHeader btreeHeader;
63
64
65 public static final int DEFAULT_PAGE_SIZE = 16;
66
67
68 public static final int DEFAULT_WRITE_BUFFER_SIZE = 4096 * 250;
69
70
71 public static final String DEFAULT_JOURNAL = "mavibot.log";
72
73
74 public static final String DATA_SUFFIX = ".db";
75
76
77 public static final String JOURNAL_SUFFIX = ".log";
78
79
80 private Comparator<K> comparator;
81
82
83 protected volatile Page<K, V> rootPage;
84
85
86 private ConcurrentLinkedQueue<Transaction<K, V>> readTransactions;
87
88
89 private int writeBufferSize;
90
91
92 protected Class<?> keyType;
93
94
95 private ElementSerializer<K> keySerializer;
96
97
98 private ElementSerializer<V> valueSerializer;
99
100
101 private File file;
102
103
104 private RecordManager recordManager;
105
106
107 private BTreeTypeEnum type;
108
109
110 private boolean withJournal;
111
112
113 private File journal;
114
115
116 private ReentrantLock writeLock;
117
118
119 private Thread readTransactionsThread;
120
121
122 private Thread journalManagerThread;
123
124
125 public static final long DEFAULT_READ_TIMEOUT = 10 * 1000L;
126
127
128 private long readTimeOut = DEFAULT_READ_TIMEOUT;
129
130
131 private BlockingQueue<Modification<K, V>> modificationsQueue;
132
133 private File envDir;
134
135
136
137
138
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
157 while ( ( transaction = readTransactions.peek() ) != null )
158 {
159 nbTxns++;
160
161 if ( transaction.isClosed() )
162 {
163
164 readTransactions.poll();
165 continue;
166 }
167
168
169 if ( transaction.getCreationDate() < timeoutDate )
170 {
171 transaction.close();
172 readTransactions.poll();
173 continue;
174 }
175
176
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
188 Thread.sleep( readTimeOut );
189 }
190 }
191 catch ( InterruptedException ie )
192 {
193
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
210
211
212
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
250 {
251 return false;
252 }
253
254
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
287 while ( ( modification = modificationsQueue.peek() ) != null );
288
289 try
290 {
291 flushModification( channel, modification );
292 }
293 catch ( IOException ioe )
294 {
295
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
313
314 public BTree()
315 {
316 btreeHeader = new BTreeHeader();
317 type = BTreeTypeEnum.MANAGED;
318 }
319
320
321
322
323
324
325
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
365
366 rootPage = new Leaf<K, V>( this );
367
368
369 init();
370 }
371
372
373
374
375
376
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
395
396
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
407
408
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
420
421
422
423
424
425
426
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
465
466 rootPage = new Leaf<K, V>( this );
467
468
469 init();
470 }
471
472
473
474
475
476
477
478 public void init() throws IOException
479 {
480
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
499 readTransactions = new ConcurrentLinkedQueue<Transaction<K, V>>();
500
501
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
522
523 if ( type == BTreeTypeEnum.PERSISTENT )
524 {
525 modificationsQueue = new LinkedBlockingDeque<Modification<K, V>>();
526
527 if ( file.length() > 0 )
528 {
529
530 load( file );
531 }
532
533 withJournal = true;
534
535
536
537 if ( journal.length() > 0 )
538 {
539 applyJournal();
540 }
541
542
543 createJournalManager();
544 }
545 else if ( type == null )
546 {
547 type = BTreeTypeEnum.IN_MEMORY;
548 }
549
550
551
552
553 }
554
555
556
557
558
559 public void close() throws IOException
560 {
561
562
563
564
565 if ( type == BTreeTypeEnum.PERSISTENT )
566 {
567
568
569
570 modificationsQueue.add( new PoisonPill<K, V>() );
571
572
573 flush();
574 }
575
576 rootPage = null;
577 }
578
579
580
581
582
583
584 {
585 return btreeHeader.getBTreeOffset();
586 }
587
588
589
590
591
592
593 {
594 btreeHeader.setBTreeOffset( btreeOffset );
595 }
596
597
598
599
600
601
602 {
603 return btreeHeader.getRootPageOffset();
604 }
605
606
607
608
609
610
611 {
612 btreeHeader.setRootPageOffset( rootPageOffset );
613 }
614
615
616
617
618
619
620 {
621 return btreeHeader.getNextBTreeOffset();
622 }
623
624
625
626
627
628
629 {
630 btreeHeader.setNextBTreeOffset( nextBTreeOffset );
631 }
632
633
634
635
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
653
654
655
656
657
658
659
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
676
677
678
679
680
681 {
682 rootPage = root;
683 }
684
685
686
687
688
689
690
691
692 {
693 return recordManager;
694 }
695
696
697
698
699
700
701
702
703 {
704 this.recordManager = recordManager;
705 this.type = BTreeTypeEnum.MANAGED;
706 }
707
708
709
710
711
712 public int getPageSize()
713 {
714 return btreeHeader.getPageSize();
715 }
716
717
718
719
720
721
722
723
724 long generateRevision()
725 {
726 return btreeHeader.incrementRevision();
727 }
728
729
730
731
732
733
734
735
736
737
738
739
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
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
762 writeLock.unlock();
763 }
764
765 return existingValue;
766 }
767
768
769
770
771
772
773
774
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
793
794
795
796
797
798
799
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
823
824
825
826
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
837
838
839
840
841
842
843
844
845
846 private Tuple<K, V> delete( K key, V value, long revision ) throws IOException
847 {
848 writeLock.lock();
849
850 try
851 {
852
853
854 Tuple<K, V> tuple = null;
855
856
857
858 DeleteResult<K, V> result = rootPage.delete( revision, key, value, null, -1 );
859
860 if ( result instanceof NotPresentResult )
861 {
862
863 return null;
864 }
865
866
867 Page<K, V> oldRootPage = rootPage;
868
869 if ( result instanceof RemoveResult )
870 {
871
872 RemoveResult<K, V> removeResult = ( RemoveResult<K, V> ) result;
873
874 Page<K, V> modifiedPage = removeResult.getModifiedPage();
875
876 if ( isManaged() )
877 {
878
879
880
881 ElementHolder<Page<K, V>, K, V> holder = recordManager.writePage( this, modifiedPage,
882 revision );
883
884
885 ( ( AbstractPage<K, V> ) modifiedPage ).setOffset( ( ( ReferenceHolder<Page<K, V>, K, V> ) holder )
886 .getOffset() );
887
888
889 ( ( AbstractPage<K, V> ) modifiedPage )
890 .setLastOffset( ( ( ReferenceHolder<Page<K, V>, K, V> ) holder )
891 .getLastOffset() );
892 }
893
894
895 rootPage = modifiedPage;
896 tuple = removeResult.getRemovedElement();
897 }
898
899 if ( type == BTreeTypeEnum.PERSISTENT )
900 {
901
902 modificationsQueue.add( new Deletion<K, V>( key ) );
903 }
904
905
906 if ( tuple != null )
907 {
908 btreeHeader.decrementNbElems();
909
910
911 if ( isManaged() )
912 {
913
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
923 recordManager.storeRootPage( this, rootPage );
924 }
925 else
926 {
927
928 }
929
930
931 return tuple;
932 }
933 finally
934 {
935
936 writeLock.unlock();
937 }
938 }
939
940
941
942
943
944
945
946
947
948
949
950
951 public V get( K key ) throws IOException, KeyNotFoundException
952 {
953 return rootPage.get( key );
954 }
955
956
957
958
959
960 public BTree<V, V> getValues( K key ) throws IOException, KeyNotFoundException
961 {
962 return rootPage.getValues( key );
963 }
964
965
966
967
968
969
970
971
972
973
974
975
976
977 public V get( long revision, K key ) throws IOException, KeyNotFoundException
978 {
979
980 Page<K, V> revisionRootPage = getRootPage( revision );
981
982 return revisionRootPage.get( key );
983 }
984
985
986
987
988
989
990
991
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
1006
1007
1008
1009
1010
1011
1012
1013 public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException
1014 {
1015 if ( key == null )
1016 {
1017 return false;
1018 }
1019
1020
1021 Page<K, V> revisionRootPage = getRootPage( revision );
1022
1023 return revisionRootPage.hasKey( key );
1024 }
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034 public boolean contains( K key, V value ) throws IOException
1035 {
1036 return rootPage.contains( key, value );
1037 }
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException
1050 {
1051
1052 Page<K, V> revisionRootPage = getRootPage( revision );
1053
1054 return revisionRootPage.contains( key, value );
1055 }
1056
1057
1058
1059
1060
1061
1062
1063
1064 public Cursor<K, V> browse() throws IOException
1065 {
1066 Transaction<K, V> transaction = beginReadTransaction();
1067
1068
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
1079
1080
1081
1082
1083
1084
1085 public Cursor<K, V> browse( long revision ) throws IOException, KeyNotFoundException
1086 {
1087 Transaction<K, V> transaction = beginReadTransaction();
1088
1089
1090 Page<K, V> revisionRootPage = getRootPage( revision );
1091
1092
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
1102
1103
1104
1105
1106
1107
1108 public Cursor<K, V> browseFrom( K key ) throws IOException
1109 {
1110 Transaction<K, V> transaction = beginReadTransaction();
1111
1112
1113 Cursor<K, V> cursor = rootPage.browse( key, transaction, new LinkedList<ParentPos<K, V>>() );
1114
1115 return cursor;
1116 }
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 public Cursor<K, V> browseFrom( long revision, K key ) throws IOException, KeyNotFoundException
1130 {
1131 Transaction<K, V> transaction = beginReadTransaction();
1132
1133
1134 Page<K, V> revisionRootPage = getRootPage( revision );
1135
1136
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
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 {
1159 if ( key == null )
1160 {
1161 throw new IllegalArgumentException( "Key must not be null" );
1162 }
1163
1164
1165
1166 V modifiedValue = null;
1167
1168
1169
1170
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
1182
1183
1184 ElementHolder<Page<K, V>, K, V> holder = recordManager.writePage( this, modifiedPage,
1185 revision );
1186
1187
1188 ( ( AbstractPage<K, V> ) modifiedPage ).setOffset( ( ( ReferenceHolder<Page<K, V>, K, V> ) holder )
1189 .getOffset() );
1190
1191
1192 ( ( AbstractPage<K, V> ) modifiedPage ).setLastOffset( ( ( ReferenceHolder<Page<K, V>, K, V> ) holder )
1193 .getLastOffset() );
1194 }
1195
1196
1197
1198 rootPage = modifiedPage;
1199
1200 modifiedValue = modifyResult.getModifiedValue();
1201 }
1202 else
1203 {
1204
1205
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
1214
1215 if ( isManaged() )
1216 {
1217 ElementHolder<Page<K, V>, K, V> holderLeft = recordManager.writePage( this,
1218 leftPage, revision );
1219
1220
1221 ( ( AbstractPage ) splitResult.getLeftPage() )
1222 .setOffset( ( ( ReferenceHolder ) holderLeft ).getOffset() );
1223
1224
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
1232 ( ( AbstractPage<K, V> ) splitResult.getRightPage() )
1233 .setOffset( ( ( ReferenceHolder ) holderRight ).getOffset() );
1234
1235
1236 ( ( AbstractPage<K, V> ) splitResult.getRightPage() )
1237 .setLastOffset( ( ( ReferenceHolder ) holderRight ).getLastOffset() );
1238
1239
1240 newRootPage = new Node<K, V>( this, revision, pivot, holderLeft, holderRight );
1241 }
1242 else
1243 {
1244
1245 newRootPage = new Node<K, V>( this, revision, pivot, leftPage, rightPage );
1246 }
1247
1248
1249
1250 if ( isManaged() )
1251 {
1252 ElementHolder<Page<K, V>, K, V> holder = recordManager
1253 .writePage( this, newRootPage, revision );
1254
1255
1256 ( ( AbstractPage<K, V> ) newRootPage ).setOffset( ( ( ReferenceHolder ) holder ).getOffset() );
1257
1258
1259 ( ( AbstractPage<K, V> ) newRootPage ).setLastOffset( ( ( ReferenceHolder ) holder ).getLastOffset() );
1260 }
1261
1262 rootPage = newRootPage;
1263 }
1264
1265
1266 if ( type == BTreeTypeEnum.PERSISTENT )
1267 {
1268 modificationsQueue.add( new Addition<K, V>( key, value ) );
1269 }
1270
1271
1272
1273 if ( modifiedValue == null )
1274 {
1275 btreeHeader.incrementNbElems();
1276 }
1277
1278
1279 if ( isManaged() )
1280 {
1281
1282 recordManager.updateBtreeHeader( this, ( ( AbstractPage<K, V> ) rootPage ).getOffset() );
1283
1284
1285 recordManager.addFreePages( this, ( List ) result.getCopiedPages() );
1286
1287
1288 recordManager.storeRootPage( this, rootPage );
1289 }
1290 else
1291 {
1292
1293 }
1294
1295
1296 return result;
1297 }
1298
1299
1300
1301
1302
1303
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
1318
1319
1320 {
1321 return keyType;
1322 }
1323
1324
1325
1326
1327
1328 public Comparator<K> getComparator()
1329 {
1330 return comparator;
1331 }
1332
1333
1334
1335
1336
1337 public void setComparator( Comparator<K> comparator )
1338 {
1339 this.comparator = comparator;
1340 }
1341
1342
1343
1344
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
1356
1357 public void setValueSerializer( ElementSerializer<V> valueSerializer )
1358 {
1359 this.valueSerializer = valueSerializer;
1360 btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
1361 }
1362
1363
1364
1365
1366
1367
1368
1369
1370
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
1378 do
1379 {
1380 if ( bb.remaining() >= size )
1381 {
1382
1383 bb.put( buffer, pos, size );
1384 size = 0;
1385 }
1386 else
1387 {
1388
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
1407
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
1424 File tmpFileFD = File.createTempFile( "mavibot", null, baseDirectory );
1425 FileOutputStream stream = new FileOutputStream( tmpFileFD );
1426 FileChannel ch = stream.getChannel();
1427
1428
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
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
1460 if ( bb.position() > 0 )
1461 {
1462 bb.flip();
1463 ch.write( bb );
1464 }
1465
1466
1467 ch.force( true );
1468 ch.close();
1469
1470
1471 File backupFile = File.createTempFile( "mavibot", null, baseDirectory );
1472 file.renameTo( backupFile );
1473
1474
1475 tmpFileFD.renameTo( file );
1476
1477
1478 backupFile.delete();
1479 }
1480
1481
1482
1483
1484
1485
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
1503 try
1504 {
1505 while ( true )
1506 {
1507
1508 byte[] type = bufferHandler.read( 1 );
1509
1510 if ( type[0] == Modification.ADDITION )
1511 {
1512
1513 K key = keySerializer.deserialize( bufferHandler );
1514
1515
1516
1517
1518 V value = valueSerializer.deserialize( bufferHandler );
1519
1520
1521
1522
1523 insert( key, value, revision );
1524 }
1525 else
1526 {
1527
1528 K key = keySerializer.deserialize( bufferHandler );
1529
1530
1531 delete( key, revision );
1532 }
1533 }
1534 }
1535 catch ( EOFException eofe )
1536 {
1537
1538 journal.delete();
1539 journal.createNewFile();
1540 }
1541 }
1542
1543
1544
1545
1546
1547
1548
1549
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
1570
1571
1572
1573
1574 boolean isJournalActivated = withJournal;
1575
1576 withJournal = false;
1577
1578
1579 for ( long i = 0; i < nbElems; i++ )
1580 {
1581
1582 K key = keySerializer.deserialize( bufferHandler );
1583
1584
1585
1586
1587 V value = valueSerializer.deserialize( bufferHandler );
1588
1589
1590
1591
1592 insert( key, value, revision );
1593 }
1594
1595
1596 withJournal = isJournalActivated;
1597
1598
1599
1600 }
1601
1602
1603
1604
1605
1606
1607
1608
1609
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
1621 return rootPage;
1622 }
1623 }
1624
1625
1626
1627
1628
1629
1630 public void flush() throws IOException
1631 {
1632 if ( type == BTreeTypeEnum.PERSISTENT )
1633 {
1634
1635 flush( file );
1636
1637
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
1648
1649 public long getReadTimeOut()
1650 {
1651 return readTimeOut;
1652 }
1653
1654
1655
1656
1657
1658 public void setReadTimeOut( long readTimeOut )
1659 {
1660 this.readTimeOut = readTimeOut;
1661 }
1662
1663
1664
1665
1666
1667 public String getName()
1668 {
1669 return btreeHeader.getName();
1670 }
1671
1672
1673
1674
1675
1676 public void setName( String name )
1677 {
1678 btreeHeader.setName( name );
1679 }
1680
1681
1682
1683
1684
1685 public File getFile()
1686 {
1687 return file;
1688 }
1689
1690
1691
1692
1693
1694 public File getJournal()
1695 {
1696 return journal;
1697 }
1698
1699
1700
1701
1702
1703 public int getWriteBufferSize()
1704 {
1705 return writeBufferSize;
1706 }
1707
1708
1709
1710
1711
1712 public void setWriteBufferSize( int writeBufferSize )
1713 {
1714 this.writeBufferSize = writeBufferSize;
1715 }
1716
1717
1718
1719
1720
1721 public boolean isInMemory()
1722 {
1723 return type == BTreeTypeEnum.IN_MEMORY;
1724 }
1725
1726
1727
1728
1729
1730 public boolean isPersistent()
1731 {
1732 return type == BTreeTypeEnum.IN_MEMORY;
1733 }
1734
1735
1736
1737
1738
1739 public boolean isManaged()
1740 {
1741 return type == BTreeTypeEnum.MANAGED;
1742 }
1743
1744
1745
1746
1747
1748
1749
1750
1751
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
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
1786
1787 public ElementSerializer<K> getKeySerializer()
1788 {
1789 return keySerializer;
1790 }
1791
1792
1793
1794
1795
1796 public String getKeySerializerFQCN()
1797 {
1798 return btreeHeader.getKeySerializerFQCN();
1799 }
1800
1801
1802
1803
1804
1805 public ElementSerializer<V> getValueSerializer()
1806 {
1807 return valueSerializer;
1808 }
1809
1810
1811
1812
1813
1814 public String getValueSerializerFQCN()
1815 {
1816 return btreeHeader.getValueSerializerFQCN();
1817 }
1818
1819
1820
1821
1822
1823 public long getRevision()
1824 {
1825 return btreeHeader.getRevision();
1826 }
1827
1828
1829
1830
1831
1832
1833 {
1834 btreeHeader.setRevision( revision );
1835 }
1836
1837
1838
1839
1840
1841 public long getNbElems()
1842 {
1843 return btreeHeader.getNbElems();
1844 }
1845
1846
1847
1848
1849
1850
1851 {
1852 btreeHeader.setNbElems( nbElems );
1853 }
1854
1855
1856
1857
1858
1859 public boolean isAllowDuplicates()
1860 {
1861 return btreeHeader.isAllowDuplicates();
1862 }
1863
1864
1865
1866 {
1867 btreeHeader.setAllowDuplicates( allowDuplicates );
1868 }
1869
1870
1871
1872
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
1949 }
1950 }
1951
1952 sb.append( ") : \n" );
1953 sb.append( rootPage.dumpPage( "" ) );
1954
1955 return sb.toString();
1956 }
1957 }