1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package com.healthmarketscience.jackcess;
29
30 import java.io.IOException;
31 import java.lang.ref.Reference;
32 import java.lang.ref.SoftReference;
33 import java.util.AbstractList;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.RandomAccess;
41
42
43 import static com.healthmarketscience.jackcess.Index.*;
44
45
46
47
48
49 public class IndexPageCache
50 {
51 private enum UpdateType {
52 ADD, REMOVE, REPLACE;
53 }
54
55
56 private final BigIndex _index;
57
58 private DataPageMain _rootPage;
59
60 private final Map<Integer, DataPageMain> _dataPages =
61 new HashMap<Integer, DataPageMain>();
62
63 private final List<CacheDataPage> _modifiedPages =
64 new ArrayList<CacheDataPage>();
65
66 public IndexPageCache(BigIndex index) {
67 _index = index;
68 }
69
70 public BigIndex getIndex() {
71 return _index;
72 }
73
74 public PageChannel getPageChannel() {
75 return getIndex().getPageChannel();
76 }
77
78
79
80
81
82
83 public void setRootPageNumber(int pageNumber) throws IOException {
84 _rootPage = getDataPage(pageNumber);
85
86 _rootPage.initParentPage(INVALID_INDEX_PAGE_NUMBER, false);
87 }
88
89
90
91
92 public void write()
93 throws IOException
94 {
95
96 handleEmptyPages();
97
98 preparePagesForWriting();
99
100 writeDataPages();
101 }
102
103
104
105
106
107
108 private void handleEmptyPages() throws IOException
109 {
110 for(Iterator<CacheDataPage> iter = _modifiedPages.iterator();
111 iter.hasNext(); ) {
112 CacheDataPage cacheDataPage = iter.next();
113 if(cacheDataPage._extra._entryView.isEmpty()) {
114 if(!cacheDataPage._main.isRoot()) {
115 deleteDataPage(cacheDataPage);
116 } else {
117 writeDataPage(cacheDataPage);
118 }
119 iter.remove();
120 }
121 }
122 }
123
124
125
126
127
128
129 private void preparePagesForWriting() throws IOException
130 {
131 boolean splitPages = false;
132 int maxPageEntrySize = getIndex().getMaxPageEntrySize();
133
134
135
136 do {
137 splitPages = false;
138
139
140
141 for(int i = 0; i < _modifiedPages.size(); ++i) {
142
143 CacheDataPage cacheDataPage = _modifiedPages.get(i);
144
145 if(!cacheDataPage.isLeaf()) {
146
147 DataPageMain dpMain = cacheDataPage._main;
148 int size = cacheDataPage._extra._entryView.size();
149 if(dpMain.hasChildTail()) {
150 if(size == 1) {
151 demoteTail(cacheDataPage);
152 }
153 } else {
154 if(size > 1) {
155 promoteTail(cacheDataPage);
156 }
157 }
158 }
159
160
161 if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
162
163
164
165 cacheDataPage._extra.updateEntryPrefix();
166
167
168 if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
169
170 splitPages = true;
171 splitDataPage(cacheDataPage);
172 }
173 }
174 }
175
176 } while(splitPages);
177 }
178
179
180
181
182
183 private void writeDataPages() throws IOException
184 {
185 for(CacheDataPage cacheDataPage : _modifiedPages) {
186 if(cacheDataPage._extra._entryView.isEmpty()) {
187 throw new IllegalStateException("Unexpected empty page " +
188 cacheDataPage);
189 }
190 writeDataPage(cacheDataPage);
191 }
192 _modifiedPages.clear();
193 }
194
195
196
197
198
199 public CacheDataPage getCacheDataPage(Integer pageNumber)
200 throws IOException
201 {
202 DataPageMain main = getDataPage(pageNumber);
203 return((main != null) ? new CacheDataPage(main) : null);
204 }
205
206
207
208
209
210 private DataPageMain getDataPage(Integer pageNumber)
211 throws IOException
212 {
213 DataPageMain dataPage = _dataPages.get(pageNumber);
214 if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
215 dataPage = readDataPage(pageNumber)._main;
216 _dataPages.put(pageNumber, dataPage);
217 }
218 return dataPage;
219 }
220
221
222
223
224 private void writeDataPage(CacheDataPage cacheDataPage)
225 throws IOException
226 {
227 getIndex().writeDataPage(cacheDataPage);
228
229
230 cacheDataPage._extra._modified = false;
231 }
232
233
234
235
236 private void deleteDataPage(CacheDataPage cacheDataPage)
237 throws IOException
238 {
239
240 getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
241
242
243 _dataPages.remove(cacheDataPage._main._pageNumber);
244
245
246 cacheDataPage._extra._modified = false;
247 }
248
249
250
251
252 private CacheDataPage readDataPage(Integer pageNumber)
253 throws IOException
254 {
255 DataPageMain dataPage = new DataPageMain(pageNumber);
256 DataPageExtra extra = new DataPageExtra();
257 CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
258 getIndex().readDataPage(cacheDataPage);
259
260
261 dataPage.setExtra(extra);
262
263 return cacheDataPage;
264 }
265
266
267
268
269
270
271
272 private void removeEntry(CacheDataPage cacheDataPage, int entryIdx)
273 throws IOException
274 {
275 updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
276 }
277
278
279
280
281
282
283
284
285 private void addEntry(CacheDataPage cacheDataPage,
286 int entryIdx,
287 Entry newEntry)
288 throws IOException
289 {
290 updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
291 }
292
293
294
295
296
297
298
299
300
301 private void updateEntry(CacheDataPage cacheDataPage,
302 int entryIdx,
303 Entry newEntry,
304 UpdateType upType)
305 throws IOException
306 {
307 DataPageMain dpMain = cacheDataPage._main;
308 DataPageExtra dpExtra = cacheDataPage._extra;
309
310 if(newEntry != null) {
311 validateEntryForPage(dpMain, newEntry);
312 }
313
314
315
316 CacheDataPage parentDataPage = (!dpMain.isRoot() ?
317 new CacheDataPage(dpMain.getParentPage()) :
318 null);
319
320 Entry oldLastEntry = dpExtra._entryView.getLast();
321 Entry oldEntry = null;
322 int entrySizeDiff = 0;
323
324 switch(upType) {
325 case ADD:
326 dpExtra._entryView.add(entryIdx, newEntry);
327 entrySizeDiff += newEntry.size();
328 break;
329
330 case REPLACE:
331 oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
332 entrySizeDiff += newEntry.size() - oldEntry.size();
333 break;
334
335 case REMOVE: {
336 oldEntry = dpExtra._entryView.remove(entryIdx);
337 entrySizeDiff -= oldEntry.size();
338 break;
339 }
340 default:
341 throw new RuntimeException("unknown update type " + upType);
342 }
343
344 boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
345
346
347 if(!updateLast || !dpMain.hasChildTail()) {
348 dpExtra._totalEntrySize += entrySizeDiff;
349 setModified(cacheDataPage);
350
351
352 dpExtra._entryPrefix = EMPTY_PREFIX;
353 }
354
355 if(dpExtra._entryView.isEmpty()) {
356
357 removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
358 return;
359 }
360
361
362 if(!updateLast || dpMain.isRoot()) {
363
364 return;
365 }
366
367
368 replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
369 }
370
371
372
373
374
375
376
377
378
379 private void removeDataPage(CacheDataPage parentDataPage,
380 CacheDataPage cacheDataPage,
381 Entry oldLastEntry)
382 throws IOException
383 {
384 DataPageMain dpMain = cacheDataPage._main;
385 DataPageExtra dpExtra = cacheDataPage._extra;
386
387 if(dpMain.hasChildTail()) {
388 throw new IllegalStateException("Still has child tail?");
389 }
390
391 if(dpExtra._totalEntrySize != 0) {
392 throw new IllegalStateException("Empty page but size is not 0? " +
393 dpExtra._totalEntrySize + ", " +
394 cacheDataPage);
395 }
396
397 if(dpMain.isRoot()) {
398
399 dpExtra._entryPrefix = EMPTY_PREFIX;
400
401 dpMain._leaf = true;
402 return;
403 }
404
405
406 updateParentEntry(parentDataPage, cacheDataPage, oldLastEntry, null,
407 UpdateType.REMOVE);
408
409
410 removeFromPeers(cacheDataPage);
411 }
412
413
414
415
416
417
418 private void removeFromPeers(CacheDataPage cacheDataPage)
419 throws IOException
420 {
421 DataPageMain dpMain = cacheDataPage._main;
422
423 Integer prevPageNumber = dpMain._prevPageNumber;
424 Integer nextPageNumber = dpMain._nextPageNumber;
425
426 DataPageMain prevMain = dpMain.getPrevPage();
427 if(prevMain != null) {
428 setModified(new CacheDataPage(prevMain));
429 prevMain._nextPageNumber = nextPageNumber;
430 }
431
432 DataPageMain nextMain = dpMain.getNextPage();
433 if(nextMain != null) {
434 setModified(new CacheDataPage(nextMain));
435 nextMain._prevPageNumber = prevPageNumber;
436 }
437 }
438
439
440
441
442
443
444
445 private void addParentEntry(CacheDataPage parentDataPage,
446 CacheDataPage childDataPage)
447 throws IOException
448 {
449 DataPageExtra childExtra = childDataPage._extra;
450 updateParentEntry(parentDataPage, childDataPage, null,
451 childExtra._entryView.getLast(), UpdateType.ADD);
452 }
453
454
455
456
457
458
459
460
461 private void replaceParentEntry(CacheDataPage parentDataPage,
462 CacheDataPage childDataPage,
463 Entry oldEntry)
464 throws IOException
465 {
466 DataPageExtra childExtra = childDataPage._extra;
467 updateParentEntry(parentDataPage, childDataPage, oldEntry,
468 childExtra._entryView.getLast(), UpdateType.REPLACE);
469 }
470
471
472
473
474
475
476
477
478
479
480
481 private void updateParentEntry(CacheDataPage parentDataPage,
482 CacheDataPage childDataPage,
483 Entry oldEntry, Entry newEntry,
484 UpdateType upType)
485 throws IOException
486 {
487 DataPageMain childMain = childDataPage._main;
488 DataPageExtra parentExtra = parentDataPage._extra;
489
490 if(childMain.isTail() && (upType != UpdateType.REMOVE)) {
491
492
493 updateParentTail(parentDataPage, childDataPage, upType);
494 }
495
496 if(oldEntry != null) {
497 oldEntry = oldEntry.asNodeEntry(childMain._pageNumber);
498 }
499 if(newEntry != null) {
500 newEntry = newEntry.asNodeEntry(childMain._pageNumber);
501 }
502
503 boolean expectFound = true;
504 int idx = 0;
505
506 switch(upType) {
507 case ADD:
508 expectFound = false;
509 idx = parentExtra._entryView.find(newEntry);
510 break;
511
512 case REPLACE:
513 case REMOVE:
514 idx = parentExtra._entryView.find(oldEntry);
515 break;
516
517 default:
518 throw new RuntimeException("unknown update type " + upType);
519 }
520
521 if(idx < 0) {
522 if(expectFound) {
523 throw new IllegalStateException(
524 "Could not find child entry in parent; childEntry " + oldEntry +
525 "; parent " + parentDataPage);
526 }
527 idx = missingIndexToInsertionPoint(idx);
528 } else {
529 if(!expectFound) {
530 throw new IllegalStateException(
531 "Unexpectedly found child entry in parent; childEntry " +
532 newEntry + "; parent " + parentDataPage);
533 }
534 }
535 updateEntry(parentDataPage, idx, newEntry, upType);
536
537 if(childMain.isTail() && (upType == UpdateType.REMOVE)) {
538
539
540 updateParentTail(parentDataPage, childDataPage, upType);
541 }
542 }
543
544
545
546
547
548
549
550
551
552 private void updateParentTail(CacheDataPage parentDataPage,
553 CacheDataPage childDataPage,
554 UpdateType upType)
555 throws IOException
556 {
557 DataPageMain parentMain = parentDataPage._main;
558
559 int newChildTailPageNumber =
560 ((upType == UpdateType.REMOVE) ?
561 INVALID_INDEX_PAGE_NUMBER :
562 childDataPage._main._pageNumber);
563 if(!parentMain.isChildTailPageNumber(newChildTailPageNumber)) {
564 setModified(parentDataPage);
565 parentMain._childTailPageNumber = newChildTailPageNumber;
566 }
567 }
568
569
570
571
572
573
574
575
576
577
578 private void validateEntryForPage(DataPageMain dpMain, Entry entry) {
579 if(dpMain._leaf != entry.isLeafEntry()) {
580 throw new IllegalStateException(
581 "Trying to update page with wrong entry type; pageLeaf " +
582 dpMain._leaf + ", entryLeaf " + entry.isLeafEntry());
583 }
584 }
585
586
587
588
589
590
591 private void splitDataPage(CacheDataPage origDataPage)
592 throws IOException
593 {
594 DataPageMain origMain = origDataPage._main;
595 DataPageExtra origExtra = origDataPage._extra;
596
597 setModified(origDataPage);
598
599 int numEntries = origExtra._entries.size();
600 if(numEntries < 2) {
601 throw new IllegalStateException(
602 "Cannot split page with less than 2 entries " + origDataPage);
603 }
604
605 if(origMain.isRoot()) {
606
607
608 CacheDataPage newDataPage = nestRootDataPage(origDataPage);
609
610
611 origDataPage = newDataPage;
612 origMain = newDataPage._main;
613 origExtra = newDataPage._extra;
614 }
615
616
617
618 DataPageMain parentMain = origMain.getParentPage();
619 CacheDataPage parentDataPage = new CacheDataPage(parentMain);
620
621
622
623
624
625 CacheDataPage newDataPage = allocateNewCacheDataPage(
626 parentMain._pageNumber, origMain._leaf);
627 DataPageMain newMain = newDataPage._main;
628 DataPageExtra newExtra = newDataPage._extra;
629
630 List<Entry> headEntries =
631 origExtra._entries.subList(0, ((numEntries + 1) / 2));
632
633
634
635 for(Entry headEntry : headEntries) {
636 newExtra._totalEntrySize += headEntry.size();
637 newExtra._entries.add(headEntry);
638 }
639 newExtra.setEntryView(newMain);
640
641
642 headEntries.clear();
643 origExtra._entryPrefix = EMPTY_PREFIX;
644 origExtra._totalEntrySize -= newExtra._totalEntrySize;
645
646
647 addToPeersBefore(newDataPage, origDataPage);
648
649 if(!newMain._leaf) {
650
651 reparentChildren(newDataPage);
652
653
654
655
656
657 DataPageMain childMain = newMain.getChildPage(
658 newExtra._entryView.getLast());
659 if(!childMain._leaf) {
660 separateFromNextPeer(new CacheDataPage(childMain));
661 }
662 }
663
664
665 addParentEntry(parentDataPage, newDataPage);
666 }
667
668
669
670
671
672
673
674
675
676
677 private CacheDataPage nestRootDataPage(CacheDataPage rootDataPage)
678 throws IOException
679 {
680 DataPageMain rootMain = rootDataPage._main;
681 DataPageExtra rootExtra = rootDataPage._extra;
682
683 if(!rootMain.isRoot()) {
684 throw new IllegalArgumentException("should be called with root, duh");
685 }
686
687 CacheDataPage newDataPage =
688 allocateNewCacheDataPage(rootMain._pageNumber, rootMain._leaf);
689 DataPageMain newMain = newDataPage._main;
690 DataPageExtra newExtra = newDataPage._extra;
691
692
693 newMain._childTailPageNumber = rootMain._childTailPageNumber;
694 newExtra._entries = rootExtra._entries;
695 newExtra._entryPrefix = rootExtra._entryPrefix;
696 newExtra._totalEntrySize = rootExtra._totalEntrySize;
697 newExtra.setEntryView(newMain);
698
699 if(!newMain._leaf) {
700
701 reparentChildren(newDataPage);
702 }
703
704
705 rootMain._leaf = false;
706 rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
707 rootExtra._entries = new ArrayList<Entry>();
708 rootExtra._entryPrefix = EMPTY_PREFIX;
709 rootExtra._totalEntrySize = 0;
710 rootExtra.setEntryView(rootMain);
711
712
713 addParentEntry(rootDataPage, newDataPage);
714
715 return newDataPage;
716 }
717
718
719
720
721
722
723
724
725
726 private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
727 boolean isLeaf)
728 throws IOException
729 {
730 DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
731 DataPageExtra dpExtra = new DataPageExtra();
732 dpMain.initParentPage(parentPageNumber, false);
733 dpMain._leaf = isLeaf;
734 dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
735 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
736 dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
737 dpExtra._entries = new ArrayList<Entry>();
738 dpExtra._entryPrefix = EMPTY_PREFIX;
739 dpMain.setExtra(dpExtra);
740
741
742 _dataPages.put(dpMain._pageNumber, dpMain);
743
744
745 CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
746 setModified(cacheDataPage);
747
748 return cacheDataPage;
749 }
750
751
752
753
754
755
756
757
758 private void addToPeersBefore(CacheDataPage newDataPage,
759 CacheDataPage origDataPage)
760 throws IOException
761 {
762 DataPageMain origMain = origDataPage._main;
763 DataPageMain newMain = newDataPage._main;
764
765 DataPageMain prevMain = origMain.getPrevPage();
766
767 newMain._nextPageNumber = origMain._pageNumber;
768 newMain._prevPageNumber = origMain._prevPageNumber;
769 origMain._prevPageNumber = newMain._pageNumber;
770
771 if(prevMain != null) {
772 setModified(new CacheDataPage(prevMain));
773 prevMain._nextPageNumber = newMain._pageNumber;
774 }
775 }
776
777
778
779
780
781
782 private void separateFromNextPeer(CacheDataPage cacheDataPage)
783 throws IOException
784 {
785 DataPageMain dpMain = cacheDataPage._main;
786
787 setModified(cacheDataPage);
788
789 DataPageMain nextMain = dpMain.getNextPage();
790 setModified(new CacheDataPage(nextMain));
791
792 nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
793 dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
794 }
795
796
797
798
799
800
801
802 private void reparentChildren(CacheDataPage cacheDataPage)
803 throws IOException
804 {
805 DataPageMain dpMain = cacheDataPage._main;
806 DataPageExtra dpExtra = cacheDataPage._extra;
807
808
809
810
811 for(Entry entry : dpExtra._entryView) {
812 Integer childPageNumber = entry.getSubPageNumber();
813 DataPageMain childMain = _dataPages.get(childPageNumber);
814 if(childMain != null) {
815 childMain.setParentPage(dpMain._pageNumber,
816 dpMain.isChildTailPageNumber(childPageNumber));
817 }
818 }
819 }
820
821
822
823
824
825
826
827 private void demoteTail(CacheDataPage cacheDataPage)
828 throws IOException
829 {
830
831
832 DataPageMain dpMain = cacheDataPage._main;
833 DataPageExtra dpExtra = cacheDataPage._extra;
834
835 setModified(cacheDataPage);
836
837 DataPageMain tailMain = dpMain.getChildTailPage();
838 CacheDataPage tailDataPage = new CacheDataPage(tailMain);
839
840
841 updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
842 Entry tailEntry = dpExtra._entryView.demoteTail();
843 dpExtra._totalEntrySize += tailEntry.size();
844 dpExtra._entryPrefix = EMPTY_PREFIX;
845
846 tailMain.setParentPage(dpMain._pageNumber, false);
847 }
848
849
850
851
852
853
854
855 private void promoteTail(CacheDataPage cacheDataPage)
856 throws IOException
857 {
858
859 DataPageMain dpMain = cacheDataPage._main;
860 DataPageExtra dpExtra = cacheDataPage._extra;
861
862 setModified(cacheDataPage);
863
864 DataPageMain lastMain = dpMain.getChildPage(dpExtra._entryView.getLast());
865 CacheDataPage lastDataPage = new CacheDataPage(lastMain);
866
867
868 updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
869 Entry lastEntry = dpExtra._entryView.promoteTail();
870 dpExtra._totalEntrySize -= lastEntry.size();
871 dpExtra._entryPrefix = EMPTY_PREFIX;
872
873 lastMain.setParentPage(dpMain._pageNumber, true);
874 }
875
876
877
878
879
880
881 public CacheDataPage findCacheDataPage(Entry e)
882 throws IOException
883 {
884 DataPageMain curPage = _rootPage;
885 while(true) {
886
887 if(curPage._leaf) {
888
889 return new CacheDataPage(curPage);
890 }
891
892 DataPageExtra extra = curPage.getExtra();
893
894
895 int idx = extra._entryView.find(e);
896 if(idx < 0) {
897 idx = missingIndexToInsertionPoint(idx);
898 if(idx == extra._entryView.size()) {
899
900 --idx;
901 }
902 }
903
904 Entry nodeEntry = extra._entryView.get(idx);
905 curPage = curPage.getChildPage(nodeEntry);
906 }
907 }
908
909
910
911
912
913
914
915 private void setModified(CacheDataPage cacheDataPage)
916 {
917 if(!cacheDataPage._extra._modified) {
918 _modifiedPages.add(cacheDataPage);
919 cacheDataPage._extra._modified = true;
920 }
921 }
922
923
924
925
926
927
928
929
930
931
932 private static byte[] findCommonPrefix(Entry e1, Entry e2)
933 {
934 byte[] b1 = e1.getEntryBytes();
935 byte[] b2 = e2.getEntryBytes();
936
937 int maxLen = b1.length;
938 byte[] prefix = b1;
939 if(b1.length > b2.length) {
940 maxLen = b2.length;
941 prefix = b2;
942 }
943
944 int len = 0;
945 while((len < maxLen) && (b1[len] == b2[len])) {
946 ++len;
947 }
948
949 if(len < prefix.length) {
950 if(len == 0) {
951 return EMPTY_PREFIX;
952 }
953
954
955 byte[] tmpPrefix = new byte[len];
956 System.arraycopy(prefix, 0, tmpPrefix, 0, len);
957 prefix = tmpPrefix;
958 }
959
960 return prefix;
961 }
962
963
964
965
966 void validate() throws IOException {
967 for(DataPageMain dpMain : _dataPages.values()) {
968 DataPageExtra dpExtra = dpMain.getExtra();
969 validateEntries(dpExtra);
970 validateChildren(dpMain, dpExtra);
971 validatePeers(dpMain);
972 }
973 }
974
975
976
977
978
979
980 private void validateEntries(DataPageExtra dpExtra) throws IOException {
981 int entrySize = 0;
982 Entry prevEntry = Index.FIRST_ENTRY;
983 for(Entry e : dpExtra._entries) {
984 entrySize += e.size();
985 if(prevEntry.compareTo(e) >= 0) {
986 throw new IOException("Unexpected order in index entries, " +
987 prevEntry + " >= " + e);
988 }
989 prevEntry = e;
990 }
991 if(entrySize != dpExtra._totalEntrySize) {
992 throw new IllegalStateException("Expected size " + entrySize +
993 " but was " + dpExtra._totalEntrySize);
994 }
995 }
996
997
998
999
1000
1001
1002
1003 private void validateChildren(DataPageMain dpMain,
1004 DataPageExtra dpExtra) throws IOException {
1005 int childTailPageNumber = dpMain._childTailPageNumber;
1006 if(dpMain._leaf) {
1007 if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1008 throw new IllegalStateException("Leaf page has tail " + dpMain);
1009 }
1010 return;
1011 }
1012 if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1013 throw new IllegalStateException("Single child is tail " + dpMain);
1014 }
1015 for(Entry e : dpExtra._entryView) {
1016 validateEntryForPage(dpMain, e);
1017 Integer subPageNumber = e.getSubPageNumber();
1018 DataPageMain childMain = _dataPages.get(subPageNumber);
1019 if(childMain != null) {
1020 if(childMain._parentPageNumber != null) {
1021 if((int)childMain._parentPageNumber != dpMain._pageNumber) {
1022 throw new IllegalStateException("Child's parent is incorrect " +
1023 childMain);
1024 }
1025 boolean expectTail = ((int)subPageNumber == childTailPageNumber);
1026 if(expectTail != childMain._tail) {
1027 throw new IllegalStateException("Child tail status incorrect " +
1028 childMain);
1029 }
1030 }
1031 Entry lastEntry = childMain.getExtra()._entryView.getLast();
1032 if(e.compareTo(lastEntry) != 0) {
1033 throw new IllegalStateException("Invalid entry " + e +
1034 " but child is " + lastEntry);
1035 }
1036 }
1037 }
1038 }
1039
1040
1041
1042
1043
1044
1045 private void validatePeers(DataPageMain dpMain) throws IOException {
1046 DataPageMain prevMain = _dataPages.get(dpMain._prevPageNumber);
1047 if(prevMain != null) {
1048 if((int)prevMain._nextPageNumber != dpMain._pageNumber) {
1049 throw new IllegalStateException("Prev page " + prevMain +
1050 " does not ref " + dpMain);
1051 }
1052 validatePeerStatus(dpMain, prevMain);
1053 }
1054
1055 DataPageMain nextMain = _dataPages.get(dpMain._nextPageNumber);
1056 if(nextMain != null) {
1057 if((int)nextMain._prevPageNumber != dpMain._pageNumber) {
1058 throw new IllegalStateException("Next page " + nextMain +
1059 " does not ref " + dpMain);
1060 }
1061 validatePeerStatus(dpMain, nextMain);
1062 }
1063 }
1064
1065
1066
1067
1068
1069
1070
1071 private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1072 throws IOException
1073 {
1074 if(dpMain._leaf != peerMain._leaf) {
1075 throw new IllegalStateException("Mismatched peer status " +
1076 dpMain._leaf + " " + peerMain._leaf);
1077 }
1078 if(!dpMain._leaf) {
1079 if((dpMain._parentPageNumber != null) &&
1080 (peerMain._parentPageNumber != null) &&
1081 ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1082 throw new IllegalStateException("Mismatched node parents " +
1083 dpMain._parentPageNumber + " " +
1084 peerMain._parentPageNumber);
1085 }
1086 }
1087 }
1088
1089
1090
1091
1092
1093
1094
1095 private void dumpPage(StringBuilder rtn, DataPageMain dpMain) {
1096 try {
1097 CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
1098 rtn.append(cacheDataPage).append("\n");
1099 if(!dpMain._leaf) {
1100 for(Entry e : cacheDataPage._extra._entryView) {
1101 DataPageMain childMain = dpMain.getChildPage(e);
1102 dumpPage(rtn, childMain);
1103 }
1104 }
1105 } catch(IOException e) {
1106 rtn.append("Page[" + dpMain._pageNumber + "]: " + e);
1107 }
1108 }
1109
1110 @Override
1111 public String toString() {
1112 if(_rootPage == null) {
1113 return "Cache: (uninitialized)";
1114 }
1115
1116 StringBuilder rtn = new StringBuilder("Cache: \n");
1117 dumpPage(rtn, _rootPage);
1118 return rtn.toString();
1119 }
1120
1121
1122
1123
1124 private class DataPageMain
1125 {
1126 public final int _pageNumber;
1127 public Integer _prevPageNumber;
1128 public Integer _nextPageNumber;
1129 public Integer _childTailPageNumber;
1130 public Integer _parentPageNumber;
1131 public boolean _leaf;
1132 public boolean _tail;
1133 private Reference<DataPageExtra> _extra;
1134
1135 private DataPageMain(int pageNumber) {
1136 _pageNumber = pageNumber;
1137 }
1138
1139 public IndexPageCache getCache() {
1140 return IndexPageCache.this;
1141 }
1142
1143 public boolean isRoot() {
1144 return(this == _rootPage);
1145 }
1146
1147 public boolean isTail() throws IOException
1148 {
1149 resolveParent();
1150 return _tail;
1151 }
1152
1153 public boolean hasChildTail() {
1154 return((int)_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1155 }
1156
1157 public boolean isChildTailPageNumber(int pageNumber) {
1158 return((int)_childTailPageNumber == pageNumber);
1159 }
1160
1161 public DataPageMain getParentPage() throws IOException
1162 {
1163 resolveParent();
1164 return IndexPageCache.this.getDataPage(_parentPageNumber);
1165 }
1166
1167 public void initParentPage(Integer parentPageNumber, boolean isTail) {
1168
1169 if(_parentPageNumber == null) {
1170 setParentPage(parentPageNumber, isTail);
1171 }
1172 }
1173
1174 public void setParentPage(Integer parentPageNumber, boolean isTail) {
1175 _parentPageNumber = parentPageNumber;
1176 _tail = isTail;
1177 }
1178
1179 public DataPageMain getPrevPage() throws IOException
1180 {
1181 return IndexPageCache.this.getDataPage(_prevPageNumber);
1182 }
1183
1184 public DataPageMain getNextPage() throws IOException
1185 {
1186 return IndexPageCache.this.getDataPage(_nextPageNumber);
1187 }
1188
1189 public DataPageMain getChildPage(Entry e) throws IOException
1190 {
1191 Integer childPageNumber = e.getSubPageNumber();
1192 return getChildPage(childPageNumber,
1193 isChildTailPageNumber(childPageNumber));
1194 }
1195
1196 public DataPageMain getChildTailPage() throws IOException
1197 {
1198 return getChildPage(_childTailPageNumber, true);
1199 }
1200
1201
1202
1203
1204
1205 private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1206 throws IOException
1207 {
1208 DataPageMain child = getDataPage(childPageNumber);
1209 if(child != null) {
1210
1211 child.initParentPage(_pageNumber, isTail);
1212 }
1213 return child;
1214 }
1215
1216 public DataPageExtra getExtra() throws IOException
1217 {
1218 DataPageExtra extra = _extra.get();
1219 if(extra == null) {
1220 extra = readDataPage(_pageNumber)._extra;
1221 setExtra(extra);
1222 }
1223
1224 return extra;
1225 }
1226
1227 public void setExtra(DataPageExtra extra) throws IOException
1228 {
1229 extra.setEntryView(this);
1230 _extra = new SoftReference<DataPageExtra>(extra);
1231 }
1232
1233 private void resolveParent() throws IOException {
1234 if(_parentPageNumber == null) {
1235
1236
1237 findCacheDataPage(getExtra()._entryView.getLast());
1238 if(_parentPageNumber == null) {
1239 throw new IllegalStateException("Parent was not resolved");
1240 }
1241 }
1242 }
1243
1244 @Override
1245 public String toString() {
1246 return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1247 "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1248 _childTailPageNumber + ")";
1249 }
1250 }
1251
1252
1253
1254
1255
1256 private static class DataPageExtra
1257 {
1258
1259
1260 public List<Entry> _entries;
1261 public EntryListView _entryView;
1262 public byte[] _entryPrefix;
1263 public int _totalEntrySize;
1264 public boolean _modified;
1265
1266 private DataPageExtra()
1267 {
1268 }
1269
1270 public void setEntryView(DataPageMain main) throws IOException {
1271 _entryView = new EntryListView(main, this);
1272 }
1273
1274 public void updateEntryPrefix() {
1275 if(_entryPrefix.length == 0) {
1276
1277 _entryPrefix = findCommonPrefix(_entries.get(0),
1278 _entries.get(_entries.size() - 1));
1279 }
1280 }
1281
1282 @Override
1283 public String toString() {
1284 return "DPExtra: " + _entryView;
1285 }
1286 }
1287
1288
1289
1290
1291 public static final class CacheDataPage
1292 extends Index.DataPage
1293 {
1294 public final DataPageMain _main;
1295 public final DataPageExtra _extra;
1296
1297 private CacheDataPage(DataPageMain dataPage) throws IOExc