1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.IOException;
20 import java.lang.ref.Reference;
21 import java.lang.ref.SoftReference;
22 import java.util.AbstractList;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.LinkedHashMap;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Queue;
32 import java.util.RandomAccess;
33
34 import static com.healthmarketscience.jackcess.impl.IndexData.*;
35 import com.healthmarketscience.jackcess.impl.IndexData.DataPage;
36 import org.apache.commons.lang3.builder.ToStringBuilder;
37
38
39
40
41
42 public class IndexPageCache
43 {
44 private enum UpdateType {
45 ADD, REMOVE, REPLACE;
46 }
47
48
49
50 private static final int MAX_CACHE_SIZE = 25;
51
52
53 private final IndexData _indexData;
54
55 private DataPageMain _rootPage;
56
57 private final Map<Integer, DataPageMain> _dataPages =
58 new LinkedHashMap<Integer, DataPageMain>(16, 0.75f, true) {
59 private static final long serialVersionUID = 0L;
60 @Override
61 protected boolean removeEldestEntry(Map.Entry<Integer, DataPageMain> e) {
62
63
64
65 if((size() > MAX_CACHE_SIZE) && !getPageChannel().isWriting()) {
66 purgeOldPages();
67 }
68 return false;
69 }
70 };
71
72 private final List<CacheDataPage> _modifiedPages =
73 new ArrayList<CacheDataPage>();
74
75 public IndexPageCache(IndexData indexData) {
76 _indexData = indexData;
77 }
78
79 public IndexData getIndexData() {
80 return _indexData;
81 }
82
83 public PageChannel getPageChannel() {
84 return getIndexData().getPageChannel();
85 }
86
87
88
89
90
91
92 public void setRootPageNumber(int pageNumber) throws IOException {
93 _rootPage = getDataPage(pageNumber);
94
95 _rootPage.initParentPage(INVALID_INDEX_PAGE_NUMBER, false);
96 }
97
98
99
100
101 public void write()
102 throws IOException
103 {
104
105 handleEmptyPages();
106
107 preparePagesForWriting();
108
109 writeDataPages();
110
111 if(_dataPages.size() > MAX_CACHE_SIZE) {
112 purgeOldPages();
113 }
114 }
115
116
117
118
119
120
121 private void handleEmptyPages() throws IOException
122 {
123 for(Iterator<CacheDataPage> iter = _modifiedPages.iterator();
124 iter.hasNext(); ) {
125 CacheDataPage cacheDataPage = iter.next();
126 if(cacheDataPage._extra._entryView.isEmpty()) {
127 if(!cacheDataPage._main.isRoot()) {
128 deleteDataPage(cacheDataPage);
129 } else {
130 writeDataPage(cacheDataPage);
131 }
132 iter.remove();
133 }
134 }
135 }
136
137
138
139
140
141
142 private void preparePagesForWriting() throws IOException
143 {
144 boolean splitPages = false;
145 int maxPageEntrySize = getIndexData().getMaxPageEntrySize();
146
147
148
149 do {
150 splitPages = false;
151
152
153
154 for(int i = 0; i < _modifiedPages.size(); ++i) {
155
156 CacheDataPage cacheDataPage = _modifiedPages.get(i);
157
158 if(!cacheDataPage.isLeaf()) {
159
160 DataPageMain dpMain = cacheDataPage._main;
161 int size = cacheDataPage._extra._entryView.size();
162 if(dpMain.hasChildTail()) {
163 if(size == 1) {
164 demoteTail(cacheDataPage);
165 }
166 } else {
167 if(size > 1) {
168
169 DataPageMain lastChild = dpMain.getChildPage(
170 cacheDataPage._extra._entryView.getLast());
171 if(lastChild._leaf) {
172 promoteTail(cacheDataPage, lastChild);
173 }
174 }
175 }
176 }
177
178
179 if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
180
181
182
183 cacheDataPage._extra.updateEntryPrefix();
184
185
186 if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
187
188 splitPages = true;
189 splitDataPage(cacheDataPage);
190 }
191 }
192 }
193
194 } while(splitPages);
195 }
196
197
198
199
200
201 private void writeDataPages() throws IOException
202 {
203 for(CacheDataPage cacheDataPage : _modifiedPages) {
204 if(cacheDataPage._extra._entryView.isEmpty()) {
205 throw new IllegalStateException(withErrorContext(
206 "Unexpected empty page " + cacheDataPage));
207 }
208 writeDataPage(cacheDataPage);
209 }
210 _modifiedPages.clear();
211 }
212
213
214
215
216
217 public CacheDataPage getCacheDataPage(Integer pageNumber)
218 throws IOException
219 {
220 DataPageMain main = getDataPage(pageNumber);
221 return((main != null) ? new CacheDataPage(main) : null);
222 }
223
224
225
226
227
228 private DataPageMain getDataPage(Integer pageNumber)
229 throws IOException
230 {
231 DataPageMain dataPage = _dataPages.get(pageNumber);
232 if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
233 dataPage = readDataPage(pageNumber)._main;
234 _dataPages.put(pageNumber, dataPage);
235 }
236 return dataPage;
237 }
238
239
240
241
242 private void writeDataPage(CacheDataPage cacheDataPage)
243 throws IOException
244 {
245 getIndexData().writeDataPage(cacheDataPage);
246
247
248 cacheDataPage._extra._modified = false;
249 }
250
251
252
253
254 private void deleteDataPage(CacheDataPage cacheDataPage)
255 throws IOException
256 {
257
258 getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
259
260
261 _dataPages.remove(cacheDataPage._main._pageNumber);
262
263
264 cacheDataPage._extra._modified = false;
265 }
266
267
268
269
270 private CacheDataPage readDataPage(Integer pageNumber)
271 throws IOException
272 {
273 DataPageMain dataPage = new DataPageMain(pageNumber);
274 DataPageExtra extra = new DataPageExtra();
275 CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
276 getIndexData().readDataPage(cacheDataPage);
277
278
279 dataPage.setExtra(extra);
280
281 return cacheDataPage;
282 }
283
284
285
286
287
288
289
290 private Entry removeEntry(CacheDataPage cacheDataPage, int entryIdx)
291 throws IOException
292 {
293 return updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
294 }
295
296
297
298
299
300
301
302
303 private void addEntry(CacheDataPage cacheDataPage,
304 int entryIdx,
305 Entry newEntry)
306 throws IOException
307 {
308 updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
309 }
310
311
312
313
314
315
316
317
318
319 private Entry updateEntry(CacheDataPage cacheDataPage,
320 int entryIdx,
321 Entry newEntry,
322 UpdateType upType)
323 throws IOException
324 {
325 DataPageMain dpMain = cacheDataPage._main;
326 DataPageExtra dpExtra = cacheDataPage._extra;
327
328 if(newEntry != null) {
329 validateEntryForPage(dpMain, newEntry);
330 }
331
332
333
334 CacheDataPage parentDataPage = (!dpMain.isRoot() ?
335 new CacheDataPage(dpMain.getParentPage()) :
336 null);
337
338 Entry oldLastEntry = dpExtra._entryView.getLast();
339 Entry oldEntry = null;
340 int entrySizeDiff = 0;
341
342 switch(upType) {
343 case ADD:
344 dpExtra._entryView.add(entryIdx, newEntry);
345 entrySizeDiff += newEntry.size();
346 break;
347
348 case REPLACE:
349 oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
350 entrySizeDiff += newEntry.size() - oldEntry.size();
351 break;
352
353 case REMOVE: {
354 oldEntry = dpExtra._entryView.remove(entryIdx);
355 entrySizeDiff -= oldEntry.size();
356 break;
357 }
358 default:
359 throw new RuntimeException(withErrorContext(
360 "unknown update type " + upType));
361 }
362
363 boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
364
365
366 if(!updateLast || !dpMain.hasChildTail()) {
367 dpExtra._totalEntrySize += entrySizeDiff;
368 setModified(cacheDataPage);
369
370
371 dpExtra._entryPrefix = EMPTY_PREFIX;
372 }
373
374 if(dpExtra._entryView.isEmpty()) {
375
376 removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
377 return oldEntry;
378 }
379
380
381 if(!updateLast || dpMain.isRoot()) {
382
383 return oldEntry;
384 }
385
386
387 replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
388 return oldEntry;
389 }
390
391
392
393
394
395
396
397
398
399 private void removeDataPage(CacheDataPage parentDataPage,
400 CacheDataPage cacheDataPage,
401 Entry oldLastEntry)
402 throws IOException
403 {
404 DataPageMain dpMain = cacheDataPage._main;
405 DataPageExtra dpExtra = cacheDataPage._extra;
406
407 if(dpMain.hasChildTail()) {
408 throw new IllegalStateException(withErrorContext("Still has child tail?"));
409 }
410
411 if(dpExtra._totalEntrySize != 0) {
412 throw new IllegalStateException(withErrorContext(
413 "Empty page but size is not 0? " + dpExtra._totalEntrySize + ", " +
414 cacheDataPage));
415 }
416
417 if(dpMain.isRoot()) {
418
419 dpExtra._entryPrefix = EMPTY_PREFIX;
420
421 dpMain._leaf = true;
422 return;
423 }
424
425
426 updateParentEntry(parentDataPage, cacheDataPage, oldLastEntry, null,
427 UpdateType.REMOVE);
428
429
430 removeFromPeers(cacheDataPage);
431 }
432
433
434
435
436
437
438 private void removeFromPeers(CacheDataPage cacheDataPage)
439 throws IOException
440 {
441 DataPageMain dpMain = cacheDataPage._main;
442
443 Integer prevPageNumber = dpMain._prevPageNumber;
444 Integer nextPageNumber = dpMain._nextPageNumber;
445
446 DataPageMain prevMain = dpMain.getPrevPage();
447 if(prevMain != null) {
448 setModified(new CacheDataPage(prevMain));
449 prevMain._nextPageNumber = nextPageNumber;
450 }
451
452 DataPageMain nextMain = dpMain.getNextPage();
453 if(nextMain != null) {
454 setModified(new CacheDataPage(nextMain));
455 nextMain._prevPageNumber = prevPageNumber;
456 }
457 }
458
459
460
461
462
463
464
465 private void addParentEntry(CacheDataPage parentDataPage,
466 CacheDataPage childDataPage)
467 throws IOException
468 {
469 DataPageExtra childExtra = childDataPage._extra;
470 updateParentEntry(parentDataPage, childDataPage, null,
471 childExtra._entryView.getLast(), UpdateType.ADD);
472 }
473
474
475
476
477
478
479
480
481 private void replaceParentEntry(CacheDataPage parentDataPage,
482 CacheDataPage childDataPage,
483 Entry oldEntry)
484 throws IOException
485 {
486 DataPageExtra childExtra = childDataPage._extra;
487 updateParentEntry(parentDataPage, childDataPage, oldEntry,
488 childExtra._entryView.getLast(), UpdateType.REPLACE);
489 }
490
491
492
493
494
495
496
497
498
499
500
501 private void updateParentEntry(CacheDataPage parentDataPage,
502 CacheDataPage childDataPage,
503 Entry oldEntry, Entry newEntry,
504 UpdateType upType)
505 throws IOException
506 {
507 DataPageMain childMain = childDataPage._main;
508 DataPageExtra parentExtra = parentDataPage._extra;
509
510 if(childMain.isTail() && (upType != UpdateType.REMOVE)) {
511
512
513 updateParentTail(parentDataPage, childDataPage, upType);
514 }
515
516 if(oldEntry != null) {
517 oldEntry = oldEntry.asNodeEntry(childMain._pageNumber);
518 }
519 if(newEntry != null) {
520 newEntry = newEntry.asNodeEntry(childMain._pageNumber);
521 }
522
523 boolean expectFound = true;
524 int idx = 0;
525
526 switch(upType) {
527 case ADD:
528 expectFound = false;
529 idx = parentExtra._entryView.find(newEntry);
530 break;
531
532 case REPLACE:
533 case REMOVE:
534 idx = parentExtra._entryView.find(oldEntry);
535 break;
536
537 default:
538 throw new RuntimeException(withErrorContext(
539 "unknown update type " + upType));
540 }
541
542 if(idx < 0) {
543 if(expectFound) {
544 throw new IllegalStateException(withErrorContext(
545 "Could not find child entry in parent; childEntry " + oldEntry +
546 "; parent " + parentDataPage));
547 }
548 idx = missingIndexToInsertionPoint(idx);
549 } else {
550 if(!expectFound) {
551 throw new IllegalStateException(withErrorContext(
552 "Unexpectedly found child entry in parent; childEntry " +
553 newEntry + "; parent " + parentDataPage));
554 }
555 }
556 updateEntry(parentDataPage, idx, newEntry, upType);
557
558 if(childMain.isTail() && (upType == UpdateType.REMOVE)) {
559
560
561 updateParentTail(parentDataPage, childDataPage, upType);
562 }
563 }
564
565
566
567
568
569
570
571
572
573 private void updateParentTail(CacheDataPage parentDataPage,
574 CacheDataPage childDataPage,
575 UpdateType upType)
576 {
577 DataPageMain parentMain = parentDataPage._main;
578
579 int newChildTailPageNumber =
580 ((upType == UpdateType.REMOVE) ?
581 INVALID_INDEX_PAGE_NUMBER :
582 childDataPage._main._pageNumber);
583 if(!parentMain.isChildTailPageNumber(newChildTailPageNumber)) {
584 setModified(parentDataPage);
585 parentMain._childTailPageNumber = newChildTailPageNumber;
586 }
587 }
588
589
590
591
592
593
594
595
596
597
598 private void validateEntryForPage(DataPageMain dpMain, Entry entry) {
599 if(dpMain._leaf != entry.isLeafEntry()) {
600 throw new IllegalStateException(withErrorContext(
601 "Trying to update page with wrong entry type; pageLeaf " +
602 dpMain._leaf + ", entryLeaf " + entry.isLeafEntry()));
603 }
604 }
605
606
607
608
609
610
611 private void splitDataPage(CacheDataPage origDataPage)
612 throws IOException
613 {
614 DataPageMain origMain = origDataPage._main;
615 DataPageExtra origExtra = origDataPage._extra;
616
617 setModified(origDataPage);
618
619 int numEntries = origExtra._entries.size();
620 if(numEntries < 2) {
621 throw new IllegalStateException(withErrorContext(
622 "Cannot split page with less than 2 entries " + origDataPage));
623 }
624
625 if(origMain.isRoot()) {
626
627
628 CacheDataPage newDataPage = nestRootDataPage(origDataPage);
629
630
631 origDataPage = newDataPage;
632 origMain = newDataPage._main;
633 origExtra = newDataPage._extra;
634 }
635
636
637
638 DataPageMain parentMain = origMain.getParentPage();
639 CacheDataPage parentDataPage = new CacheDataPage(parentMain);
640
641
642
643
644
645 CacheDataPage newDataPage = allocateNewCacheDataPage(
646 parentMain._pageNumber, origMain._leaf);
647 DataPageMain newMain = newDataPage._main;
648 DataPageExtra newExtra = newDataPage._extra;
649
650 List<Entry> headEntries =
651 origExtra._entries.subList(0, ((numEntries + 1) / 2));
652
653
654
655 for(Entry headEntry : headEntries) {
656 newExtra._totalEntrySize += headEntry.size();
657 newExtra._entries.add(headEntry);
658 }
659 newExtra.setEntryView(newMain);
660
661
662 headEntries.clear();
663 origExtra._entryPrefix = EMPTY_PREFIX;
664 origExtra._totalEntrySize -= newExtra._totalEntrySize;
665
666
667 addToPeersBefore(newDataPage, origDataPage);
668
669 if(!newMain._leaf) {
670
671 reparentChildren(newDataPage);
672
673
674
675
676
677 DataPageMain childMain = newMain.getChildPage(
678 newExtra._entryView.getLast());
679 if(!childMain._leaf) {
680 separateFromNextPeer(new CacheDataPage(childMain));
681 }
682 }
683
684
685 addParentEntry(parentDataPage, newDataPage);
686 }
687
688
689
690
691
692
693
694
695
696
697 private CacheDataPage nestRootDataPage(CacheDataPage rootDataPage)
698 throws IOException
699 {
700 DataPageMain rootMain = rootDataPage._main;
701 DataPageExtra rootExtra = rootDataPage._extra;
702
703 if(!rootMain.isRoot()) {
704 throw new IllegalArgumentException(withErrorContext(
705 "should be called with root, duh"));
706 }
707
708 CacheDataPage newDataPage =
709 allocateNewCacheDataPage(rootMain._pageNumber, rootMain._leaf);
710 DataPageMain newMain = newDataPage._main;
711 DataPageExtra newExtra = newDataPage._extra;
712
713
714 newMain._childTailPageNumber = rootMain._childTailPageNumber;
715 newExtra._entries = rootExtra._entries;
716 newExtra._entryPrefix = rootExtra._entryPrefix;
717 newExtra._totalEntrySize = rootExtra._totalEntrySize;
718 newExtra.setEntryView(newMain);
719
720 if(!newMain._leaf) {
721
722 reparentChildren(newDataPage);
723 }
724
725
726 rootMain._leaf = false;
727 rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
728 rootExtra._entries = new ArrayList<Entry>();
729 rootExtra._entryPrefix = EMPTY_PREFIX;
730 rootExtra._totalEntrySize = 0;
731 rootExtra.setEntryView(rootMain);
732
733
734 addParentEntry(rootDataPage, newDataPage);
735
736 return newDataPage;
737 }
738
739
740
741
742
743
744
745
746
747 private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
748 boolean isLeaf)
749 throws IOException
750 {
751 DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
752 DataPageExtra dpExtra = new DataPageExtra();
753 dpMain.initParentPage(parentPageNumber, false);
754 dpMain._leaf = isLeaf;
755 dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
756 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
757 dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
758 dpExtra._entries = new ArrayList<Entry>();
759 dpExtra._entryPrefix = EMPTY_PREFIX;
760 dpMain.setExtra(dpExtra);
761
762
763 _dataPages.put(dpMain._pageNumber, dpMain);
764
765
766 _indexData.addOwnedPage(dpMain._pageNumber);
767
768
769 CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
770 setModified(cacheDataPage);
771
772 return cacheDataPage;
773 }
774
775
776
777
778
779
780
781
782 private void addToPeersBefore(CacheDataPage newDataPage,
783 CacheDataPage origDataPage)
784 throws IOException
785 {
786 DataPageMain origMain = origDataPage._main;
787 DataPageMain newMain = newDataPage._main;
788
789 DataPageMain prevMain = origMain.getPrevPage();
790
791 newMain._nextPageNumber = origMain._pageNumber;
792 newMain._prevPageNumber = origMain._prevPageNumber;
793 origMain._prevPageNumber = newMain._pageNumber;
794
795 if(prevMain != null) {
796 setModified(new CacheDataPage(prevMain));
797 prevMain._nextPageNumber = newMain._pageNumber;
798 }
799 }
800
801
802
803
804
805
806 private void separateFromNextPeer(CacheDataPage cacheDataPage)
807 throws IOException
808 {
809 DataPageMain dpMain = cacheDataPage._main;
810
811 setModified(cacheDataPage);
812
813 DataPageMain nextMain = dpMain.getNextPage();
814 setModified(new CacheDataPage(nextMain));
815
816 nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
817 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
818 }
819
820
821
822
823
824
825
826 private void reparentChildren(CacheDataPage cacheDataPage)
827 {
828 DataPageMain dpMain = cacheDataPage._main;
829 DataPageExtra dpExtra = cacheDataPage._extra;
830
831
832
833
834 for(Entry entry : dpExtra._entryView) {
835 Integer childPageNumber = entry.getSubPageNumber();
836 DataPageMain childMain = _dataPages.get(childPageNumber);
837 if(childMain != null) {
838 childMain.setParentPage(dpMain._pageNumber,
839 dpMain.isChildTailPageNumber(childPageNumber));
840 }
841 }
842 }
843
844
845
846
847
848
849
850 private void demoteTail(CacheDataPage cacheDataPage)
851 throws IOException
852 {
853
854
855 DataPageMain dpMain = cacheDataPage._main;
856 DataPageExtra dpExtra = cacheDataPage._extra;
857
858 setModified(cacheDataPage);
859
860 DataPageMain tailMain = dpMain.getChildTailPage();
861 CacheDataPage tailDataPage = new CacheDataPage(tailMain);
862
863
864 updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
865 Entry tailEntry = dpExtra._entryView.demoteTail();
866 dpExtra._totalEntrySize += tailEntry.size();
867 dpExtra._entryPrefix = EMPTY_PREFIX;
868
869 tailMain.setParentPage(dpMain._pageNumber, false);
870 }
871
872
873
874
875
876
877
878 private void promoteTail(CacheDataPage cacheDataPage, DataPageMain lastMain)
879 throws IOException
880 {
881
882 DataPageMain dpMain = cacheDataPage._main;
883 DataPageExtra dpExtra = cacheDataPage._extra;
884
885 setModified(cacheDataPage);
886
887 CacheDataPage lastDataPage = new CacheDataPage(lastMain);
888
889
890 updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
891 Entry lastEntry = dpExtra._entryView.promoteTail();
892 dpExtra._totalEntrySize -= lastEntry.size();
893 dpExtra._entryPrefix = EMPTY_PREFIX;
894
895 lastMain.setParentPage(dpMain._pageNumber, true);
896 }
897
898
899
900
901
902
903 public CacheDataPage findCacheDataPage(Entry e)
904 throws IOException
905 {
906 DataPageMain curPage = _rootPage;
907 while(true) {
908
909 if(curPage._leaf) {
910
911 return new CacheDataPage(curPage);
912 }
913
914 DataPageExtra extra = curPage.getExtra();
915
916
917 int idx = extra._entryView.find(e);
918 if(idx < 0) {
919 idx = missingIndexToInsertionPoint(idx);
920 if(idx == extra._entryView.size()) {
921
922 --idx;
923 }
924 }
925
926 Entry nodeEntry = extra._entryView.get(idx);
927 curPage = curPage.getChildPage(nodeEntry);
928 }
929 }
930
931
932
933
934
935
936
937 private void setModified(CacheDataPage cacheDataPage)
938 {
939 if(!cacheDataPage._extra._modified) {
940 _modifiedPages.add(cacheDataPage);
941 cacheDataPage._extra._modified = true;
942 }
943 }
944
945
946
947
948
949
950
951
952
953
954 private static byte[] findCommonPrefix(Entry e1, Entry e2)
955 {
956 byte[] b1 = e1.getEntryBytes();
957 byte[] b2 = e2.getEntryBytes();
958
959 int maxLen = b1.length;
960 byte[] prefix = b1;
961 if(b1.length > b2.length) {
962 maxLen = b2.length;
963 prefix = b2;
964 }
965
966 int len = 0;
967 while((len < maxLen) && (b1[len] == b2[len])) {
968 ++len;
969 }
970
971 if(len < prefix.length) {
972 if(len == 0) {
973 return EMPTY_PREFIX;
974 }
975
976
977 prefix = ByteUtil.copyOf(prefix, len);
978 }
979
980 return prefix;
981 }
982
983
984
985
986 void validate(boolean forceLoad) throws IOException {
987 new Validator(forceLoad).validate();
988 }
989
990
991
992
993
994
995
996 private List<Object> collectPages(List<Object> pages, DataPageMain dpMain) {
997 try {
998 CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
999 pages.add(cacheDataPage);
1000 if(!dpMain._leaf) {
1001 for(Entry e : cacheDataPage._extra._entryView) {
1002 DataPageMain childMain = dpMain.getChildPage(e);
1003 collectPages(pages, childMain);
1004 }
1005 }
1006 } catch(IOException e) {
1007 pages.add("DataPage[" + dpMain._pageNumber + "]: <" + e + ">");
1008 }
1009 return pages;
1010 }
1011
1012
1013
1014
1015
1016 private void purgeOldPages() {
1017 Iterator<DataPageMain> iter = _dataPages.values().iterator();
1018 while(iter.hasNext()) {
1019 DataPageMain dpMain = iter.next();
1020
1021 if(dpMain != _rootPage) {
1022 iter.remove();
1023 if(_dataPages.size() <= MAX_CACHE_SIZE) {
1024 break;
1025 }
1026 }
1027 }
1028 }
1029
1030 @Override
1031 public String toString() {
1032 ToStringBuilder sb = CustomToStringStyle.builder(this);
1033 if(_rootPage == null) {
1034 sb.append("pages", "(uninitialized)");
1035 } else {
1036 sb.append("pages", collectPages(new ArrayList<Object>(), _rootPage));
1037 }
1038 return sb.toString();
1039 }
1040
1041 private String withErrorContext(String msg) {
1042 return _indexData.withErrorContext(msg);
1043 }
1044
1045
1046
1047
1048
1049 private class DataPageMain
1050 {
1051 public final int _pageNumber;
1052 public Integer _prevPageNumber;
1053 public Integer _nextPageNumber;
1054 public Integer _childTailPageNumber;
1055 public Integer _parentPageNumber;
1056 public boolean _leaf;
1057 public boolean _tail;
1058 private Reference<DataPageExtra> _extra;
1059
1060 private DataPageMain(int pageNumber) {
1061 _pageNumber = pageNumber;
1062 }
1063
1064 public IndexPageCache getCache() {
1065 return IndexPageCache.this;
1066 }
1067
1068 public boolean isRoot() {
1069 return(this == _rootPage);
1070 }
1071
1072 public boolean isTail() throws IOException
1073 {
1074 resolveParent();
1075 return _tail;
1076 }
1077
1078 public boolean hasChildTail() {
1079 return(_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1080 }
1081
1082 public boolean isChildTailPageNumber(int pageNumber) {
1083 return(_childTailPageNumber == pageNumber);
1084 }
1085
1086 public DataPageMain getParentPage() throws IOException
1087 {
1088 resolveParent();
1089 return IndexPageCache.this.getDataPage(_parentPageNumber);
1090 }
1091
1092 public void initParentPage(Integer parentPageNumber, boolean isTail) {
1093
1094 if(_parentPageNumber == null) {
1095 setParentPage(parentPageNumber, isTail);
1096 }
1097 }
1098
1099 public void setParentPage(Integer parentPageNumber, boolean isTail) {
1100 _parentPageNumber = parentPageNumber;
1101 _tail = isTail;
1102 }
1103
1104 public DataPageMain getPrevPage() throws IOException
1105 {
1106 return IndexPageCache.this.getDataPage(_prevPageNumber);
1107 }
1108
1109 public DataPageMain getNextPage() throws IOException
1110 {
1111 return IndexPageCache.this.getDataPage(_nextPageNumber);
1112 }
1113
1114 public DataPageMain getChildPage(Entry e) throws IOException
1115 {
1116 Integer childPageNumber = e.getSubPageNumber();
1117 return getChildPage(childPageNumber,
1118 isChildTailPageNumber(childPageNumber));
1119 }
1120
1121 public DataPageMain getChildTailPage() throws IOException
1122 {
1123 return getChildPage(_childTailPageNumber, true);
1124 }
1125
1126
1127
1128
1129
1130 private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1131 throws IOException
1132 {
1133 DataPageMain child = getDataPage(childPageNumber);
1134 if(child != null) {
1135
1136 child.initParentPage(_pageNumber, isTail);
1137 }
1138 return child;
1139 }
1140
1141 public DataPageExtra getExtra() throws IOException
1142 {
1143 DataPageExtra extra = _extra.get();
1144 if(extra == null) {
1145 extra = readDataPage(_pageNumber)._extra;
1146 setExtra(extra);
1147 }
1148
1149 return extra;
1150 }
1151
1152 public void setExtra(DataPageExtra extra) throws IOException
1153 {
1154 extra.setEntryView(this);
1155 _extra = new SoftReference<DataPageExtra>(extra);
1156 }
1157
1158 private void resolveParent() throws IOException {
1159 if(_parentPageNumber == null) {
1160
1161
1162 findCacheDataPage(getExtra()._entryView.getLast());
1163 if(_parentPageNumber == null) {
1164 throw new IllegalStateException(withErrorContext(
1165 "Parent was not resolved"));
1166 }
1167 }
1168 }
1169
1170 @Override
1171 public String toString() {
1172 return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1173 "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1174 _childTailPageNumber + ")";
1175 }
1176 }
1177
1178
1179
1180
1181
1182 private static class DataPageExtra
1183 {
1184
1185
1186 public List<Entry> _entries;
1187 public EntryListView _entryView;
1188 public byte[] _entryPrefix;
1189 public int _totalEntrySize;
1190 public boolean _modified;
1191
1192 private DataPageExtra()
1193 {
1194 }
1195
1196 public void setEntryView(DataPageMain main) throws IOException {
1197 _entryView = new EntryListView(main, this);
1198 }
1199
1200 public void updateEntryPrefix() {
1201 if(_entryPrefix.length == 0) {
1202
1203 _entryPrefix = findCommonPrefix(_entries.get(0),
1204 _entries.get(_entries.size() - 1));
1205 }
1206 }
1207
1208 @Override
1209 public String toString() {
1210 return CustomToStringStyle.builder("DPExtra")
1211 .append(null, _entryView)
1212 .toString();
1213 }
1214 }
1215
1216
1217
1218
1219 private static final class CacheDataPage extends DataPage
1220 {
1221 public final DataPageMain _main;
1222 public final DataPageExtra _extra;
1223
1224 private CacheDataPage(DataPageMain dataPage) throws IOException {
1225 this(dataPage, dataPage.getExtra());
1226 }
1227
1228 private CacheDataPage(DataPageMain dataPage, DataPageExtra extra) {
1229 _main = dataPage;
1230 _extra = extra;
1231 }
1232
1233 @Override
1234 public int getPageNumber() {
1235 return _main._pageNumber;
1236 }
1237
1238 @Override
1239 public boolean isLeaf() {
1240 return _main._leaf;
1241 }
1242
1243 @Override
1244 public void setLeaf(boolean isLeaf) {
1245 _main._leaf = isLeaf;
1246 }
1247
1248
1249 @Override
1250 public int getPrevPageNumber() {
1251 return _main._prevPageNumber;
1252 }
1253
1254 @Override
1255 public void setPrevPageNumber(int pageNumber) {
1256 _main._prevPageNumber = pageNumber;
1257 }
1258
1259 @Override
1260 public int getNextPageNumber() {
1261 return _main._nextPageNumber;
1262 }
1263
1264 @Override
1265 public void setNextPageNumber(int pageNumber) {
1266 _main._nextPageNumber = pageNumber;
1267 }
1268
1269 @Override
1270 public int getChildTailPageNumber() {
1271 return _main._childTailPageNumber;
1272 }
1273
1274 @Override
1275 public void setChildTailPageNumber(int pageNumber) {
1276 _main._childTailPageNumber = pageNumber;
1277 }
1278
1279
1280 @Override
1281 public int getTotalEntrySize() {
1282 return _extra._totalEntrySize;
1283 }
1284
1285 @Override
1286 public void setTotalEntrySize(int totalSize) {
1287 _extra._totalEntrySize = totalSize;
1288 }
1289
1290 @Override
1291 public byte[] getEntryPrefix() {
1292 return _extra._entryPrefix;
1293 }
1294
1295 @Override
1296 public void setEntryPrefix(byte[] entryPrefix) {
1297 _extra._entryPrefix = entryPrefix;
1298 }
1299
1300
1301 @Override
1302 public List<Entry> getEntries() {
1303 return _extra._entries;
1304 }
1305
1306 @Override
1307 public void setEntries(List<Entry> entries) {
1308 _extra._entries = entries;
1309 }
1310
1311 @Override
1312 public void addEntry(int idx, Entry entry) throws IOException {
1313 _main.getCache().addEntry(this, idx, entry);
1314 }
1315
1316 @Override
1317 public Entry removeEntry(int idx) throws IOException {
1318 return _main.getCache().removeEntry(this, idx);
1319 }
1320
1321 }
1322
1323
1324
1325
1326
1327 private static class EntryListView extends AbstractList<Entry>
1328 implements RandomAccess
1329 {
1330 private final DataPageExtra _extra;
1331 private Entry _childTailEntry;
1332
1333 private EntryListView(DataPageMain main, DataPageExtra extra)
1334 throws IOException
1335 {
1336 if(main.hasChildTail()) {
1337 _childTailEntry = main.getChildTailPage().getExtra()._entryView
1338 .getLast().asNodeEntry(main._childTailPageNumber);
1339 }
1340 _extra = extra;
1341 }
1342
1343 private List<Entry> getEntries() {
1344 return _extra._entries;
1345 }
1346
1347 @Override
1348 public int size() {
1349 int size = getEntries().size();
1350 if(hasChildTail()) {
1351 ++size;
1352 }
1353 return size;
1354 }
1355
1356 @Override
1357 public Entry get(int idx) {
1358 return (isCurrentChildTailIndex(idx) ?
1359 _childTailEntry :
1360 getEntries().get(idx));
1361 }
1362
1363 @Override
1364 public Entry set(int idx, Entry newEntry) {
1365 return (isCurrentChildTailIndex(idx) ?
1366 setChildTailEntry(newEntry) :
1367 getEntries().set(idx, newEntry));
1368 }
1369
1370 @Override
1371 public void add(int idx, Entry newEntry) {
1372
1373
1374 getEntries().add(idx, newEntry);
1375 }
1376
1377 @Override
1378 public Entry remove(int idx) {
1379 return (isCurrentChildTailIndex(idx) ?
1380 setChildTailEntry(null) :
1381 getEntries().remove(idx));
1382 }
1383
1384 public Entry setChildTailEntry(Entry newEntry) {
1385 Entry old = _childTailEntry;
1386 _childTailEntry = newEntry;
1387 return old;
1388 }
1389
1390 private boolean hasChildTail() {
1391 return(_childTailEntry != null);
1392 }
1393
1394 private boolean isCurrentChildTailIndex(int idx) {
1395 return(idx == getEntries().size());
1396 }
1397
1398 public Entry getLast() {
1399 return(hasChildTail() ? _childTailEntry :
1400 (!getEntries().isEmpty() ?
1401 getEntries().get(getEntries().size() - 1) : null));
1402 }
1403
1404 public Entry demoteTail() {
1405 Entry tail = _childTailEntry;
1406 _childTailEntry = null;
1407 getEntries().add(tail);
1408 return tail;
1409 }
1410
1411 public Entry promoteTail() {
1412 Entry last = getEntries().remove(getEntries().size() - 1);
1413 _childTailEntry = last;
1414 return last;
1415 }
1416
1417 public int find(Entry e) {
1418 return Collections.binarySearch(this, e);
1419 }
1420
1421 }
1422
1423
1424
1425
1426 private final class Validator {
1427 private final boolean _forceLoad;
1428 private final Map<Integer,DataPageMain> _knownPages = new HashMap<>();
1429 private final Queue<DataPageMain> _pendingPages = new LinkedList<>();
1430
1431 private Validator(boolean forceLoad) {
1432 _forceLoad = forceLoad;
1433 _knownPages.putAll(_dataPages);
1434 _pendingPages.addAll(_knownPages.values());
1435 }
1436
1437 void validate() throws IOException {
1438 DataPageMain dpMain = null;
1439 while((dpMain = _pendingPages.poll()) != null) {
1440 DataPageExtra dpExtra = dpMain.getExtra();
1441 validateEntries(dpExtra);
1442 validateChildren(dpMain, dpExtra);
1443 validatePeers(dpMain);
1444 }
1445 }
1446
1447
1448
1449
1450
1451
1452 private void validateEntries(DataPageExtra dpExtra) throws IOException {
1453 int entrySize = 0;
1454 Entry prevEntry = FIRST_ENTRY;
1455 for(Entry e : dpExtra._entries) {
1456 entrySize += e.size();
1457 if(prevEntry.compareTo(e) >= 0) {
1458 throw new IOException(withErrorContext(
1459 "Unexpected order in index entries, " + prevEntry +
1460 " >= " + e));
1461 }
1462 prevEntry = e;
1463 }
1464
1465 if(dpExtra._entryView.hasChildTail()) {
1466 Entry tailE = dpExtra._entryView.getLast();
1467 if(prevEntry.compareTo(tailE) >= 0) {
1468 throw new IOException(withErrorContext(
1469 "Unexpected order in index entries, " + prevEntry +
1470 " >= " + tailE));
1471 }
1472 }
1473
1474 if(entrySize != dpExtra._totalEntrySize) {
1475 throw new IllegalStateException(withErrorContext(
1476 "Expected size " + entrySize +
1477 " but was " + dpExtra._totalEntrySize));
1478 }
1479 }
1480
1481
1482
1483
1484
1485
1486
1487 private void validateChildren(DataPageMain dpMain,
1488 DataPageExtra dpExtra) throws IOException {
1489 int childTailPageNumber = dpMain._childTailPageNumber;
1490 if(dpMain._leaf) {
1491 if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1492 throw new IllegalStateException(
1493 withErrorContext("Leaf page has tail " + dpMain));
1494 }
1495 return;
1496 }
1497 if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1498 throw new IllegalStateException(
1499 withErrorContext("Single child is tail " + dpMain));
1500 }
1501 Integer prevPageNumber = null;
1502 Integer nextPageNumber = null;
1503 Entry prevLastEntry = FIRST_ENTRY;
1504 for(Entry e : dpExtra._entryView) {
1505 validateEntryForPage(dpMain, e);
1506 Integer subPageNumber = e.getSubPageNumber();
1507 DataPageMain childMain = getPageForValidate(subPageNumber);
1508 if(childMain != null) {
1509 if((prevPageNumber != null) &&
1510 ((int)childMain._prevPageNumber != prevPageNumber)) {
1511 throw new IllegalStateException(withErrorContext(
1512 "Child's prevPageNumber is not the previous child for " +
1513 childMain + " " + dpExtra._entryView + " " +
1514 prevPageNumber));
1515 }
1516 if((nextPageNumber != null) &&
1517 (childMain._pageNumber != nextPageNumber)) {
1518 throw new IllegalStateException(withErrorContext(
1519 "Child's pageNumber is not the expected next child for " +
1520 childMain));
1521 }
1522 if(childMain._parentPageNumber != null) {
1523 if(childMain._parentPageNumber != dpMain._pageNumber) {
1524 throw new IllegalStateException(
1525 withErrorContext("Child's parent is incorrect " + childMain));
1526 }
1527 boolean expectTail = (subPageNumber == childTailPageNumber);
1528 if(expectTail != childMain._tail) {
1529 throw new IllegalStateException(withErrorContext(
1530 "Child tail status incorrect " + childMain));
1531 }
1532 }
1533 DataPageExtra childExtra = childMain.getExtra();
1534 Entry lastEntry = childExtra._entryView.getLast();
1535 if(e.compareTo(lastEntry) != 0) {
1536 throw new IllegalStateException(withErrorContext(
1537 "Invalid last entry " + e + " but child is " + lastEntry));
1538 }
1539 Entry firstEntry = childExtra._entries.get(0);
1540 if(prevLastEntry.compareTo(firstEntry) >= 0) {
1541 throw new IllegalStateException(withErrorContext(
1542 "Invalid first entry " + firstEntry + " but prev last is " +
1543 prevLastEntry));
1544 }
1545 nextPageNumber = childMain._nextPageNumber;
1546 prevPageNumber = childMain._pageNumber;
1547 prevLastEntry = lastEntry;
1548 } else {
1549
1550
1551 nextPageNumber = null;
1552 prevPageNumber = null;
1553 }
1554 }
1555 }
1556
1557
1558
1559
1560
1561
1562 private void validatePeers(DataPageMain dpMain)
1563 throws IOException {
1564
1565 DataPageMain prevMain = getPageForValidate(dpMain._prevPageNumber);
1566 if(prevMain != null) {
1567 if(prevMain._nextPageNumber != dpMain._pageNumber) {
1568 throw new IllegalStateException(withErrorContext(
1569 "Prev page " + prevMain + " does not ref " + dpMain));
1570 }
1571 validatePeerStatus(dpMain, prevMain);
1572 validatePeerEntries(prevMain, dpMain);
1573 }
1574
1575 DataPageMain nextMain =
1576 getPageForValidate(dpMain._nextPageNumber);
1577 if(nextMain != null) {
1578 if(nextMain._prevPageNumber != dpMain._pageNumber) {
1579 throw new IllegalStateException(withErrorContext(
1580 "Next page " + nextMain + " does not ref " + dpMain));
1581 }
1582 validatePeerStatus(dpMain, nextMain);
1583 validatePeerEntries(dpMain, nextMain);
1584 }
1585 }
1586
1587
1588
1589
1590
1591
1592
1593 private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1594 {
1595 if(dpMain._leaf != peerMain._leaf) {
1596 throw new IllegalStateException(withErrorContext(
1597 "Mismatched peer status " + dpMain._leaf + " " +
1598 peerMain._leaf));
1599 }
1600 if(!dpMain._leaf) {
1601 if((dpMain._parentPageNumber != null) &&
1602 (peerMain._parentPageNumber != null) &&
1603 ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1604 throw new IllegalStateException(withErrorContext(
1605 "Mismatched node parents " + dpMain._parentPageNumber + " " +
1606 peerMain._parentPageNumber));
1607 }
1608 }
1609 }
1610
1611
1612
1613
1614 private void validatePeerEntries(DataPageMain prevMain, DataPageMain nextMain)
1615 throws IOException {
1616 Entry lastE = prevMain.getExtra()._entryView.getLast();
1617 Entry firstE = nextMain.getExtra()._entries.get(0);
1618 if(lastE.compareTo(firstE) >= 0) {
1619 throw new IOException(withErrorContext(
1620 "Unexpected peer order in index entries, " + lastE +
1621 " >= " + firstE));
1622 }
1623 }
1624
1625 private DataPageMain getPageForValidate(
1626 Integer pageNumber) throws IOException {
1627 DataPageMain dpMain = _knownPages.get(pageNumber);
1628 if((dpMain == null) && _forceLoad &&
1629 (pageNumber != INVALID_INDEX_PAGE_NUMBER)) {
1630 dpMain = getDataPage(pageNumber);
1631 if(dpMain != null) {
1632 _knownPages.put(pageNumber, dpMain);
1633 _pendingPages.add(dpMain);
1634 } else {
1635 throw new IllegalStateException(
1636 withErrorContext("Could not find index page " + pageNumber));
1637 }
1638 }
1639 return dpMain;
1640 }
1641 }
1642
1643 }