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