View Javadoc
1   /*
2   Copyright (c) 2005 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
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   * Describes which database pages a particular table uses
28   * @author Tim McCune
29   */
30  public class UsageMap
31  {
32    /** Inline map type */
33    public static final byte MAP_TYPE_INLINE = 0x0;
34    /** Reference map type, for maps that are too large to fit inline */
35    public static final byte MAP_TYPE_REFERENCE = 0x1;
36  
37    /** bit index value for an invalid page number */
38    private static final int INVALID_BIT_INDEX = -1;
39  
40    /** owning database */
41    private final DatabaseImpl _database;
42    /** Page number of the map table declaration */
43    private final int _tablePageNum;
44    /** Offset of the data page at which the usage map data starts */
45    private int _startOffset;
46    /** Offset of the data page at which the usage map declaration starts */
47    private final short _rowStart;
48    /** First page that this usage map applies to */
49    private int _startPage;
50    /** Last page that this usage map applies to */
51    private int _endPage;
52    /** bits representing page numbers used, offset from _startPage */
53    private final BitSet _pageNumbers = new BitSet();
54    /** Buffer that contains the usage map table declaration page */
55    private final ByteBuffer _tableBuffer;
56    /** modification count on the usage map, used to keep the cursors in
57        sync */
58    private int _modCount;
59    /** the current handler implementation for reading/writing the specific
60        usage map type.  note, this may change over time. */
61    private Handler _handler;
62  
63    /** Error message prefix used when map type is unrecognized. */
64    static final String MSG_PREFIX_UNRECOGNIZED_MAP = "Unrecognized map type: ";
65  
66      /**
67     * @param database database that contains this usage map
68     * @param tableBuffer Buffer that contains this map's declaration
69     * @param pageNum Page number that this usage map is contained in
70     * @param rowStart Offset at which the declaration starts in the buffer
71     */
72    private UsageMap(DatabaseImpl database, ByteBuffer tableBuffer,
73                     int pageNum, short rowStart)
74    {
75      _database = database;
76      _tableBuffer = tableBuffer;
77      _tablePageNum = pageNum;
78      _rowStart = rowStart;
79      _tableBuffer.position(_rowStart + getFormat().OFFSET_USAGE_MAP_START);
80      _startOffset = _tableBuffer.position();
81    }
82  
83    public DatabaseImpl getDatabase() {
84      return _database;
85    }
86  
87    public JetFormat getFormat() {
88      return getDatabase().getFormat();
89    }
90  
91    public PageChannel getPageChannel() {
92      return getDatabase().getPageChannel();
93    }
94  
95    /**
96     * @param database database that contains this usage map
97     * @param buf buffer which contains the usage map row info
98     * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on
99     *         which type of map is found
100    */
101   public static UsageMap read(DatabaseImpl database, ByteBuffer buf)
102     throws IOException
103   {
104     int umapRowNum = buf.get();
105     int umapPageNum = ByteUtil.get3ByteInt(buf);
106     return read(database, umapPageNum, umapRowNum, false);
107   }
108 
109   /**
110    * @param database database that contains this usage map
111    * @param pageNum Page number that this usage map is contained in
112    * @param rowNum Number of the row on the page that contains this usage map
113    * @param isGlobal whether or not we are reading the "global" usage map
114    * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on
115    *         which type of map is found
116    */
117   static UsageMap read(DatabaseImpl database, int pageNum,
118                        int rowNum, boolean isGlobal)
119     throws IOException
120   {
121     if(pageNum <= 0) {
122       // usage maps will never appear on page 0 (or less)
123       throw new IllegalStateException("Invalid usage map page number " + pageNum);
124     }
125 
126     JetFormat format = database.getFormat();
127     PageChannel pageChannel = database.getPageChannel();
128     ByteBuffer tableBuffer = pageChannel.createPageBuffer();
129     pageChannel.readPage(tableBuffer, pageNum);
130     short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format);
131     int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format);
132     tableBuffer.limit(rowEnd);
133     byte mapType = tableBuffer.get(rowStart);
134     UsageMapkcess/impl/UsageMap.html#UsageMap">UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart);
135     rtn.initHandler(mapType, isGlobal);
136     return rtn;
137   }
138 
139   private void initHandler(byte mapType, boolean isGlobal)
140     throws IOException
141   {
142     if (mapType == MAP_TYPE_INLINE) {
143       _handler = (isGlobal ? new GlobalInlineHandler() :
144                   new InlineHandler());
145     } else if (mapType == MAP_TYPE_REFERENCE) {
146       _handler = (isGlobal ? new GlobalReferenceHandler() :
147                   new ReferenceHandler());
148     } else {
149       throw new IOException(MSG_PREFIX_UNRECOGNIZED_MAP + mapType);
150     }
151   }
152 
153   public PageCursor cursor() {
154     return new PageCursor();
155   }
156 
157   public int getPageCount() {
158     return _pageNumbers.cardinality();
159   }
160 
161   protected short getRowStart() {
162     return _rowStart;
163   }
164 
165   protected int getRowEnd() {
166     return getTableBuffer().limit();
167   }
168 
169   protected void setStartOffset(int startOffset) {
170     _startOffset = startOffset;
171   }
172 
173   protected int getStartOffset() {
174     return _startOffset;
175   }
176 
177   protected ByteBuffer getTableBuffer() {
178     return _tableBuffer;
179   }
180 
181   protected int getTablePageNumber() {
182     return _tablePageNum;
183   }
184 
185   protected int getStartPage() {
186     return _startPage;
187   }
188 
189   protected int getEndPage() {
190     return _endPage;
191   }
192 
193   protected BitSet getPageNumbers() {
194     return _pageNumbers;
195   }
196 
197   protected void setPageRange(int newStartPage, int newEndPage) {
198     _startPage = newStartPage;
199     _endPage = newEndPage;
200   }
201 
202   protected boolean isPageWithinRange(int pageNumber)
203   {
204     return((pageNumber >= _startPage) && (pageNumber < _endPage));
205   }
206 
207   protected int getFirstPageNumber() {
208     return bitIndexToPageNumber(getNextBitIndex(-1),
209                                 RowIdImpl.LAST_PAGE_NUMBER);
210   }
211 
212   protected int getNextPageNumber(int curPage) {
213     return bitIndexToPageNumber(
214         getNextBitIndex(pageNumberToBitIndex(curPage)),
215         RowIdImpl.LAST_PAGE_NUMBER);
216   }
217 
218   protected int getNextBitIndex(int curIndex) {
219     return _pageNumbers.nextSetBit(curIndex + 1);
220   }
221 
222   protected int getLastPageNumber() {
223     return bitIndexToPageNumber(getPrevBitIndex(_pageNumbers.length()),
224                                 RowIdImpl.FIRST_PAGE_NUMBER);
225   }
226 
227   protected int getPrevPageNumber(int curPage) {
228     return bitIndexToPageNumber(
229         getPrevBitIndex(pageNumberToBitIndex(curPage)),
230         RowIdImpl.FIRST_PAGE_NUMBER);
231   }
232 
233   protected int getPrevBitIndex(int curIndex) {
234     --curIndex;
235     while((curIndex >= 0) && !_pageNumbers.get(curIndex)) {
236       --curIndex;
237     }
238     return curIndex;
239   }
240 
241   protected int bitIndexToPageNumber(int bitIndex,
242                                      int invalidPageNumber) {
243     return((bitIndex >= 0) ? (_startPage + bitIndex) : invalidPageNumber);
244   }
245 
246   protected int pageNumberToBitIndex(int pageNumber) {
247     return((pageNumber >= 0) ? (pageNumber - _startPage) :
248            INVALID_BIT_INDEX);
249   }
250 
251   protected void clearTableAndPages()
252   {
253     // reset some values
254     _pageNumbers.clear();
255     _startPage = 0;
256     _endPage = 0;
257     ++_modCount;
258 
259     // clear out the table data (everything except map type)
260     int tableStart = getRowStart() + 1;
261     int tableEnd = getRowEnd();
262     ByteUtil.clearRange(_tableBuffer, tableStart, tableEnd);
263   }
264 
265   protected void writeTable()
266     throws IOException
267   {
268     // note, we only want to write the row data with which we are working
269     getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart);
270   }
271 
272   /**
273    * Read in the page numbers in this inline map
274    */
275   protected void processMap(ByteBuffer buffer, int bufferStartPage)
276   {
277     int byteCount = 0;
278     while (buffer.hasRemaining()) {
279       byte b = buffer.get();
280       if(b != (byte)0) {
281         for (int i = 0; i < 8; i++) {
282           if ((b & (1 << i)) != 0) {
283             int pageNumberOffset = (byteCount * 8 + i) + bufferStartPage;
284             int pageNumber = bitIndexToPageNumber(
285                 pageNumberOffset,
286                 PageChannel.INVALID_PAGE_NUMBER);
287             if(!isPageWithinRange(pageNumber)) {
288               throw new IllegalStateException(
289                   "found page number " + pageNumber
290                   + " in usage map outside of expected range " +
291                   _startPage + " to " + _endPage);
292             }
293             _pageNumbers.set(pageNumberOffset);
294           }
295         }
296       }
297       byteCount++;
298     }
299   }
300 
301   /**
302    * Determines if the given page number is contained in this map.
303    */
304   public boolean containsPageNumber(int pageNumber) {
305     return _handler.containsPageNumber(pageNumber);
306   }
307 
308   /**
309    * Add a page number to this usage map
310    */
311   public void addPageNumber(int pageNumber) throws IOException {
312     ++_modCount;
313     _handler.addOrRemovePageNumber(pageNumber, true, false);
314   }
315 
316   /**
317    * Remove a page number from this usage map
318    */
319   public void removePageNumber(int pageNumber)
320     throws IOException
321   {
322     removePageNumber(pageNumber, true);
323   }
324 
325   private void removePageNumber(int pageNumber, boolean force)
326     throws IOException
327   {
328     ++_modCount;
329     _handler.addOrRemovePageNumber(pageNumber, false, force);
330   }
331 
332   protected void updateMap(int absolutePageNumber,
333                            int bufferRelativePageNumber,
334                            ByteBuffer buffer, boolean add, boolean force)
335     throws IOException
336   {
337     //Find the byte to which to apply the bitmask and create the bitmask
338     int offset = bufferRelativePageNumber / 8;
339     int bitmask = 1 << (bufferRelativePageNumber % 8);
340     byte b = buffer.get(_startOffset + offset);
341 
342     // check current value for this page number
343     int pageNumberOffset = pageNumberToBitIndex(absolutePageNumber);
344     boolean isOn = _pageNumbers.get(pageNumberOffset);
345     if((isOn == add) && !force) {
346       throw new IOException("Page number " + absolutePageNumber + " already " +
347                             ((add) ? "added to" : "removed from") +
348                             " usage map, expected range " +
349                             _startPage + " to " + _endPage);
350     }
351 
352     //Apply the bitmask
353     if (add) {
354       b |= bitmask;
355       _pageNumbers.set(pageNumberOffset);
356     } else {
357       b &= ~bitmask;
358       _pageNumbers.clear(pageNumberOffset);
359     }
360     buffer.put(_startOffset + offset, b);
361   }
362 
363   /**
364    * Promotes and inline usage map to a reference usage map.
365    */
366   private void promoteInlineHandlerToReferenceHandler(int newPageNumber)
367     throws IOException
368   {
369     // copy current page number info to new references and then clear old
370     int oldStartPage = _startPage;
371     BitSet oldPageNumbers = (BitSet)_pageNumbers.clone();
372 
373     // clear out the main table (inline usage map data and start page)
374     clearTableAndPages();
375 
376     // set the new map type
377     _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE);
378 
379     // write the new table data
380     writeTable();
381 
382     // set new handler
383     _handler = new ReferenceHandler();
384 
385     // update new handler with old data
386     reAddPages(oldStartPage, oldPageNumbers, newPageNumber);
387   }
388 
389   private void reAddPages(int oldStartPage, BitSet oldPageNumbers,
390                           int newPageNumber)
391     throws IOException
392   {
393     // add all the old pages back in
394     for(int i = oldPageNumbers.nextSetBit(0); i >= 0;
395         i = oldPageNumbers.nextSetBit(i + 1)) {
396       addPageNumber(oldStartPage + i);
397     }
398 
399     if(newPageNumber > PageChannel.INVALID_PAGE_NUMBER) {
400       // and then add the new page
401       addPageNumber(newPageNumber);
402     }
403   }
404 
405   @Override
406   public String toString() {
407 
408     List<String> ranges = new ArrayList<String>();
409     PageCursor pCursor = cursor();
410     int curRangeStart = Integer.MIN_VALUE;
411     int prevPage = Integer.MIN_VALUE;
412     while(true) {
413       int nextPage = pCursor.getNextPage();
414       if(nextPage < 0) {
415         break;
416       }
417 
418       if(nextPage != (prevPage + 1)) {
419         if(prevPage >= 0) {
420           rangeToString(ranges, curRangeStart, prevPage);
421         }
422         curRangeStart = nextPage;
423       }
424       prevPage = nextPage;
425     }
426     if(prevPage >= 0) {
427       rangeToString(ranges, curRangeStart, prevPage);
428     }
429 
430     return CustomToStringStyle.valueBuilder(
431         _handler.getClass().getSimpleName())
432       .append("range", "(" + _startPage + "-" + _endPage + ")")
433       .append("pageNumbers", ranges)
434       .toString();
435   }
436 
437   private static void rangeToString(List<String> ranges, int rangeStart,
438                                     int rangeEnd)
439   {
440     if(rangeEnd > rangeStart) {
441       ranges.add(rangeStart + "-" + rangeEnd);
442     } else {
443       ranges.add(String.valueOf(rangeStart));
444     }
445   }
446 
447   private static int toValidStartPage(int startPage) {
448     // start page must be a multiple of 8
449     return ((startPage / 8) * 8);
450   }
451 
452   private abstract class Handler
453   {
454     protected Handler() {
455     }
456 
457     public boolean containsPageNumber(int pageNumber) {
458       return(isPageWithinRange(pageNumber) &&
459              getPageNumbers().get(pageNumberToBitIndex(pageNumber)));
460     }
461 
462     /**
463      * @param pageNumber Page number to add or remove from this map
464      * @param add True to add it, false to remove it
465      * @param force true to force add/remove and ignore certain inconsistencies
466      */
467     public abstract void addOrRemovePageNumber(int pageNumber, boolean add,
468                                                boolean force)
469       throws IOException;
470   }
471 
472   /**
473    * Usage map whose map is written inline in the same page.  For Jet4, this
474    * type of map can usually contains a maximum of 512 pages.  Free space maps
475    * are always inline, used space maps may be inline or reference.  It has a
476    * start page, which all page numbers in its map are calculated as starting
477    * from.
478    * @author Tim McCune
479    */
480   private class InlineHandler extends Handler
481   {
482     private final int _maxInlinePages;
483 
484     protected InlineHandler()
485     {
486       _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8;
487       int startPage = getTableBuffer().getInt(getRowStart() + 1);
488       setInlinePageRange(startPage);
489       processMap(getTableBuffer(), 0);
490     }
491 
492     protected final int getMaxInlinePages() {
493       return _maxInlinePages;
494     }
495 
496     protected final int getInlineDataStart() {
497       return getRowStart() + getFormat().OFFSET_USAGE_MAP_START;
498     }
499 
500     protected final int getInlineDataEnd() {
501       return getRowEnd();
502     }
503 
504     /**
505      * Sets the page range for an inline usage map starting from the given
506      * page.
507      */
508     private void setInlinePageRange(int startPage) {
509       setPageRange(startPage, startPage + getMaxInlinePages());
510     }
511 
512     @Override
513     public void addOrRemovePageNumber(int pageNumber, boolean add,
514                                       boolean force)
515       throws IOException
516     {
517       if(isPageWithinRange(pageNumber)) {
518 
519         // easy enough, just update the inline data
520         int bufferRelativePageNumber = pageNumberToBitIndex(pageNumber);
521         updateMap(pageNumber, bufferRelativePageNumber, getTableBuffer(), add,
522                   force);
523         // Write the updated map back to disk
524         writeTable();
525 
526       } else {
527 
528         // uh-oh, we've split our britches.  what now?
529         addOrRemovePageNumberOutsideRange(pageNumber, add, force);
530       }
531     }
532 
533     protected void addOrRemovePageNumberOutsideRange(
534         int pageNumber, boolean add, boolean force)
535       throws IOException
536     {
537       // determine what our status is before taking action
538 
539       if(add) {
540 
541         int firstPage = getFirstPageNumber();
542         int lastPage = getLastPageNumber();
543 
544         // we are adding, can we shift the bits and stay inline?
545         if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
546           // no pages currently
547           firstPage = pageNumber;
548           lastPage = pageNumber;
549         } else if(pageNumber > lastPage) {
550           lastPage = pageNumber;
551         } else {
552           firstPage = pageNumber;
553         }
554 
555         firstPage = toValidStartPage(firstPage);
556 
557         if((lastPage - firstPage + 1) < getMaxInlinePages()) {
558 
559           // we can still fit within an inline map
560           moveToNewStartPage(firstPage, pageNumber);
561 
562         } else {
563           // not going to happen, need to promote the usage map to a
564           // reference map
565           promoteInlineHandlerToReferenceHandler(pageNumber);
566         }
567 
568       } else {
569 
570         // we are removing, what does that mean?
571         if(!force) {
572 
573           // this should not happen, we are removing a page which is not in
574           // the map
575           throw new IOException("Page number " + pageNumber +
576                                 " already removed from usage map" +
577                                 ", expected range " +
578                                 _startPage + " to " + _endPage);
579         }
580       }
581     }
582 
583     /**
584      * Shifts the inline usage map so that it now starts with the given page.
585      * @param newStartPage new page at which to start
586      * @param newPageNumber optional page number to add once the map has been
587      *                      shifted to the new start page
588      */
589     protected final void moveToNewStartPage(int newStartPage, int newPageNumber)
590       throws IOException
591     {
592       int oldStartPage = getStartPage();
593       BitSet oldPageNumbers = (BitSet)getPageNumbers().clone();
594 
595       // clear out the main table (inline usage map data and start page)
596       clearTableAndPages();
597 
598       // write new start page
599       ByteBuffer tableBuffer = getTableBuffer();
600       tableBuffer.position(getRowStart() + 1);
601       tableBuffer.putInt(newStartPage);
602 
603       // write the new table data
604       writeTable();
605 
606       // set new page range
607       setInlinePageRange(newStartPage);
608 
609       // put the pages back in
610       reAddPages(oldStartPage, oldPageNumbers, newPageNumber);
611     }
612   }
613 
614   /**
615    * Modified version of an "inline" usage map used for the global usage map.
616    * When an inline usage map is used for the global usage map, we assume
617    * out-of-range bits are on.  We never promote the global usage map to a
618    * reference usage map (although ms access may).
619    *
620    * Note, this UsageMap does not implement all the methods "correctly".  Only
621    * addPageNumber and removePageNumber should be called by PageChannel.
622    */
623   private class GlobalInlineHandler extends InlineHandler
624   {
625     private GlobalInlineHandler() {
626     }
627 
628     @Override
629     public boolean containsPageNumber(int pageNumber) {
630       // should never be called on global map
631       throw new UnsupportedOperationException();
632     }
633 
634     @Override
635     protected void addOrRemovePageNumberOutsideRange(
636         int pageNumber, boolean add, boolean force)
637       throws IOException
638     {
639       // determine what our status is
640 
641       // for the global usage map, we can ignore out-of-range page addition
642       // since we assuming out-of-range bits are "on".  Note, we are leaving
643       // small holes in the database here (leaving behind some free pages),
644       // but it's not the end of the world.
645 
646       if(!add) {
647 
648         int firstPage = getFirstPageNumber();
649         int lastPage = getLastPageNumber();
650 
651         // we are using an inline map and assuming that anything not
652         // within the current range is "on".  so, if we attempt to set a
653         // bit which is before the current page, ignore it, we are not
654         // going back for it.
655         if((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ||
656            (pageNumber > lastPage)) {
657 
658           // move to new start page, filling in as we move
659           moveToNewStartPageForRemove(firstPage, pageNumber);
660         }
661       }
662     }
663 
664     /**
665      * Shifts the inline usage map so that it now starts with the given
666      * firstPage (if valid), otherwise the newPageNumber.  Any page numbers
667      * added to the end of the usage map are set to "on".
668      * @param firstPage current first used page
669      * @param newPageNumber page number to remove once the map has been
670      *                      shifted to the new start page
671      */
672     private void moveToNewStartPageForRemove(int firstPage, int newPageNumber)
673       throws IOException
674     {
675       int oldEndPage = getEndPage();
676       int newStartPage =
677         toValidStartPage(
678             ((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber :
679              // just shift a little and discard any initial unused pages.
680              (newPageNumber - (getMaxInlinePages() / 2))));
681 
682       // move the current data
683       moveToNewStartPage(newStartPage, PageChannel.INVALID_PAGE_NUMBER);
684 
685       if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
686 
687         // this is the common case where we left everything behind
688         ByteUtil.fillRange(_tableBuffer, getInlineDataStart(),
689                            getInlineDataEnd());
690 
691         // write out the updated table
692         writeTable();
693 
694         // "add" all the page numbers
695         getPageNumbers().set(0, getMaxInlinePages());
696 
697       } else {
698 
699         // add every new page manually
700         for(int i = oldEndPage; i < getEndPage(); ++i) {
701           addPageNumber(i);
702         }
703       }
704 
705       // lastly, remove the new page
706       removePageNumber(newPageNumber, false);
707     }
708   }
709 
710   /**
711    * Usage map whose map is written across one or more entire separate pages
712    * of page type USAGE_MAP.  For Jet4, this type of map can contain 32736
713    * pages per reference page, and a maximum of 17 reference map pages for a
714    * total maximum of 556512 pages (2 GB).
715    * @author Tim McCune
716    */
717   private class ReferenceHandler extends Handler
718   {
719     /** Buffer that contains the current reference map page */
720     private final TempPageHolder _mapPageHolder =
721       TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
722     private final int _maxPagesPerUsageMapPage;
723 
724     private ReferenceHandler() throws IOException
725     {
726       _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE -
727                                    getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8);
728       int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4;
729       setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
730       setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage));
731 
732       // there is no "start page" for a reference usage map, so we get an
733       // extra page reference on top of the number of page references that fit
734       // in the table
735       for (int i = 0; i < numUsagePages; i++) {
736         int mapPageNum = getTableBuffer().getInt(
737             calculateMapPagePointerOffset(i));
738         if (mapPageNum > 0) {
739           ByteBuffer mapPageBuffer =
740             _mapPageHolder.setPage(getPageChannel(), mapPageNum);
741           byte pageType = mapPageBuffer.get();
742           if (pageType != PageTypes.USAGE_MAP) {
743             throw new IOException("Looking for usage map at page " +
744                                   mapPageNum + ", but page type is " +
745                                   pageType);
746           }
747           mapPageBuffer.position(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
748           processMap(mapPageBuffer, (_maxPagesPerUsageMapPage * i));
749         }
750       }
751     }
752 
753     protected final int getMaxPagesPerUsagePage() {
754       return _maxPagesPerUsageMapPage;
755     }
756 
757     @Override
758     public void addOrRemovePageNumber(int pageNumber, boolean add,
759                                       boolean force)
760       throws IOException
761     {
762       if(!isPageWithinRange(pageNumber)) {
763         if(force) {
764           return;
765         }
766         throw new IOException("Page number " + pageNumber +
767                               " is out of supported range");
768       }
769       int pageIndex = (pageNumber / getMaxPagesPerUsagePage());
770       int mapPageNum = getTableBuffer().getInt(
771           calculateMapPagePointerOffset(pageIndex));
772       ByteBuffer mapPageBuffer = null;
773       if(mapPageNum > 0) {
774         mapPageBuffer = _mapPageHolder.setPage(getPageChannel(), mapPageNum);
775       } else {
776         // Need to create a new usage map page
777         mapPageBuffer = createNewUsageMapPage(pageIndex);
778         mapPageNum = _mapPageHolder.getPageNumber();
779       }
780       updateMap(pageNumber,
781                 (pageNumber - (getMaxPagesPerUsagePage() * pageIndex)),
782                 mapPageBuffer, add, force);
783       getPageChannel().writePage(mapPageBuffer, mapPageNum);
784     }
785 
786     /**
787      * Create a new usage map page and update the map declaration with a
788      * pointer to it.
789      * @param pageIndex Index of the page reference within the map declaration
790      */
791     private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException
792     {
793       ByteBuffer mapPageBuffer = allocateNewUsageMapPage(pageIndex);
794       int mapPageNum = _mapPageHolder.getPageNumber();
795       getTableBuffer().putInt(calculateMapPagePointerOffset(pageIndex),
796                               mapPageNum);
797       writeTable();
798       return mapPageBuffer;
799     }
800 
801     private int calculateMapPagePointerOffset(int pageIndex) {
802       return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS +
803         (pageIndex * 4);
804     }
805 
806     protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
807       throws IOException
808     {
809       ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel());
810       mapPageBuffer.put(PageTypes.USAGE_MAP);
811       mapPageBuffer.put((byte) 0x01);  //Unknown
812       mapPageBuffer.putShort((short) 0); //Unknown
813       return mapPageBuffer;
814     }
815   }
816 
817   /**
818    * Modified version of a "reference" usage map used for the global usage
819    * map.  Since reference usage maps require allocating pages for their own
820    * use, we need to handle potential cycles where the PageChannel is
821    * attempting to allocate a new page (and remove it from the global usage
822    * map) and this usage map also needs to allocate a new page.  When that
823    * happens, we stash the pending information from the PageChannel and handle
824    * it after we have retrieved the new page.
825    *
826    * Note, this UsageMap does not implement all the methods "correctly".  Only
827    * addPageNumber and removePageNumber should be called by PageChannel.
828    */
829   private class GlobalReferenceHandler extends ReferenceHandler
830   {
831     private boolean _allocatingPage;
832     private Integer _pendingPage;
833 
834     private GlobalReferenceHandler() throws IOException {
835     }
836 
837     @Override
838     public boolean containsPageNumber(int pageNumber) {
839       // should never be called on global map
840       throw new UnsupportedOperationException();
841     }
842 
843     @Override
844     public void addOrRemovePageNumber(int pageNumber, boolean add,
845                                       boolean force)
846       throws IOException
847     {
848       if(_allocatingPage && !add) {
849         // we are in the midst of allocating a page for ourself, keep track of
850         // this new page so we can mark it later...
851         if(_pendingPage != null) {
852           throw new IllegalStateException("should only have single pending page");
853         }
854         _pendingPage = pageNumber;
855         return;
856       }
857 
858       super.addOrRemovePageNumber(pageNumber, add, force);
859 
860       while(_pendingPage != null) {
861 
862         // while updating our usage map, we needed to allocate a new page (and
863         // thus mark a new page as used).  we delayed that marking so that we
864         // didn't get into an infinite loop.  now that we completed the
865         // original updated, handle the new page.  (we use a loop under the
866         // off the wall chance that adding this page requires allocating a new
867         // page.  in theory, we could do this more than once, but not
868         // forever).
869         int removedPageNumber = _pendingPage;
870         _pendingPage = null;
871 
872         super.addOrRemovePageNumber(removedPageNumber, false, true);
873       }
874     }
875 
876     @Override
877     protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
878       throws IOException
879     {
880       try {
881         // keep track of the fact that we are actively allocating a page for our
882         // own use so that we can break the potential cycle.
883         _allocatingPage = true;
884 
885         ByteBuffer mapPageBuffer = super.allocateNewUsageMapPage(pageIndex);
886 
887         // for the global usage map, all pages are "on" by default.  so
888         // whenever we add a new backing page to the usage map, we need to
889         // turn all the pages that it represents to "on" (we essentially lazy
890         // load this map, which is fine because we should only add pages which
891         // represent the size of the database currently in use).
892         int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA;
893         ByteUtil.fillRange(mapPageBuffer, dataStart,
894                            getFormat().PAGE_SIZE - dataStart);
895 
896         int maxPagesPerUmapPage = getMaxPagesPerUsagePage();
897         int firstNewPage = (pageIndex * maxPagesPerUmapPage);
898         int lastNewPage = firstNewPage + maxPagesPerUmapPage;
899         _pageNumbers.set(firstNewPage, lastNewPage);
900 
901         return mapPageBuffer;
902 
903       } finally {
904         _allocatingPage = false;
905       }
906     }
907   }
908 
909   /**
910    * Utility class to traverse over the pages in the UsageMap.  Remains valid
911    * in the face of usage map modifications.
912    */
913   public final class PageCursor
914   {
915     /** handler for moving the page cursor forward */
916     private final DirHandler _forwardDirHandler = new ForwardDirHandler();
917     /** handler for moving the page cursor backward */
918     private final DirHandler _reverseDirHandler = new ReverseDirHandler();
919     /** the current used page number */
920     private int _curPageNumber;
921     /** the previous used page number */
922     private int _prevPageNumber;
923     /** the last read modification count on the UsageMap.  we track this so
924         that the cursor can detect updates to the usage map while traversing
925         and act accordingly */
926     private int _lastModCount;
927 
928     private PageCursor() {
929       reset();
930     }
931 
932     public UsageMap getUsageMap() {
933       return UsageMap.this;
934     }
935 
936     /**
937      * Returns the DirHandler for the given direction
938      */
939     private DirHandler getDirHandler(boolean moveForward) {
940       return (moveForward ? _forwardDirHandler : _reverseDirHandler);
941     }
942 
943     /**
944      * Returns {@code true} if this cursor is up-to-date with respect to its
945      * usage map.
946      */
947     public boolean isUpToDate() {
948       return(UsageMap.this._modCount == _lastModCount);
949     }
950 
951     /**
952      * @return valid page number if there was another page to read,
953      *         {@link RowIdImpl#LAST_PAGE_NUMBER} otherwise
954      */
955     public int getNextPage() {
956       return getAnotherPage(CursorImpl.MOVE_FORWARD);
957     }
958 
959     /**
960      * @return valid page number if there was another page to read,
961      *         {@link RowIdImpl#FIRST_PAGE_NUMBER} otherwise
962      */
963     public int getPreviousPage() {
964       return getAnotherPage(CursorImpl.MOVE_REVERSE);
965     }
966 
967     /**
968      * Gets another page in the given direction, returning the new page.
969      */
970     private int getAnotherPage(boolean moveForward) {
971       DirHandler handler = getDirHandler(moveForward);
972       if(_curPageNumber == handler.getEndPageNumber()) {
973         if(!isUpToDate()) {
974           restorePosition(_prevPageNumber);
975           // drop through and retry moving to another page
976         } else {
977           // at end, no more
978           return _curPageNumber;
979         }
980       }
981 
982       checkForModification();
983 
984       _prevPageNumber = _curPageNumber;
985       _curPageNumber = handler.getAnotherPageNumber(_curPageNumber);
986       return _curPageNumber;
987     }
988 
989     /**
990      * After calling this method, getNextPage will return the first page in
991      * the map
992      */
993     public void reset() {
994       beforeFirst();
995     }
996 
997     /**
998      * After calling this method, {@link #getNextPage} will return the first
999      * page in the map
1000      */
1001     public void beforeFirst() {
1002       reset(CursorImpl.MOVE_FORWARD);
1003     }
1004 
1005     /**
1006      * After calling this method, {@link #getPreviousPage} will return the
1007      * last page in the map
1008      */
1009     public void afterLast() {
1010       reset(CursorImpl.MOVE_REVERSE);
1011     }
1012 
1013     /**
1014      * Resets this page cursor for traversing the given direction.
1015      */
1016     protected void reset(boolean moveForward) {
1017       _curPageNumber = getDirHandler(moveForward).getBeginningPageNumber();
1018       _prevPageNumber = _curPageNumber;
1019       _lastModCount = UsageMap.this._modCount;
1020     }
1021 
1022     /**
1023      * Restores a current position for the cursor (current position becomes
1024      * previous position).
1025      */
1026     private void restorePosition(int curPageNumber)
1027     {
1028       restorePosition(curPageNumber, _curPageNumber);
1029     }
1030 
1031     /**
1032      * Restores a current and previous position for the cursor.
1033      */
1034     protected void restorePosition(int curPageNumber, int prevPageNumber)
1035     {
1036       if((curPageNumber != _curPageNumber) ||
1037          (prevPageNumber != _prevPageNumber))
1038       {
1039         _prevPageNumber = updatePosition(prevPageNumber);
1040         _curPageNumber = updatePosition(curPageNumber);
1041         _lastModCount = UsageMap.this._modCount;
1042       } else {
1043         checkForModification();
1044       }
1045     }
1046 
1047     /**
1048      * Checks the usage map for modifications an updates state accordingly.
1049      */
1050     private void checkForModification() {
1051       if(!isUpToDate()) {
1052         _prevPageNumber = updatePosition(_prevPageNumber);
1053         _curPageNumber = updatePosition(_curPageNumber);
1054         _lastModCount = UsageMap.this._modCount;
1055       }
1056     }
1057 
1058     private int updatePosition(int pageNumber) {
1059       if(pageNumber < UsageMap.this.getFirstPageNumber()) {
1060         pageNumber = RowIdImpl.FIRST_PAGE_NUMBER;
1061       } else if(pageNumber > UsageMap.this.getLastPageNumber()) {
1062         pageNumber = RowIdImpl.LAST_PAGE_NUMBER;
1063       }
1064       return pageNumber;
1065     }
1066 
1067     @Override
1068     public String toString() {
1069       return getClass().getSimpleName() + " CurPosition " + _curPageNumber +
1070         ", PrevPosition " + _prevPageNumber;
1071     }
1072 
1073 
1074     /**
1075      * Handles moving the cursor in a given direction.  Separates cursor
1076      * logic from value storage.
1077      */
1078     private abstract class DirHandler {
1079       public abstract int getAnotherPageNumber(int curPageNumber);
1080       public abstract int getBeginningPageNumber();
1081       public abstract int getEndPageNumber();
1082     }
1083 
1084     /**
1085      * Handles moving the cursor forward.
1086      */
1087     private final class ForwardDirHandler extends DirHandler {
1088       @Override
1089       public int getAnotherPageNumber(int curPageNumber) {
1090         if(curPageNumber == getBeginningPageNumber()) {
1091           return UsageMap.this.getFirstPageNumber();
1092         }
1093         return UsageMap.this.getNextPageNumber(curPageNumber);
1094       }
1095       @Override
1096       public int getBeginningPageNumber() {
1097         return RowIdImpl.FIRST_PAGE_NUMBER;
1098       }
1099       @Override
1100       public int getEndPageNumber() {
1101         return RowIdImpl.LAST_PAGE_NUMBER;
1102       }
1103     }
1104 
1105     /**
1106      * Handles moving the cursor backward.
1107      */
1108     private final class ReverseDirHandler extends DirHandler {
1109       @Override
1110       public int getAnotherPageNumber(int curPageNumber) {
1111         if(curPageNumber == getBeginningPageNumber()) {
1112           return UsageMap.this.getLastPageNumber();
1113         }
1114         return UsageMap.this.getPrevPageNumber(curPageNumber);
1115       }
1116       @Override
1117       public int getBeginningPageNumber() {
1118         return RowIdImpl.LAST_PAGE_NUMBER;
1119       }
1120       @Override
1121       public int getEndPageNumber() {
1122         return RowIdImpl.FIRST_PAGE_NUMBER;
1123       }
1124     }
1125 
1126   }
1127 
1128 }