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