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