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.nio.ByteBuffer;
21 import java.util.ArrayList;
22 import java.util.BitSet;
23 import java.util.List;
24
25
26
27
28
29
30 public class UsageMap
31 {
32
33 public static final byte MAP_TYPE_INLINE = 0x0;
34
35 public static final byte MAP_TYPE_REFERENCE = 0x1;
36
37
38
39 static final int POINTER_SIZE = 4;
40
41
42 private static final int INVALID_BIT_INDEX = -1;
43
44
45 private final DatabaseImpl _database;
46
47 private final int _tablePageNum;
48
49 private int _startOffset;
50
51 private final short _rowStart;
52
53 private int _startPage;
54
55 private int _endPage;
56
57 private final BitSet _pageNumbers = new BitSet();
58
59 private final ByteBuffer _tableBuffer;
60
61
62 private int _modCount;
63
64
65 private Handler _handler;
66
67
68 static final String MSG_PREFIX_UNRECOGNIZED_MAP = "Unrecognized map type: ";
69
70
71
72
73
74
75
76 private UsageMap(DatabaseImpl database, ByteBuffer tableBuffer,
77 int pageNum, short rowStart)
78 {
79 _database = database;
80 _tableBuffer = tableBuffer;
81 _tablePageNum = pageNum;
82 _rowStart = rowStart;
83 _tableBuffer.position(_rowStart + getFormat().OFFSET_USAGE_MAP_START);
84 _startOffset = _tableBuffer.position();
85 }
86
87 public DatabaseImpl getDatabase() {
88 return _database;
89 }
90
91 public JetFormat getFormat() {
92 return getDatabase().getFormat();
93 }
94
95 public PageChannel getPageChannel() {
96 return getDatabase().getPageChannel();
97 }
98
99
100
101
102
103
104
105 public static UsageMap read(DatabaseImpl database, ByteBuffer buf)
106 throws IOException
107 {
108
109 if(buf.remaining() < POINTER_SIZE) {
110 throw new IOException("Table definition ends inside a usage map " +
111 "pointer, " + buf.remaining() + " bytes left");
112 }
113 int umapRowNum = buf.get();
114 int umapPageNum = ByteUtil.get3ByteInt(buf);
115 return read(database, umapPageNum, umapRowNum, false);
116 }
117
118
119
120
121
122
123
124
125
126 static UsageMap read(DatabaseImpl database, int pageNum,
127 int rowNum, boolean isGlobal)
128 throws IOException
129 {
130 if(pageNum <= 0) {
131
132 throw new IOException("Invalid usage map page number " + pageNum);
133 }
134
135 JetFormat format = database.getFormat();
136 PageChannel pageChannel = database.getPageChannel();
137 ByteBuffer tableBuffer = pageChannel.createPageBuffer();
138 pageChannel.readPage(tableBuffer, pageNum);
139
140
141
142 if((rowNum < 0) ||
143 ((TableImpl.getRowStartOffset(rowNum, format) +
144 format.SIZE_ROW_LOCATION) > format.PAGE_SIZE)) {
145 throw new IOException("Invalid usage map row number " + rowNum +
146 " on page " + pageNum);
147 }
148
149 short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format);
150 int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format);
151
152
153
154
155
156 if((rowStart >= rowEnd) || (rowEnd > format.PAGE_SIZE) ||
157 ((rowEnd - rowStart) < format.OFFSET_USAGE_MAP_START)) {
158 throw new IOException("Invalid usage map row " + rowNum + " on page " +
159 pageNum + ", " + rowStart + " to " + rowEnd +
160 " is not a usage map within the page");
161 }
162
163 tableBuffer.limit(rowEnd);
164 byte mapType = tableBuffer.get(rowStart);
165 UsageMapkcess/impl/UsageMap.html#UsageMap">UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart);
166 rtn.initHandler(mapType, isGlobal);
167 return rtn;
168 }
169
170 private void initHandler(byte mapType, boolean isGlobal)
171 throws IOException
172 {
173 if (mapType == MAP_TYPE_INLINE) {
174 _handler = (isGlobal ? new GlobalInlineHandler() :
175 new InlineHandler());
176 } else if (mapType == MAP_TYPE_REFERENCE) {
177 _handler = (isGlobal ? new GlobalReferenceHandler() :
178 new ReferenceHandler());
179 } else {
180 throw new IOException(MSG_PREFIX_UNRECOGNIZED_MAP + mapType);
181 }
182 }
183
184 public PageCursor cursor() {
185 return new PageCursor();
186 }
187
188 public int getPageCount() {
189 return _pageNumbers.cardinality();
190 }
191
192 protected short getRowStart() {
193 return _rowStart;
194 }
195
196 protected int getRowEnd() {
197 return getTableBuffer().limit();
198 }
199
200 protected void setStartOffset(int startOffset) {
201 _startOffset = startOffset;
202 }
203
204 protected int getStartOffset() {
205 return _startOffset;
206 }
207
208 protected ByteBuffer getTableBuffer() {
209 return _tableBuffer;
210 }
211
212 protected int getTablePageNumber() {
213 return _tablePageNum;
214 }
215
216 protected int getStartPage() {
217 return _startPage;
218 }
219
220 protected int getEndPage() {
221 return _endPage;
222 }
223
224 protected BitSet getPageNumbers() {
225 return _pageNumbers;
226 }
227
228 protected void setPageRange(int newStartPage, int newEndPage) {
229 _startPage = newStartPage;
230 _endPage = newEndPage;
231 }
232
233 protected boolean isPageWithinRange(int pageNumber)
234 {
235 return((pageNumber >= _startPage) && (pageNumber < _endPage));
236 }
237
238 protected int getFirstPageNumber() {
239 return bitIndexToPageNumber(getNextBitIndex(-1),
240 RowIdImpl.LAST_PAGE_NUMBER);
241 }
242
243 protected int getNextPageNumber(int curPage) {
244 return bitIndexToPageNumber(
245 getNextBitIndex(pageNumberToBitIndex(curPage)),
246 RowIdImpl.LAST_PAGE_NUMBER);
247 }
248
249 protected int getNextBitIndex(int curIndex) {
250 return _pageNumbers.nextSetBit(curIndex + 1);
251 }
252
253 protected int getLastPageNumber() {
254 return bitIndexToPageNumber(getPrevBitIndex(_pageNumbers.length()),
255 RowIdImpl.FIRST_PAGE_NUMBER);
256 }
257
258 protected int getPrevPageNumber(int curPage) {
259 return bitIndexToPageNumber(
260 getPrevBitIndex(pageNumberToBitIndex(curPage)),
261 RowIdImpl.FIRST_PAGE_NUMBER);
262 }
263
264 protected int getPrevBitIndex(int curIndex) {
265 --curIndex;
266 while((curIndex >= 0) && !_pageNumbers.get(curIndex)) {
267 --curIndex;
268 }
269 return curIndex;
270 }
271
272 protected int bitIndexToPageNumber(int bitIndex,
273 int invalidPageNumber) {
274 return((bitIndex >= 0) ? (_startPage + bitIndex) : invalidPageNumber);
275 }
276
277 protected int pageNumberToBitIndex(int pageNumber) {
278 return((pageNumber >= 0) ? (pageNumber - _startPage) :
279 INVALID_BIT_INDEX);
280 }
281
282 protected void clearTableAndPages()
283 {
284
285 _pageNumbers.clear();
286 _startPage = 0;
287 _endPage = 0;
288 ++_modCount;
289
290
291 int tableStart = getRowStart() + 1;
292 int tableEnd = getRowEnd();
293 ByteUtil.clearRange(_tableBuffer, tableStart, tableEnd);
294 }
295
296 protected void writeTable()
297 throws IOException
298 {
299
300 getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart);
301 }
302
303
304
305
306
307 protected void processMap(ByteBuffer buffer, int bufferStartPage)
308 throws IOException
309 {
310 int byteCount = 0;
311 while (buffer.hasRemaining()) {
312 byte b = buffer.get();
313 if(b != (byte)0) {
314 for (int i = 0; i < 8; i++) {
315 if ((b & (1 << i)) != 0) {
316 int pageNumberOffset = (byteCount * 8 + i) + bufferStartPage;
317 int pageNumber = bitIndexToPageNumber(
318 pageNumberOffset,
319 PageChannel.INVALID_PAGE_NUMBER);
320 if(!isPageWithinRange(pageNumber)) {
321 throw new IOException(
322 "found page number " + pageNumber
323 + " in usage map outside of expected range " +
324 _startPage + " to " + _endPage);
325 }
326 _pageNumbers.set(pageNumberOffset);
327 }
328 }
329 }
330 byteCount++;
331 }
332 }
333
334
335
336
337 public boolean containsPageNumber(int pageNumber) {
338 return _handler.containsPageNumber(pageNumber);
339 }
340
341
342
343
344
345
346
347
348
349
350
351 int getNextPageNumberFrom(int fromPageNumber) {
352 int fromIndex = pageNumberToBitIndex(fromPageNumber);
353 if(fromIndex < 0) {
354 fromIndex = 0;
355 }
356 return bitIndexToPageNumber(_pageNumbers.nextSetBit(fromIndex),
357 PageChannel.INVALID_PAGE_NUMBER);
358 }
359
360
361
362
363
364
365
366
367 boolean holdsPageNumber(int pageNumber) {
368 int index = pageNumberToBitIndex(pageNumber);
369 return ((index >= 0) && _pageNumbers.get(index));
370 }
371
372
373
374
375 public void addPageNumber(int pageNumber) throws IOException {
376 ++_modCount;
377 _handler.addOrRemovePageNumber(pageNumber, true, false);
378 }
379
380
381
382
383 public void removePageNumber(int pageNumber)
384 throws IOException
385 {
386 removePageNumber(pageNumber, true);
387 }
388
389 private void removePageNumber(int pageNumber, boolean force)
390 throws IOException
391 {
392 ++_modCount;
393 _handler.addOrRemovePageNumber(pageNumber, false, force);
394 }
395
396 protected void updateMap(int absolutePageNumber,
397 int bufferRelativePageNumber,
398 ByteBuffer buffer, boolean add, boolean force)
399 throws IOException
400 {
401
402 int offset = bufferRelativePageNumber / 8;
403 int bitmask = 1 << (bufferRelativePageNumber % 8);
404 byte b = buffer.get(_startOffset + offset);
405
406
407 int pageNumberOffset = pageNumberToBitIndex(absolutePageNumber);
408 boolean isOn = _pageNumbers.get(pageNumberOffset);
409 if((isOn == add) && !force) {
410 throw new IOException("Page number " + absolutePageNumber + " already " +
411 ((add) ? "added to" : "removed from") +
412 " usage map, expected range " +
413 _startPage + " to " + _endPage);
414 }
415
416
417 if (add) {
418 b |= bitmask;
419 _pageNumbers.set(pageNumberOffset);
420 } else {
421 b &= ~bitmask;
422 _pageNumbers.clear(pageNumberOffset);
423 }
424 buffer.put(_startOffset + offset, b);
425 }
426
427
428
429
430 private void promoteInlineHandlerToReferenceHandler(int newPageNumber)
431 throws IOException
432 {
433
434 int oldStartPage = _startPage;
435 BitSet oldPageNumbers = (BitSet)_pageNumbers.clone();
436
437
438 clearTableAndPages();
439
440
441 _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE);
442
443
444 writeTable();
445
446
447 _handler = new ReferenceHandler();
448
449
450 reAddPages(oldStartPage, oldPageNumbers, newPageNumber);
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464
465 private void promoteGlobalInlineHandlerToReferenceHandler(
466 int frontierPageNumber)
467 throws IOException
468 {
469
470
471
472
473 clearTableAndPages();
474 _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE);
475 writeTable();
476
477
478
479 _handler = new GlobalReferenceHandler();
480
481
482
483
484
485 for(int pageNumber = 0; pageNumber <= frontierPageNumber; ++pageNumber) {
486 _handler.addOrRemovePageNumber(pageNumber, false, true);
487 }
488 }
489
490 private void reAddPages(int oldStartPage, BitSet oldPageNumbers,
491 int newPageNumber)
492 throws IOException
493 {
494
495 for(int i = oldPageNumbers.nextSetBit(0); i >= 0;
496 i = oldPageNumbers.nextSetBit(i + 1)) {
497 addPageNumber(oldStartPage + i);
498 }
499
500 if(newPageNumber > PageChannel.INVALID_PAGE_NUMBER) {
501
502 addPageNumber(newPageNumber);
503 }
504 }
505
506 @Override
507 public String toString() {
508
509 List<String> ranges = new ArrayList<>();
510 PageCursor pCursor = cursor();
511 int curRangeStart = Integer.MIN_VALUE;
512 int prevPage = Integer.MIN_VALUE;
513 while(true) {
514 int nextPage = pCursor.getNextPage();
515 if(nextPage < 0) {
516 break;
517 }
518
519 if(nextPage != (prevPage + 1)) {
520 if(prevPage >= 0) {
521 rangeToString(ranges, curRangeStart, prevPage);
522 }
523 curRangeStart = nextPage;
524 }
525 prevPage = nextPage;
526 }
527 if(prevPage >= 0) {
528 rangeToString(ranges, curRangeStart, prevPage);
529 }
530
531 return ToStringBuilder.valueBuilder(
532 _handler.getClass().getSimpleName())
533 .append("range", "(" + _startPage + "-" + _endPage + ")")
534 .append("pageNumbers", ranges)
535 .toString();
536 }
537
538 private static void rangeToString(List<String> ranges, int rangeStart,
539 int rangeEnd)
540 {
541 if(rangeEnd > rangeStart) {
542 ranges.add(rangeStart + "-" + rangeEnd);
543 } else {
544 ranges.add(String.valueOf(rangeStart));
545 }
546 }
547
548 private static int toValidStartPage(int startPage) {
549
550 return ((startPage / 8) * 8);
551 }
552
553 private abstract class Handler
554 {
555 protected Handler() {
556 }
557
558 public boolean containsPageNumber(int pageNumber) {
559 return(isPageWithinRange(pageNumber) &&
560 getPageNumbers().get(pageNumberToBitIndex(pageNumber)));
561 }
562
563
564
565
566
567
568 public abstract void addOrRemovePageNumber(int pageNumber, boolean add,
569 boolean force)
570 throws IOException;
571 }
572
573
574
575
576
577
578
579
580
581 private class InlineHandler extends Handler
582 {
583 private final int _maxInlinePages;
584
585 protected InlineHandler() throws IOException
586 {
587 _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8;
588 int startPage = getTableBuffer().getInt(getRowStart() + 1);
589 setInlinePageRange(startPage);
590 processMap(getTableBuffer(), 0);
591 }
592
593 protected final int getMaxInlinePages() {
594 return _maxInlinePages;
595 }
596
597 protected final int getInlineDataStart() {
598 return getRowStart() + getFormat().OFFSET_USAGE_MAP_START;
599 }
600
601 protected final int getInlineDataEnd() {
602 return getRowEnd();
603 }
604
605
606
607
608
609 private void setInlinePageRange(int startPage) {
610 setPageRange(startPage, startPage + getMaxInlinePages());
611 }
612
613 @Override
614 public void addOrRemovePageNumber(int pageNumber, boolean add,
615 boolean force)
616 throws IOException
617 {
618 if(isPageWithinRange(pageNumber)) {
619
620
621 int bufferRelativePageNumber = pageNumberToBitIndex(pageNumber);
622 updateMap(pageNumber, bufferRelativePageNumber, getTableBuffer(), add,
623 force);
624
625 writeTable();
626
627 } else {
628
629
630 addOrRemovePageNumberOutsideRange(pageNumber, add, force);
631 }
632 }
633
634 protected void addOrRemovePageNumberOutsideRange(
635 int pageNumber, boolean add, boolean force)
636 throws IOException
637 {
638
639
640 if(add) {
641
642 int firstPage = getFirstPageNumber();
643 int lastPage = getLastPageNumber();
644
645
646 if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
647
648 firstPage = pageNumber;
649 lastPage = pageNumber;
650 } else if(pageNumber > lastPage) {
651 lastPage = pageNumber;
652 } else {
653 firstPage = pageNumber;
654 }
655
656 firstPage = toValidStartPage(firstPage);
657
658 if((lastPage - firstPage + 1) < getMaxInlinePages()) {
659
660
661 moveToNewStartPage(firstPage, pageNumber);
662
663 } else {
664
665
666 promoteInlineHandlerToReferenceHandler(pageNumber);
667 }
668
669 } else {
670
671
672 if(!force) {
673
674
675
676 throw new IOException("Page number " + pageNumber +
677 " already removed from usage map" +
678 ", expected range " +
679 _startPage + " to " + _endPage);
680 }
681 }
682 }
683
684
685
686
687
688
689
690 protected final void moveToNewStartPage(int newStartPage, int newPageNumber)
691 throws IOException
692 {
693 int oldStartPage = getStartPage();
694 BitSet oldPageNumbers = (BitSet)getPageNumbers().clone();
695
696
697 clearTableAndPages();
698
699
700 ByteBuffer tableBuffer = getTableBuffer();
701 tableBuffer.position(getRowStart() + 1);
702 tableBuffer.putInt(newStartPage);
703
704
705 writeTable();
706
707
708 setInlinePageRange(newStartPage);
709
710
711 reAddPages(oldStartPage, oldPageNumbers, newPageNumber);
712 }
713 }
714
715
716
717
718
719
720
721
722
723
724
725
726
727 private class GlobalInlineHandler extends InlineHandler
728 {
729 private GlobalInlineHandler() throws IOException {
730 }
731
732 @Override
733 public boolean containsPageNumber(int pageNumber) {
734
735 throw new UnsupportedOperationException();
736 }
737
738 @Override
739 protected void addOrRemovePageNumberOutsideRange(
740 int pageNumber, boolean add, boolean force)
741 throws IOException
742 {
743
744
745
746
747
748 if(!add) {
749
750
751
752
753 promoteGlobalInlineHandlerToReferenceHandler(pageNumber);
754 }
755 }
756 }
757
758
759
760
761
762
763
764
765 private class ReferenceHandler extends Handler
766 {
767
768 private final TempPageHolder _mapPageHolder =
769 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
770 private final int _maxPagesPerUsageMapPage;
771
772 private ReferenceHandler() throws IOException
773 {
774 _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE -
775 getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8);
776 int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4;
777 setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
778 setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage));
779
780
781
782
783 for (int i = 0; i < numUsagePages; i++) {
784 int mapPageNum = getTableBuffer().getInt(
785 calculateMapPagePointerOffset(i));
786 if (mapPageNum > 0) {
787 ByteBuffer mapPageBuffer =
788 _mapPageHolder.setPage(getPageChannel(), mapPageNum);
789 byte pageType = mapPageBuffer.get();
790 if (pageType != PageTypes.USAGE_MAP) {
791 throw new IOException("Looking for usage map at page " +
792 mapPageNum + ", but page type is " +
793 pageType);
794 }
795 mapPageBuffer.position(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
796 processMap(mapPageBuffer, (_maxPagesPerUsageMapPage * i));
797 }
798 }
799 }
800
801 protected final int getMaxPagesPerUsagePage() {
802 return _maxPagesPerUsageMapPage;
803 }
804
805 @Override
806 public void addOrRemovePageNumber(int pageNumber, boolean add,
807 boolean force)
808 throws IOException
809 {
810 if(!isPageWithinRange(pageNumber)) {
811 if(force) {
812 return;
813 }
814 throw new IOException("Page number " + pageNumber +
815 " is out of supported range");
816 }
817 int pageIndex = (pageNumber / getMaxPagesPerUsagePage());
818 int mapPageNum = getTableBuffer().getInt(
819 calculateMapPagePointerOffset(pageIndex));
820 ByteBuffer mapPageBuffer = null;
821 if(mapPageNum > 0) {
822 mapPageBuffer = _mapPageHolder.setPage(getPageChannel(), mapPageNum);
823 } else {
824
825 mapPageBuffer = createNewUsageMapPage(pageIndex);
826 mapPageNum = _mapPageHolder.getPageNumber();
827 }
828 updateMap(pageNumber,
829 (pageNumber - (getMaxPagesPerUsagePage() * pageIndex)),
830 mapPageBuffer, add, force);
831 getPageChannel().writePage(mapPageBuffer, mapPageNum);
832 }
833
834
835
836
837
838
839 private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException
840 {
841 ByteBuffer mapPageBuffer = allocateNewUsageMapPage(pageIndex);
842 int mapPageNum = _mapPageHolder.getPageNumber();
843 getTableBuffer().putInt(calculateMapPagePointerOffset(pageIndex),
844 mapPageNum);
845 writeTable();
846 return mapPageBuffer;
847 }
848
849 private int calculateMapPagePointerOffset(int pageIndex) {
850 return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS +
851 (pageIndex * 4);
852 }
853
854 protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
855 throws IOException
856 {
857 ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel());
858 mapPageBuffer.put(PageTypes.USAGE_MAP);
859 mapPageBuffer.put((byte) 0x01);
860 mapPageBuffer.putShort((short) 0);
861 return mapPageBuffer;
862 }
863 }
864
865
866
867
868
869
870
871
872
873
874
875
876
877 private class GlobalReferenceHandler extends ReferenceHandler
878 {
879 private boolean _allocatingPage;
880 private Integer _pendingPage;
881
882 private GlobalReferenceHandler() throws IOException {
883 }
884
885 @Override
886 public boolean containsPageNumber(int pageNumber) {
887
888 throw new UnsupportedOperationException();
889 }
890
891 @Override
892 public void addOrRemovePageNumber(int pageNumber, boolean add,
893 boolean force)
894 throws IOException
895 {
896 if(_allocatingPage && !add) {
897
898
899 if(_pendingPage != null) {
900 throw new IllegalStateException("should only have single pending page");
901 }
902 _pendingPage = pageNumber;
903 return;
904 }
905
906 super.addOrRemovePageNumber(pageNumber, add, force);
907
908 while(_pendingPage != null) {
909
910
911
912
913
914
915
916
917 int removedPageNumber = _pendingPage;
918 _pendingPage = null;
919
920 super.addOrRemovePageNumber(removedPageNumber, false, true);
921 }
922 }
923
924 @Override
925 protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
926 throws IOException
927 {
928 try {
929
930
931 _allocatingPage = true;
932
933 ByteBuffer mapPageBuffer = super.allocateNewUsageMapPage(pageIndex);
934
935
936
937
938
939
940 int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA;
941 ByteUtil.fillRange(mapPageBuffer, dataStart,
942 getFormat().PAGE_SIZE - dataStart);
943
944 int maxPagesPerUmapPage = getMaxPagesPerUsagePage();
945 int firstNewPage = (pageIndex * maxPagesPerUmapPage);
946 int lastNewPage = firstNewPage + maxPagesPerUmapPage;
947 _pageNumbers.set(firstNewPage, lastNewPage);
948
949 return mapPageBuffer;
950
951 } finally {
952 _allocatingPage = false;
953 }
954 }
955 }
956
957
958
959
960
961 public final class PageCursor
962 {
963
964 private final DirHandler _forwardDirHandler = new ForwardDirHandler();
965
966 private final DirHandler _reverseDirHandler = new ReverseDirHandler();
967
968 private int _curPageNumber;
969
970 private int _prevPageNumber;
971
972
973
974 private int _lastModCount;
975
976 private PageCursor() {
977 reset();
978 }
979
980 public UsageMap getUsageMap() {
981 return UsageMap.this;
982 }
983
984
985
986
987 private DirHandler getDirHandler(boolean moveForward) {
988 return (moveForward ? _forwardDirHandler : _reverseDirHandler);
989 }
990
991
992
993
994
995 public boolean isUpToDate() {
996 return(UsageMap.this._modCount == _lastModCount);
997 }
998
999
1000
1001
1002
1003 public int getNextPage() {
1004 return getAnotherPage(CursorImpl.MOVE_FORWARD);
1005 }
1006
1007
1008
1009
1010
1011 public int getPreviousPage() {
1012 return getAnotherPage(CursorImpl.MOVE_REVERSE);
1013 }
1014
1015
1016
1017
1018 private int getAnotherPage(boolean moveForward) {
1019 DirHandler handler = getDirHandler(moveForward);
1020 if(_curPageNumber == handler.getEndPageNumber()) {
1021 if(!isUpToDate()) {
1022 restorePosition(_prevPageNumber);
1023
1024 } else {
1025
1026 return _curPageNumber;
1027 }
1028 }
1029
1030 checkForModification();
1031
1032 _prevPageNumber = _curPageNumber;
1033 _curPageNumber = handler.getAnotherPageNumber(_curPageNumber);
1034 return _curPageNumber;
1035 }
1036
1037
1038
1039
1040
1041 public void reset() {
1042 beforeFirst();
1043 }
1044
1045
1046
1047
1048
1049 public void beforeFirst() {
1050 reset(CursorImpl.MOVE_FORWARD);
1051 }
1052
1053
1054
1055
1056
1057 public void afterLast() {
1058 reset(CursorImpl.MOVE_REVERSE);
1059 }
1060
1061
1062
1063
1064 protected void reset(boolean moveForward) {
1065 _curPageNumber = getDirHandler(moveForward).getBeginningPageNumber();
1066 _prevPageNumber = _curPageNumber;
1067 _lastModCount = UsageMap.this._modCount;
1068 }
1069
1070
1071
1072
1073
1074 private void restorePosition(int curPageNumber)
1075 {
1076 restorePosition(curPageNumber, _curPageNumber);
1077 }
1078
1079
1080
1081
1082 protected void restorePosition(int curPageNumber, int prevPageNumber)
1083 {
1084 if((curPageNumber != _curPageNumber) ||
1085 (prevPageNumber != _prevPageNumber))
1086 {
1087 _prevPageNumber = updatePosition(prevPageNumber);
1088 _curPageNumber = updatePosition(curPageNumber);
1089 _lastModCount = UsageMap.this._modCount;
1090 } else {
1091 checkForModification();
1092 }
1093 }
1094
1095
1096
1097
1098 private void checkForModification() {
1099 if(!isUpToDate()) {
1100 _prevPageNumber = updatePosition(_prevPageNumber);
1101 _curPageNumber = updatePosition(_curPageNumber);
1102 _lastModCount = UsageMap.this._modCount;
1103 }
1104 }
1105
1106 private int updatePosition(int pageNumber) {
1107 if(pageNumber < UsageMap.this.getFirstPageNumber()) {
1108 pageNumber = RowIdImpl.FIRST_PAGE_NUMBER;
1109 } else if(pageNumber > UsageMap.this.getLastPageNumber()) {
1110 pageNumber = RowIdImpl.LAST_PAGE_NUMBER;
1111 }
1112 return pageNumber;
1113 }
1114
1115 @Override
1116 public String toString() {
1117 return getClass().getSimpleName() + " CurPosition " + _curPageNumber +
1118 ", PrevPosition " + _prevPageNumber;
1119 }
1120
1121
1122
1123
1124
1125
1126 private abstract class DirHandler {
1127 public abstract int getAnotherPageNumber(int curPageNumber);
1128 public abstract int getBeginningPageNumber();
1129 public abstract int getEndPageNumber();
1130 }
1131
1132
1133
1134
1135 private final class ForwardDirHandler extends DirHandler {
1136 @Override
1137 public int getAnotherPageNumber(int curPageNumber) {
1138 if(curPageNumber == getBeginningPageNumber()) {
1139 return UsageMap.this.getFirstPageNumber();
1140 }
1141 return UsageMap.this.getNextPageNumber(curPageNumber);
1142 }
1143 @Override
1144 public int getBeginningPageNumber() {
1145 return RowIdImpl.FIRST_PAGE_NUMBER;
1146 }
1147 @Override
1148 public int getEndPageNumber() {
1149 return RowIdImpl.LAST_PAGE_NUMBER;
1150 }
1151 }
1152
1153
1154
1155
1156 private final class ReverseDirHandler extends DirHandler {
1157 @Override
1158 public int getAnotherPageNumber(int curPageNumber) {
1159 if(curPageNumber == getBeginningPageNumber()) {
1160 return UsageMap.this.getLastPageNumber();
1161 }
1162 return UsageMap.this.getPrevPageNumber(curPageNumber);
1163 }
1164 @Override
1165 public int getBeginningPageNumber() {
1166 return RowIdImpl.LAST_PAGE_NUMBER;
1167 }
1168 @Override
1169 public int getEndPageNumber() {
1170 return RowIdImpl.FIRST_PAGE_NUMBER;
1171 }
1172 }
1173
1174 }
1175
1176 }