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
37
38
39
40
41 public class IndexPageCache
42 {
43 private enum UpdateType {
44 ADD, REMOVE, REPLACE;
45 }
46
47
48
49 private static final int MAX_CACHE_SIZE = 25;
50
51
52 private final IndexData _indexData;
53
54 private DataPageMain _rootPage;
55
56 private final Map<Integer, DataPageMain> _dataPages =
57 new LinkedHashMap<Integer, DataPageMain>(16, 0.75f, true) {
58 private static final long serialVersionUID = 0L;
59 @Override
60 protected boolean removeEldestEntry(Map.Entry<Integer, DataPageMain> e) {
61
62
63
64 if((size() > MAX_CACHE_SIZE) && !getPageChannel().isWriting()) {
65 purgeOldPages();
66 }
67 return false;
68 }
69 };
70
71 private final List<CacheDataPage> _modifiedPages =
72 new ArrayList<>();
73
74 public IndexPageCache(IndexData indexData) {
75 _indexData = indexData;
76 }
77
78 public IndexData getIndexData() {
79 return _indexData;
80 }
81
82 public PageChannel getPageChannel() {
83 return getIndexData().getPageChannel();
84 }
85
86
87
88
89
90
91 public void setRootPageNumber(int pageNumber) throws IOException {
92 _rootPage = getDataPage(pageNumber);
93
94 _rootPage.initParentPage(INVALID_INDEX_PAGE_NUMBER, false);
95 }
96
97
98
99
100 public void write()
101 throws IOException
102 {
103
104 handleEmptyPages();
105
106 preparePagesForWriting();
107
108 writeDataPages();
109
110 if(_dataPages.size() > MAX_CACHE_SIZE) {
111 purgeOldPages();
112 }
113 }
114
115
116
117
118
119
120 private void handleEmptyPages() throws IOException
121 {
122 for(Iterator<CacheDataPage> iter = _modifiedPages.iterator();
123 iter.hasNext(); ) {
124 CacheDataPage cacheDataPage = iter.next();
125 if(cacheDataPage._extra._entryView.isEmpty()) {
126 if(!cacheDataPage._main.isRoot()) {
127 deleteDataPage(cacheDataPage);
128 } else {
129 writeDataPage(cacheDataPage);
130 }
131 iter.remove();
132 }
133 }
134 }
135
136
137
138
139
140
141 private void preparePagesForWriting() throws IOException
142 {
143 boolean splitPages = false;
144 int maxPageEntrySize = getIndexData().getMaxPageEntrySize();
145
146
147
148 do {
149 splitPages = false;
150
151
152
153 for(int i = 0; i < _modifiedPages.size(); ++i) {
154
155 CacheDataPage cacheDataPage = _modifiedPages.get(i);
156
157 if(!cacheDataPage.isLeaf()) {
158
159 DataPageMain dpMain = cacheDataPage._main;
160 int size = cacheDataPage._extra._entryView.size();
161 if(dpMain.hasChildTail()) {
162 if(size == 1) {
163 demoteTail(cacheDataPage);
164 }
165 } else {
166 if(size > 1) {
167
168 DataPageMain lastChild = dpMain.getChildPage(
169 cacheDataPage._extra._entryView.getLast());
170 if(lastChild._leaf) {
171 promoteTail(cacheDataPage, lastChild);
172 }
173 }
174 }
175 }
176
177
178 if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
179
180
181
182 cacheDataPage._extra.updateEntryPrefix();
183
184
185 if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
186
187 splitPages = true;
188 splitDataPage(cacheDataPage);
189 }
190 }
191 }
192
193 } while(splitPages);
194 }
195
196
197
198
199
200 private void writeDataPages() throws IOException
201 {
202 for(CacheDataPage cacheDataPage : _modifiedPages) {
203 if(cacheDataPage._extra._entryView.isEmpty()) {
204 throw new IllegalStateException(withErrorContext(
205 "Unexpected empty page " + cacheDataPage));
206 }
207 writeDataPage(cacheDataPage);
208 }
209 _modifiedPages.clear();
210 }
211
212
213
214
215
216 public CacheDataPage getCacheDataPage(Integer pageNumber)
217 throws IOException
218 {
219 DataPageMain main = getDataPage(pageNumber);
220 return((main != null) ? new CacheDataPage(main) : null);
221 }
222
223
224
225
226
227 private DataPageMain getDataPage(Integer pageNumber)
228 throws IOException
229 {
230 DataPageMain dataPage = _dataPages.get(pageNumber);
231 if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
232 dataPage = readDataPage(pageNumber)._main;
233 _dataPages.put(pageNumber, dataPage);
234 }
235 return dataPage;
236 }
237
238
239
240
241 private void writeDataPage(CacheDataPage cacheDataPage)
242 throws IOException
243 {
244 getIndexData().writeDataPage(cacheDataPage);
245
246
247 cacheDataPage._extra._modified = false;
248 }
249
250
251
252
253 private void deleteDataPage(CacheDataPage cacheDataPage)
254 throws IOException
255 {
256
257 getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
258
259
260 _dataPages.remove(cacheDataPage._main._pageNumber);
261
262
263 cacheDataPage._extra._modified = false;
264 }
265
266
267
268
269 private CacheDataPage readDataPage(Integer pageNumber)
270 throws IOException
271 {
272 DataPageMain dataPage = new DataPageMain(pageNumber);
273 DataPageExtra extra = new DataPageExtra();
274 CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
275 getIndexData().readDataPage(cacheDataPage);
276
277
278 dataPage.setExtra(extra);
279
280 return cacheDataPage;
281 }
282
283
284
285
286
287
288
289 private Entry removeEntry(CacheDataPage cacheDataPage, int entryIdx)
290 throws IOException
291 {
292 return updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
293 }
294
295
296
297
298
299
300
301
302 private void addEntry(CacheDataPage cacheDataPage,
303 int entryIdx,
304 Entry newEntry)
305 throws IOException
306 {
307 updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
308 }
309
310
311
312
313
314
315
316
317
318 private Entry updateEntry(CacheDataPage cacheDataPage,
319 int entryIdx,
320 Entry newEntry,
321 UpdateType upType)
322 throws IOException
323 {
324 DataPageMain dpMain = cacheDataPage._main;
325 DataPageExtra dpExtra = cacheDataPage._extra;
326
327 if(newEntry != null) {
328 validateEntryForPage(dpMain, newEntry);
329 }
330
331
332
333 CacheDataPage parentDataPage = (!dpMain.isRoot() ?
334 new CacheDataPage(dpMain.getParentPage()) :
335 null);
336
337 Entry oldLastEntry = dpExtra._entryView.getLast();
338 Entry oldEntry = null;
339 int entrySizeDiff = 0;
340
341 switch(upType) {
342 case ADD:
343 dpExtra._entryView.add(entryIdx, newEntry);
344 entrySizeDiff += newEntry.size();
345 break;
346
347 case REPLACE:
348 oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
349 entrySizeDiff += newEntry.size() - oldEntry.size();
350 break;
351
352 case REMOVE: {
353 oldEntry = dpExtra._entryView.remove(entryIdx);
354 entrySizeDiff -= oldEntry.size();
355 break;
356 }
357 default:
358 throw new RuntimeException(withErrorContext(
359 "unknown update type " + upType));
360 }
361
362 boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
363
364
365 if(!updateLast || !dpMain.hasChildTail()) {
366 dpExtra._totalEntrySize += entrySizeDiff;
367 setModified(cacheDataPage);
368
369
370 dpExtra._entryPrefix = EMPTY_PREFIX;
371 }
372
373 if(dpExtra._entryView.isEmpty()) {
374
375 removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
376 return oldEntry;
377 }
378
379
380 if(!updateLast || dpMain.isRoot()) {
381
382 return oldEntry;
383 }
384
385
386 replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
387 return oldEntry;
388 }
389
390
391
392
393
394
395
396
397
398 private void removeDataPage(CacheDataPage parentDataPage,
399 CacheDataPage cacheDataPage,
400 Entry oldLastEntry)
401 throws IOException
402 {
403 DataPageMain dpMain = cacheDataPage._main;
404 DataPageExtra dpExtra = cacheDataPage._extra;
405
406 if(dpMain.hasChildTail()) {
407 throw new IllegalStateException(withErrorContext("Still has child tail?"));
408 }
409
410 if(dpExtra._totalEntrySize != 0) {
411 throw new IllegalStateException(withErrorContext(
412 "Empty page but size is not 0? " + dpExtra._totalEntrySize + ", " +
413 cacheDataPage));
414 }
415
416 if(dpMain.isRoot()) {
417
418 dpExtra._entryPrefix = EMPTY_PREFIX;
419
420 dpMain._leaf = true;
421 dpMain._level = 0;
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, origMain.getLevel());
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 rootMain.getLevel());
711 DataPageMain newMain = newDataPage._main;
712 DataPageExtra newExtra = newDataPage._extra;
713
714
715 newMain._childTailPageNumber = rootMain._childTailPageNumber;
716 newExtra._entries = rootExtra._entries;
717 newExtra._entryPrefix = rootExtra._entryPrefix;
718 newExtra._totalEntrySize = rootExtra._totalEntrySize;
719 newExtra.setEntryView(newMain);
720
721 if(!newMain._leaf) {
722
723 reparentChildren(newDataPage);
724 }
725
726
727
728 rootMain._leaf = false;
729 rootMain._level = newMain._level + 1;
730 rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
731 rootExtra._entries = new ArrayList<>();
732 rootExtra._entryPrefix = EMPTY_PREFIX;
733 rootExtra._totalEntrySize = 0;
734 rootExtra.setEntryView(rootMain);
735
736
737 addParentEntry(rootDataPage, newDataPage);
738
739 return newDataPage;
740 }
741
742
743
744
745
746
747
748
749
750
751 private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
752 boolean isLeaf, int level)
753 throws IOException
754 {
755 DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
756 DataPageExtra dpExtra = new DataPageExtra();
757 dpMain.initParentPage(parentPageNumber, false);
758 dpMain._leaf = isLeaf;
759 dpMain._level = level;
760 dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
761 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
762 dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
763 dpExtra._entries = new ArrayList<>();
764 dpExtra._entryPrefix = EMPTY_PREFIX;
765 dpMain.setExtra(dpExtra);
766
767
768 _dataPages.put(dpMain._pageNumber, dpMain);
769
770
771 _indexData.addOwnedPage(dpMain._pageNumber);
772
773
774 CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
775 setModified(cacheDataPage);
776
777 return cacheDataPage;
778 }
779
780
781
782
783
784
785
786
787 private void addToPeersBefore(CacheDataPage newDataPage,
788 CacheDataPage origDataPage)
789 throws IOException
790 {
791 DataPageMain origMain = origDataPage._main;
792 DataPageMain newMain = newDataPage._main;
793
794 DataPageMain prevMain = origMain.getPrevPage();
795
796 newMain._nextPageNumber = origMain._pageNumber;
797 newMain._prevPageNumber = origMain._prevPageNumber;
798 origMain._prevPageNumber = newMain._pageNumber;
799
800 if(prevMain != null) {
801 setModified(new CacheDataPage(prevMain));
802 prevMain._nextPageNumber = newMain._pageNumber;
803 }
804 }
805
806
807
808
809
810
811 private void separateFromNextPeer(CacheDataPage cacheDataPage)
812 throws IOException
813 {
814 DataPageMain dpMain = cacheDataPage._main;
815
816 setModified(cacheDataPage);
817
818 DataPageMain nextMain = dpMain.getNextPage();
819 setModified(new CacheDataPage(nextMain));
820
821 nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
822 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
823 }
824
825
826
827
828
829
830
831 private void reparentChildren(CacheDataPage cacheDataPage)
832 {
833 DataPageMain dpMain = cacheDataPage._main;
834 DataPageExtra dpExtra = cacheDataPage._extra;
835
836
837
838
839 for(Entry entry : dpExtra._entryView) {
840 Integer childPageNumber = entry.getSubPageNumber();
841 DataPageMain childMain = _dataPages.get(childPageNumber);
842 if(childMain != null) {
843 childMain.setParentPage(dpMain._pageNumber,
844 dpMain.isChildTailPageNumber(childPageNumber));
845 }
846 }
847 }
848
849
850
851
852
853
854
855 private void demoteTail(CacheDataPage cacheDataPage)
856 throws IOException
857 {
858
859
860 DataPageMain dpMain = cacheDataPage._main;
861 DataPageExtra dpExtra = cacheDataPage._extra;
862
863 setModified(cacheDataPage);
864
865 DataPageMain tailMain = dpMain.getChildTailPage();
866 CacheDataPage tailDataPage = new CacheDataPage(tailMain);
867
868
869 updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
870 Entry tailEntry = dpExtra._entryView.demoteTail();
871 dpExtra._totalEntrySize += tailEntry.size();
872 dpExtra._entryPrefix = EMPTY_PREFIX;
873
874 tailMain.setParentPage(dpMain._pageNumber, false);
875 }
876
877
878
879
880
881
882
883 private void promoteTail(CacheDataPage cacheDataPage, DataPageMain lastMain)
884 throws IOException
885 {
886
887 DataPageMain dpMain = cacheDataPage._main;
888 DataPageExtra dpExtra = cacheDataPage._extra;
889
890 setModified(cacheDataPage);
891
892 CacheDataPage lastDataPage = new CacheDataPage(lastMain);
893
894
895 updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
896 Entry lastEntry = dpExtra._entryView.promoteTail();
897 dpExtra._totalEntrySize -= lastEntry.size();
898 dpExtra._entryPrefix = EMPTY_PREFIX;
899
900 lastMain.setParentPage(dpMain._pageNumber, true);
901 }
902
903
904
905
906
907
908 public CacheDataPage findCacheDataPage(Entry e)
909 throws IOException
910 {
911 DataPageMain curPage = _rootPage;
912 while(true) {
913
914 if(curPage._leaf) {
915
916 return new CacheDataPage(curPage);
917 }
918
919 DataPageExtra extra = curPage.getExtra();
920
921
922 int idx = extra._entryView.find(e);
923 if(idx < 0) {
924 idx = missingIndexToInsertionPoint(idx);
925 if(idx == extra._entryView.size()) {
926
927 --idx;
928 }
929 }
930
931 Entry nodeEntry = extra._entryView.get(idx);
932 curPage = curPage.getChildPage(nodeEntry);
933 }
934 }
935
936
937
938
939
940
941
942 private void setModified(CacheDataPage cacheDataPage)
943 {
944 if(!cacheDataPage._extra._modified) {
945 _modifiedPages.add(cacheDataPage);
946 cacheDataPage._extra._modified = true;
947 }
948 }
949
950
951
952
953
954
955
956
957
958
959 private static byte[] findCommonPrefix(Entry e1, Entry e2)
960 {
961 byte[] b1 = e1.getEntryBytes();
962 byte[] b2 = e2.getEntryBytes();
963
964 int maxLen = b1.length;
965 byte[] prefix = b1;
966 if(b1.length > b2.length) {
967 maxLen = b2.length;
968 prefix = b2;
969 }
970
971 int len = 0;
972 while((len < maxLen) && (b1[len] == b2[len])) {
973 ++len;
974 }
975
976 if(len < prefix.length) {
977 if(len == 0) {
978 return EMPTY_PREFIX;
979 }
980
981
982 prefix = ByteUtil.copyOf(prefix, len);
983 }
984
985 return prefix;
986 }
987
988
989
990
991 void validate(boolean forceLoad) throws IOException {
992 new Validator(forceLoad).validate();
993 }
994
995
996
997
998
999
1000
1001 private List<Object> collectPages(List<Object> pages, DataPageMain dpMain) {
1002 try {
1003 CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
1004 pages.add(cacheDataPage);
1005 if(!dpMain._leaf) {
1006 for(Entry e : cacheDataPage._extra._entryView) {
1007 DataPageMain childMain = dpMain.getChildPage(e);
1008 collectPages(pages, childMain);
1009 }
1010 }
1011 } catch(IOException e) {
1012 pages.add("DataPage[" + dpMain._pageNumber + "]: <" + e + ">");
1013 }
1014 return pages;
1015 }
1016
1017
1018
1019
1020
1021 private void purgeOldPages() {
1022 Iterator<DataPageMain> iter = _dataPages.values().iterator();
1023 while(iter.hasNext()) {
1024 DataPageMain dpMain = iter.next();
1025
1026 if(dpMain != _rootPage) {
1027 iter.remove();
1028 if(_dataPages.size() <= MAX_CACHE_SIZE) {
1029 break;
1030 }
1031 }
1032 }
1033 }
1034
1035 @Override
1036 public String toString() {
1037 ToStringBuilder sb = ToStringBuilder.builder(this);
1038 if(_rootPage == null) {
1039 sb.append("pages", "(uninitialized)");
1040 } else {
1041 sb.append("pages", collectPages(new ArrayList<Object>(), _rootPage));
1042 }
1043 return sb.toString();
1044 }
1045
1046 private String withErrorContext(String msg) {
1047 return _indexData.withErrorContext(msg);
1048 }
1049
1050
1051
1052
1053
1054 private class DataPageMain
1055 {
1056 public final int _pageNumber;
1057 public Integer _prevPageNumber;
1058 public Integer _nextPageNumber;
1059 public Integer _childTailPageNumber;
1060 public Integer _parentPageNumber;
1061 public boolean _leaf;
1062
1063 public int _level;
1064 public boolean _tail;
1065 private Reference<DataPageExtra> _extra;
1066
1067 private DataPageMain(int pageNumber) {
1068 _pageNumber = pageNumber;
1069 }
1070
1071 public IndexPageCache getCache() {
1072 return IndexPageCache.this;
1073 }
1074
1075 public boolean isRoot() {
1076 return(this == _rootPage);
1077 }
1078
1079 public boolean isTail() throws IOException
1080 {
1081 resolveParent();
1082 return _tail;
1083 }
1084
1085 public boolean hasChildTail() {
1086 return(_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1087 }
1088
1089 public boolean isChildTailPageNumber(int pageNumber) {
1090 return(_childTailPageNumber == pageNumber);
1091 }
1092
1093 public DataPageMain getParentPage() throws IOException
1094 {
1095 resolveParent();
1096 return IndexPageCache.this.getDataPage(_parentPageNumber);
1097 }
1098
1099 public void initParentPage(Integer parentPageNumber, boolean isTail) {
1100
1101 if(_parentPageNumber == null) {
1102 setParentPage(parentPageNumber, isTail);
1103 }
1104 }
1105
1106 public void setParentPage(Integer parentPageNumber, boolean isTail) {
1107 _parentPageNumber = parentPageNumber;
1108 _tail = isTail;
1109 }
1110
1111 public DataPageMain getPrevPage() throws IOException
1112 {
1113 return IndexPageCache.this.getDataPage(_prevPageNumber);
1114 }
1115
1116 public DataPageMain getNextPage() throws IOException
1117 {
1118 return IndexPageCache.this.getDataPage(_nextPageNumber);
1119 }
1120
1121 public DataPageMain getChildPage(Entry e) throws IOException
1122 {
1123 Integer childPageNumber = e.getSubPageNumber();
1124 return getChildPage(childPageNumber,
1125 isChildTailPageNumber(childPageNumber));
1126 }
1127
1128 public DataPageMain getChildTailPage() throws IOException
1129 {
1130 return getChildPage(_childTailPageNumber, true);
1131 }
1132
1133
1134
1135
1136
1137 private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1138 throws IOException
1139 {
1140 DataPageMain child = getDataPage(childPageNumber);
1141 if(child != null) {
1142
1143 child.initParentPage(_pageNumber, isTail);
1144 }
1145 return child;
1146 }
1147
1148 public int getLevel() throws IOException
1149 {
1150 if(!_leaf && (_level == 0)) {
1151
1152
1153 _level = getChildPage(getExtra()._entries.get(0)).getLevel() + 1;
1154 }
1155 return _level;
1156 }
1157
1158 public DataPageExtra getExtra() throws IOException
1159 {
1160 DataPageExtra extra = _extra.get();
1161 if(extra == null) {
1162 extra = readDataPage(_pageNumber)._extra;
1163 setExtra(extra);
1164 }
1165
1166 return extra;
1167 }
1168
1169 public void setExtra(DataPageExtra extra) throws IOException
1170 {
1171 extra.setEntryView(this);
1172 _extra = new SoftReference<>(extra);
1173 }
1174
1175 private void resolveParent() throws IOException {
1176 if(_parentPageNumber == null) {
1177
1178
1179 findCacheDataPage(getExtra()._entryView.getLast());
1180 if(_parentPageNumber == null) {
1181 throw new IllegalStateException(withErrorContext(
1182 "Parent was not resolved"));
1183 }
1184 }
1185 }
1186
1187 @Override
1188 public String toString() {
1189 return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1190 "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1191 _childTailPageNumber + ")";
1192 }
1193 }
1194
1195
1196
1197
1198
1199 private static class DataPageExtra
1200 {
1201
1202
1203 public List<Entry> _entries;
1204 public EntryListView _entryView;
1205 public byte[] _entryPrefix;
1206 public int _totalEntrySize;
1207 public boolean _modified;
1208
1209 private DataPageExtra()
1210 {
1211 }
1212
1213 public void setEntryView(DataPageMain main) throws IOException {
1214 _entryView = new EntryListView(main, this);
1215 }
1216
1217 public void updateEntryPrefix() {
1218 if(_entryPrefix.length == 0) {
1219
1220 _entryPrefix = findCommonPrefix(_entries.get(0),
1221 _entries.get(_entries.size() - 1));
1222 }
1223 }
1224
1225 @Override
1226 public String toString() {
1227 return ToStringBuilder.builder("DPExtra")
1228 .append(null, _entryView)
1229 .toString();
1230 }
1231 }
1232
1233
1234
1235
1236 private static final class CacheDataPage extends DataPage
1237 {
1238 public final DataPageMain _main;
1239 public final DataPageExtra _extra;
1240
1241 private CacheDataPage(DataPageMain dataPage) throws IOException {
1242 this(dataPage, dataPage.getExtra());
1243 }
1244
1245 private CacheDataPage(DataPageMain dataPage, DataPageExtra extra) {
1246 _main = dataPage;
1247 _extra = extra;
1248 }
1249
1250 @Override
1251 public int getPageNumber() {
1252 return _main._pageNumber;
1253 }
1254
1255 @Override
1256 public boolean isLeaf() {
1257 return _main._leaf;
1258 }
1259
1260 @Override
1261 public void setLeaf(boolean isLeaf) {
1262 _main._leaf = isLeaf;
1263 }
1264
1265 @Override
1266 public int getLevel() throws IOException {
1267 return _main.getLevel();
1268 }
1269
1270 @Override
1271 public void setLevel(int level) {
1272 _main._level = level;
1273 }
1274
1275 @Override
1276 public int getPrevPageNumber() {
1277 return _main._prevPageNumber;
1278 }
1279
1280 @Override
1281 public void setPrevPageNumber(int pageNumber) {
1282 _main._prevPageNumber = pageNumber;
1283 }
1284
1285 @Override
1286 public int getNextPageNumber() {
1287 return _main._nextPageNumber;
1288 }
1289
1290 @Override
1291 public void setNextPageNumber(int pageNumber) {
1292 _main._nextPageNumber = pageNumber;
1293 }
1294
1295 @Override
1296 public int getChildTailPageNumber() {
1297 return _main._childTailPageNumber;
1298 }
1299
1300 @Override
1301 public void setChildTailPageNumber(int pageNumber) {
1302 _main._childTailPageNumber = pageNumber;
1303 }
1304
1305
1306 @Override
1307 public int getTotalEntrySize() {
1308 return _extra._totalEntrySize;
1309 }
1310
1311 @Override
1312 public void setTotalEntrySize(int totalSize) {
1313 _extra._totalEntrySize = totalSize;
1314 }
1315
1316 @Override
1317 public byte[] getEntryPrefix() {
1318 return _extra._entryPrefix;
1319 }
1320
1321 @Override
1322 public void setEntryPrefix(byte[] entryPrefix) {
1323 _extra._entryPrefix = entryPrefix;
1324 }
1325
1326
1327 @Override
1328 public List<Entry> getEntries() {
1329 return _extra._entries;
1330 }
1331
1332 @Override
1333 public void setEntries(List<Entry> entries) {
1334 _extra._entries = entries;
1335 }
1336
1337 @Override
1338 public void addEntry(int idx, Entry entry) throws IOException {
1339 _main.getCache().addEntry(this, idx, entry);
1340 }
1341
1342 @Override
1343 public Entry removeEntry(int idx) throws IOException {
1344 return _main.getCache().removeEntry(this, idx);
1345 }
1346
1347 }
1348
1349
1350
1351
1352
1353 private static class EntryListView extends AbstractList<Entry>
1354 implements RandomAccess
1355 {
1356 private final DataPageExtra _extra;
1357 private Entry _childTailEntry;
1358
1359 private EntryListView(DataPageMain main, DataPageExtra extra)
1360 throws IOException
1361 {
1362 if(main.hasChildTail()) {
1363 _childTailEntry = main.getChildTailPage().getExtra()._entryView
1364 .getLast().asNodeEntry(main._childTailPageNumber);
1365 }
1366 _extra = extra;
1367 }
1368
1369 private List<Entry> getEntries() {
1370 return _extra._entries;
1371 }
1372
1373 @Override
1374 public int size() {
1375 int size = getEntries().size();
1376 if(hasChildTail()) {
1377 ++size;
1378 }
1379 return size;
1380 }
1381
1382 @Override
1383 public Entry get(int idx) {
1384 return (isCurrentChildTailIndex(idx) ?
1385 _childTailEntry :
1386 getEntries().get(idx));
1387 }
1388
1389 @Override
1390 public Entry set(int idx, Entry newEntry) {
1391 return (isCurrentChildTailIndex(idx) ?
1392 setChildTailEntry(newEntry) :
1393 getEntries().set(idx, newEntry));
1394 }
1395
1396 @Override
1397 public void add(int idx, Entry newEntry) {
1398
1399
1400 getEntries().add(idx, newEntry);
1401 }
1402
1403 @Override
1404 public Entry remove(int idx) {
1405 return (isCurrentChildTailIndex(idx) ?
1406 setChildTailEntry(null) :
1407 getEntries().remove(idx));
1408 }
1409
1410 public Entry setChildTailEntry(Entry newEntry) {
1411 Entry old = _childTailEntry;
1412 _childTailEntry = newEntry;
1413 return old;
1414 }
1415
1416 private boolean hasChildTail() {
1417 return(_childTailEntry != null);
1418 }
1419
1420 private boolean isCurrentChildTailIndex(int idx) {
1421 return(idx == getEntries().size());
1422 }
1423
1424 public Entry getLast() {
1425 return(hasChildTail() ? _childTailEntry :
1426 (!getEntries().isEmpty() ?
1427 getEntries().get(getEntries().size() - 1) : null));
1428 }
1429
1430 public Entry demoteTail() {
1431 Entry tail = _childTailEntry;
1432 _childTailEntry = null;
1433 getEntries().add(tail);
1434 return tail;
1435 }
1436
1437 public Entry promoteTail() {
1438 Entry last = getEntries().remove(getEntries().size() - 1);
1439 _childTailEntry = last;
1440 return last;
1441 }
1442
1443 public int find(Entry e) {
1444 return Collections.binarySearch(this, e);
1445 }
1446
1447 }
1448
1449
1450
1451
1452 private final class Validator {
1453 private final boolean _forceLoad;
1454 private final Map<Integer,DataPageMain> _knownPages = new HashMap<>();
1455 private final Queue<DataPageMain> _pendingPages = new LinkedList<>();
1456
1457 private Validator(boolean forceLoad) {
1458 _forceLoad = forceLoad;
1459 _knownPages.putAll(_dataPages);
1460 _pendingPages.addAll(_knownPages.values());
1461 }
1462
1463 void validate() throws IOException {
1464 DataPageMain dpMain = null;
1465 while((dpMain = _pendingPages.poll()) != null) {
1466 DataPageExtra dpExtra = dpMain.getExtra();
1467 validateEntries(dpExtra);
1468 validateChildren(dpMain, dpExtra);
1469 validatePeers(dpMain);
1470 }
1471 }
1472
1473
1474
1475
1476
1477
1478 private void validateEntries(DataPageExtra dpExtra) throws IOException {
1479 int entrySize = 0;
1480 Entry prevEntry = FIRST_ENTRY;
1481 for(Entry e : dpExtra._entries) {
1482 entrySize += e.size();
1483 if(prevEntry.compareTo(e) >= 0) {
1484 throw new IOException(withErrorContext(
1485 "Unexpected order in index entries, " + prevEntry +
1486 " >= " + e));
1487 }
1488 prevEntry = e;
1489 }
1490
1491 if(dpExtra._entryView.hasChildTail()) {
1492 Entry tailE = dpExtra._entryView.getLast();
1493 if(prevEntry.compareTo(tailE) >= 0) {
1494 throw new IOException(withErrorContext(
1495 "Unexpected order in index entries, " + prevEntry +
1496 " >= " + tailE));
1497 }
1498 }
1499
1500 if(entrySize != dpExtra._totalEntrySize) {
1501 throw new IllegalStateException(withErrorContext(
1502 "Expected size " + entrySize +
1503 " but was " + dpExtra._totalEntrySize));
1504 }
1505 }
1506
1507
1508
1509
1510
1511
1512
1513 private void validateChildren(DataPageMain dpMain,
1514 DataPageExtra dpExtra) throws IOException {
1515 int childTailPageNumber = dpMain._childTailPageNumber;
1516 if(dpMain._leaf) {
1517 if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1518 throw new IllegalStateException(
1519 withErrorContext("Leaf page has tail " + dpMain));
1520 }
1521 return;
1522 }
1523 if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1524 throw new IllegalStateException(
1525 withErrorContext("Single child is tail " + dpMain));
1526 }
1527 Integer prevPageNumber = null;
1528 Integer nextPageNumber = null;
1529 Entry prevLastEntry = FIRST_ENTRY;
1530 for(Entry e : dpExtra._entryView) {
1531 validateEntryForPage(dpMain, e);
1532 Integer subPageNumber = e.getSubPageNumber();
1533 DataPageMain childMain = getPageForValidate(subPageNumber);
1534 if(childMain != null) {
1535 if((prevPageNumber != null) &&
1536 ((int)childMain._prevPageNumber != prevPageNumber)) {
1537 throw new IllegalStateException(withErrorContext(
1538 "Child's prevPageNumber is not the previous child for " +
1539 childMain + " " + dpExtra._entryView + " " +
1540 prevPageNumber));
1541 }
1542 if((nextPageNumber != null) &&
1543 (childMain._pageNumber != nextPageNumber)) {
1544 throw new IllegalStateException(withErrorContext(
1545 "Child's pageNumber is not the expected next child for " +
1546 childMain));
1547 }
1548 if(childMain.getLevel() != (dpMain.getLevel() - 1)) {
1549 throw new IllegalStateException(withErrorContext(
1550 "Child's level " + childMain.getLevel() +
1551 " is not one below its parent's " + dpMain.getLevel() +
1552 " for " + childMain));
1553 }
1554 if(childMain._parentPageNumber != null) {
1555 if(childMain._parentPageNumber != dpMain._pageNumber) {
1556 throw new IllegalStateException(
1557 withErrorContext("Child's parent is incorrect " + childMain));
1558 }
1559 boolean expectTail = (subPageNumber == childTailPageNumber);
1560 if(expectTail != childMain._tail) {
1561 throw new IllegalStateException(withErrorContext(
1562 "Child tail status incorrect " + childMain));
1563 }
1564 }
1565 DataPageExtra childExtra = childMain.getExtra();
1566 Entry lastEntry = childExtra._entryView.getLast();
1567 if(e.compareTo(lastEntry) != 0) {
1568 throw new IllegalStateException(withErrorContext(
1569 "Invalid last entry " + e + " but child is " + lastEntry));
1570 }
1571 Entry firstEntry = childExtra._entries.get(0);
1572 if(prevLastEntry.compareTo(firstEntry) >= 0) {
1573 throw new IllegalStateException(withErrorContext(
1574 "Invalid first entry " + firstEntry + " but prev last is " +
1575 prevLastEntry));
1576 }
1577 nextPageNumber = childMain._nextPageNumber;
1578 prevPageNumber = childMain._pageNumber;
1579 prevLastEntry = lastEntry;
1580 } else {
1581
1582
1583 nextPageNumber = null;
1584 prevPageNumber = null;
1585 }
1586 }
1587 }
1588
1589
1590
1591
1592
1593
1594 private void validatePeers(DataPageMain dpMain)
1595 throws IOException {
1596
1597 DataPageMain prevMain = getPageForValidate(dpMain._prevPageNumber);
1598 if(prevMain != null) {
1599 if(prevMain._nextPageNumber != dpMain._pageNumber) {
1600 throw new IllegalStateException(withErrorContext(
1601 "Prev page " + prevMain + " does not ref " + dpMain));
1602 }
1603 validatePeerStatus(dpMain, prevMain);
1604 validatePeerEntries(prevMain, dpMain);
1605 }
1606
1607 DataPageMain nextMain =
1608 getPageForValidate(dpMain._nextPageNumber);
1609 if(nextMain != null) {
1610 if(nextMain._prevPageNumber != dpMain._pageNumber) {
1611 throw new IllegalStateException(withErrorContext(
1612 "Next page " + nextMain + " does not ref " + dpMain));
1613 }
1614 validatePeerStatus(dpMain, nextMain);
1615 validatePeerEntries(dpMain, nextMain);
1616 }
1617 }
1618
1619
1620
1621
1622
1623
1624
1625 private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1626 throws IOException
1627 {
1628 if(dpMain._leaf != peerMain._leaf) {
1629 throw new IllegalStateException(withErrorContext(
1630 "Mismatched peer status " + dpMain._leaf + " " +
1631 peerMain._leaf));
1632 }
1633 if(dpMain.getLevel() != peerMain.getLevel()) {
1634 throw new IllegalStateException(withErrorContext(
1635 "Mismatched peer level " + dpMain.getLevel() + " " +
1636 peerMain.getLevel()));
1637 }
1638 if(!dpMain._leaf) {
1639 if((dpMain._parentPageNumber != null) &&
1640 (peerMain._parentPageNumber != null) &&
1641 ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1642 throw new IllegalStateException(withErrorContext(
1643 "Mismatched node parents " + dpMain._parentPageNumber + " " +
1644 peerMain._parentPageNumber));
1645 }
1646 }
1647 }
1648
1649
1650
1651
1652 private void validatePeerEntries(DataPageMain prevMain, DataPageMain nextMain)
1653 throws IOException {
1654 Entry lastE = prevMain.getExtra()._entryView.getLast();
1655 Entry firstE = nextMain.getExtra()._entries.get(0);
1656 if(lastE.compareTo(firstE) >= 0) {
1657 throw new IOException(withErrorContext(
1658 "Unexpected peer order in index entries, " + lastE +
1659 " >= " + firstE));
1660 }
1661 }
1662
1663 private DataPageMain getPageForValidate(
1664 Integer pageNumber) throws IOException {
1665 DataPageMain dpMain = _knownPages.get(pageNumber);
1666 if((dpMain == null) && _forceLoad &&
1667 (pageNumber != INVALID_INDEX_PAGE_NUMBER)) {
1668 dpMain = getDataPage(pageNumber);
1669 if(dpMain != null) {
1670 _knownPages.put(pageNumber, dpMain);
1671 _pendingPages.add(dpMain);
1672 } else {
1673 throw new IllegalStateException(
1674 withErrorContext("Could not find index page " + pageNumber));
1675 }
1676 }
1677 return dpMain;
1678 }
1679 }
1680
1681 }