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   /**
390    * Promotes the global usage map from an inline map to a reference map.  This
391    * is done once the database grows beyond what an inline global usage map can
392    * represent (i.e. a page is allocated outside the inline map's range).  The
393    * new reference map is seeded so that every page up to the current
394    * allocation frontier is marked "used" and all higher pages remain "free"
395    * (the append-only global inline map only ever tracked free pages ahead of
396    * the allocation frontier).
397    *
398    * @param frontierPageNumber the page currently being allocated, which is the
399    *                           highest page in the database
400    */
401   private void promoteGlobalInlineHandlerToReferenceHandler(
402       int frontierPageNumber)
403     throws IOException
404   {
405     // clear out the main table (inline usage map data and start page) and
406     // switch the map type to reference.  note, the existing usage map row is
407     // large enough to hold the reference page pointers, so it does not need to
408     // be resized.
409     clearTableAndPages();
410     _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE);
411     writeTable();
412 
413     // install the global reference handler (which starts with no backing
414     // pages, so all pages are initially "free")
415     _handler = new GlobalReferenceHandler();
416 
417     // seed the new map: mark every page from 0 up to (and including) the
418     // current frontier as "used".  all higher pages remain "free".  note, this
419     // may re-mark a few previously freed pages as used, but (as with the prior
420     // inline behavior) leaving small holes behind is acceptable.
421     for(int pageNumber = 0; pageNumber <= frontierPageNumber; ++pageNumber) {
422       _handler.addOrRemovePageNumber(pageNumber, false, true);
423     }
424   }
425 
426   private void reAddPages(int oldStartPage, BitSet oldPageNumbers,
427                           int newPageNumber)
428     throws IOException
429   {
430     // add all the old pages back in
431     for(int i = oldPageNumbers.nextSetBit(0); i >= 0;
432         i = oldPageNumbers.nextSetBit(i + 1)) {
433       addPageNumber(oldStartPage + i);
434     }
435 
436     if(newPageNumber > PageChannel.INVALID_PAGE_NUMBER) {
437       // and then add the new page
438       addPageNumber(newPageNumber);
439     }
440   }
441 
442   @Override
443   public String toString() {
444 
445     List<String> ranges = new ArrayList<String>();
446     PageCursor pCursor = cursor();
447     int curRangeStart = Integer.MIN_VALUE;
448     int prevPage = Integer.MIN_VALUE;
449     while(true) {
450       int nextPage = pCursor.getNextPage();
451       if(nextPage < 0) {
452         break;
453       }
454 
455       if(nextPage != (prevPage + 1)) {
456         if(prevPage >= 0) {
457           rangeToString(ranges, curRangeStart, prevPage);
458         }
459         curRangeStart = nextPage;
460       }
461       prevPage = nextPage;
462     }
463     if(prevPage >= 0) {
464       rangeToString(ranges, curRangeStart, prevPage);
465     }
466 
467     return CustomToStringStyle.valueBuilder(
468         _handler.getClass().getSimpleName())
469       .append("range", "(" + _startPage + "-" + _endPage + ")")
470       .append("pageNumbers", ranges)
471       .toString();
472   }
473 
474   private static void rangeToString(List<String> ranges, int rangeStart,
475                                     int rangeEnd)
476   {
477     if(rangeEnd > rangeStart) {
478       ranges.add(rangeStart + "-" + rangeEnd);
479     } else {
480       ranges.add(String.valueOf(rangeStart));
481     }
482   }
483 
484   private static int toValidStartPage(int startPage) {
485     // start page must be a multiple of 8
486     return ((startPage / 8) * 8);
487   }
488 
489   private abstract class Handler
490   {
491     protected Handler() {
492     }
493 
494     public boolean containsPageNumber(int pageNumber) {
495       return(isPageWithinRange(pageNumber) &&
496              getPageNumbers().get(pageNumberToBitIndex(pageNumber)));
497     }
498 
499     /**
500      * @param pageNumber Page number to add or remove from this map
501      * @param add True to add it, false to remove it
502      * @param force true to force add/remove and ignore certain inconsistencies
503      */
504     public abstract void addOrRemovePageNumber(int pageNumber, boolean add,
505                                                boolean force)
506       throws IOException;
507   }
508 
509   /**
510    * Usage map whose map is written inline in the same page.  For Jet4, this
511    * type of map can usually contains a maximum of 512 pages.  Free space maps
512    * are always inline, used space maps may be inline or reference.  It has a
513    * start page, which all page numbers in its map are calculated as starting
514    * from.
515    * @author Tim McCune
516    */
517   private class InlineHandler extends Handler
518   {
519     private final int _maxInlinePages;
520 
521     protected InlineHandler()
522     {
523       _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8;
524       int startPage = getTableBuffer().getInt(getRowStart() + 1);
525       setInlinePageRange(startPage);
526       processMap(getTableBuffer(), 0);
527     }
528 
529     protected final int getMaxInlinePages() {
530       return _maxInlinePages;
531     }
532 
533     protected final int getInlineDataStart() {
534       return getRowStart() + getFormat().OFFSET_USAGE_MAP_START;
535     }
536 
537     protected final int getInlineDataEnd() {
538       return getRowEnd();
539     }
540 
541     /**
542      * Sets the page range for an inline usage map starting from the given
543      * page.
544      */
545     private void setInlinePageRange(int startPage) {
546       setPageRange(startPage, startPage + getMaxInlinePages());
547     }
548 
549     @Override
550     public void addOrRemovePageNumber(int pageNumber, boolean add,
551                                       boolean force)
552       throws IOException
553     {
554       if(isPageWithinRange(pageNumber)) {
555 
556         // easy enough, just update the inline data
557         int bufferRelativePageNumber = pageNumberToBitIndex(pageNumber);
558         updateMap(pageNumber, bufferRelativePageNumber, getTableBuffer(), add,
559                   force);
560         // Write the updated map back to disk
561         writeTable();
562 
563       } else {
564 
565         // uh-oh, we've split our britches.  what now?
566         addOrRemovePageNumberOutsideRange(pageNumber, add, force);
567       }
568     }
569 
570     protected void addOrRemovePageNumberOutsideRange(
571         int pageNumber, boolean add, boolean force)
572       throws IOException
573     {
574       // determine what our status is before taking action
575 
576       if(add) {
577 
578         int firstPage = getFirstPageNumber();
579         int lastPage = getLastPageNumber();
580 
581         // we are adding, can we shift the bits and stay inline?
582         if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
583           // no pages currently
584           firstPage = pageNumber;
585           lastPage = pageNumber;
586         } else if(pageNumber > lastPage) {
587           lastPage = pageNumber;
588         } else {
589           firstPage = pageNumber;
590         }
591 
592         firstPage = toValidStartPage(firstPage);
593 
594         if((lastPage - firstPage + 1) < getMaxInlinePages()) {
595 
596           // we can still fit within an inline map
597           moveToNewStartPage(firstPage, pageNumber);
598 
599         } else {
600           // not going to happen, need to promote the usage map to a
601           // reference map
602           promoteInlineHandlerToReferenceHandler(pageNumber);
603         }
604 
605       } else {
606 
607         // we are removing, what does that mean?
608         if(!force) {
609 
610           // this should not happen, we are removing a page which is not in
611           // the map
612           throw new IOException("Page number " + pageNumber +
613                                 " already removed from usage map" +
614                                 ", expected range " +
615                                 _startPage + " to " + _endPage);
616         }
617       }
618     }
619 
620     /**
621      * Shifts the inline usage map so that it now starts with the given page.
622      * @param newStartPage new page at which to start
623      * @param newPageNumber optional page number to add once the map has been
624      *                      shifted to the new start page
625      */
626     protected final void moveToNewStartPage(int newStartPage, int newPageNumber)
627       throws IOException
628     {
629       int oldStartPage = getStartPage();
630       BitSet oldPageNumbers = (BitSet)getPageNumbers().clone();
631 
632       // clear out the main table (inline usage map data and start page)
633       clearTableAndPages();
634 
635       // write new start page
636       ByteBuffer tableBuffer = getTableBuffer();
637       tableBuffer.position(getRowStart() + 1);
638       tableBuffer.putInt(newStartPage);
639 
640       // write the new table data
641       writeTable();
642 
643       // set new page range
644       setInlinePageRange(newStartPage);
645 
646       // put the pages back in
647       reAddPages(oldStartPage, oldPageNumbers, newPageNumber);
648     }
649   }
650 
651   /**
652    * Modified version of an "inline" usage map used for the global usage map.
653    * When an inline usage map is used for the global usage map, we assume
654    * out-of-range bits are on.  Once the database outgrows what an inline map
655    * can represent (i.e. a page is allocated outside the inline range), we
656    * promote the global usage map to a reference usage map, as ms access does
657    * for larger databases.  (An inline map with a shifted start page is not a
658    * valid global usage map for a large database.)
659    *
660    * Note, this UsageMap does not implement all the methods "correctly".  Only
661    * addPageNumber and removePageNumber should be called by PageChannel.
662    */
663   private class GlobalInlineHandler extends InlineHandler
664   {
665     private GlobalInlineHandler() {
666     }
667 
668     @Override
669     public boolean containsPageNumber(int pageNumber) {
670       // should never be called on global map
671       throw new UnsupportedOperationException();
672     }
673 
674     @Override
675     protected void addOrRemovePageNumberOutsideRange(
676         int pageNumber, boolean add, boolean force)
677       throws IOException
678     {
679       // for the global usage map, we can ignore out-of-range page addition
680       // since we are assuming out-of-range bits are "on".  Note, we are
681       // leaving small holes in the database here (leaving behind some free
682       // pages), but it's not the end of the world.
683 
684       if(!add) {
685 
686         // a page is being allocated outside the inline range.  the inline
687         // global map (anchored at page 0) can no longer describe the extent
688         // of the database, so promote it to a reference usage map.
689         promoteGlobalInlineHandlerToReferenceHandler(pageNumber);
690       }
691     }
692   }
693 
694   /**
695    * Usage map whose map is written across one or more entire separate pages
696    * of page type USAGE_MAP.  For Jet4, this type of map can contain 32736
697    * pages per reference page, and a maximum of 17 reference map pages for a
698    * total maximum of 556512 pages (2 GB).
699    * @author Tim McCune
700    */
701   private class ReferenceHandler extends Handler
702   {
703     /** Buffer that contains the current reference map page */
704     private final TempPageHolder _mapPageHolder =
705       TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
706     private final int _maxPagesPerUsageMapPage;
707 
708     private ReferenceHandler() throws IOException
709     {
710       _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE -
711                                    getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8);
712       int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4;
713       setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
714       setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage));
715 
716       // there is no "start page" for a reference usage map, so we get an
717       // extra page reference on top of the number of page references that fit
718       // in the table
719       for (int i = 0; i < numUsagePages; i++) {
720         int mapPageNum = getTableBuffer().getInt(
721             calculateMapPagePointerOffset(i));
722         if (mapPageNum > 0) {
723           ByteBuffer mapPageBuffer =
724             _mapPageHolder.setPage(getPageChannel(), mapPageNum);
725           byte pageType = mapPageBuffer.get();
726           if (pageType != PageTypes.USAGE_MAP) {
727             throw new IOException("Looking for usage map at page " +
728                                   mapPageNum + ", but page type is " +
729                                   pageType);
730           }
731           mapPageBuffer.position(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
732           processMap(mapPageBuffer, (_maxPagesPerUsageMapPage * i));
733         }
734       }
735     }
736 
737     protected final int getMaxPagesPerUsagePage() {
738       return _maxPagesPerUsageMapPage;
739     }
740 
741     @Override
742     public void addOrRemovePageNumber(int pageNumber, boolean add,
743                                       boolean force)
744       throws IOException
745     {
746       if(!isPageWithinRange(pageNumber)) {
747         if(force) {
748           return;
749         }
750         throw new IOException("Page number " + pageNumber +
751                               " is out of supported range");
752       }
753       int pageIndex = (pageNumber / getMaxPagesPerUsagePage());
754       int mapPageNum = getTableBuffer().getInt(
755           calculateMapPagePointerOffset(pageIndex));
756       ByteBuffer mapPageBuffer = null;
757       if(mapPageNum > 0) {
758         mapPageBuffer = _mapPageHolder.setPage(getPageChannel(), mapPageNum);
759       } else {
760         // Need to create a new usage map page
761         mapPageBuffer = createNewUsageMapPage(pageIndex);
762         mapPageNum = _mapPageHolder.getPageNumber();
763       }
764       updateMap(pageNumber,
765                 (pageNumber - (getMaxPagesPerUsagePage() * pageIndex)),
766                 mapPageBuffer, add, force);
767       getPageChannel().writePage(mapPageBuffer, mapPageNum);
768     }
769 
770     /**
771      * Create a new usage map page and update the map declaration with a
772      * pointer to it.
773      * @param pageIndex Index of the page reference within the map declaration
774      */
775     private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException
776     {
777       ByteBuffer mapPageBuffer = allocateNewUsageMapPage(pageIndex);
778       int mapPageNum = _mapPageHolder.getPageNumber();
779       getTableBuffer().putInt(calculateMapPagePointerOffset(pageIndex),
780                               mapPageNum);
781       writeTable();
782       return mapPageBuffer;
783     }
784 
785     private int calculateMapPagePointerOffset(int pageIndex) {
786       return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS +
787         (pageIndex * 4);
788     }
789 
790     protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
791       throws IOException
792     {
793       ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel());
794       mapPageBuffer.put(PageTypes.USAGE_MAP);
795       mapPageBuffer.put((byte) 0x01);  //Unknown
796       mapPageBuffer.putShort((short) 0); //Unknown
797       return mapPageBuffer;
798     }
799   }
800 
801   /**
802    * Modified version of a "reference" usage map used for the global usage
803    * map.  Since reference usage maps require allocating pages for their own
804    * use, we need to handle potential cycles where the PageChannel is
805    * attempting to allocate a new page (and remove it from the global usage
806    * map) and this usage map also needs to allocate a new page.  When that
807    * happens, we stash the pending information from the PageChannel and handle
808    * it after we have retrieved the new page.
809    *
810    * Note, this UsageMap does not implement all the methods "correctly".  Only
811    * addPageNumber and removePageNumber should be called by PageChannel.
812    */
813   private class GlobalReferenceHandler extends ReferenceHandler
814   {
815     private boolean _allocatingPage;
816     private Integer _pendingPage;
817 
818     private GlobalReferenceHandler() throws IOException {
819     }
820 
821     @Override
822     public boolean containsPageNumber(int pageNumber) {
823       // should never be called on global map
824       throw new UnsupportedOperationException();
825     }
826 
827     @Override
828     public void addOrRemovePageNumber(int pageNumber, boolean add,
829                                       boolean force)
830       throws IOException
831     {
832       if(_allocatingPage && !add) {
833         // we are in the midst of allocating a page for ourself, keep track of
834         // this new page so we can mark it later...
835         if(_pendingPage != null) {
836           throw new IllegalStateException("should only have single pending page");
837         }
838         _pendingPage = pageNumber;
839         return;
840       }
841 
842       super.addOrRemovePageNumber(pageNumber, add, force);
843 
844       while(_pendingPage != null) {
845 
846         // while updating our usage map, we needed to allocate a new page (and
847         // thus mark a new page as used).  we delayed that marking so that we
848         // didn't get into an infinite loop.  now that we completed the
849         // original updated, handle the new page.  (we use a loop under the
850         // off the wall chance that adding this page requires allocating a new
851         // page.  in theory, we could do this more than once, but not
852         // forever).
853         int removedPageNumber = _pendingPage;
854         _pendingPage = null;
855 
856         super.addOrRemovePageNumber(removedPageNumber, false, true);
857       }
858     }
859 
860     @Override
861     protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
862       throws IOException
863     {
864       try {
865         // keep track of the fact that we are actively allocating a page for our
866         // own use so that we can break the potential cycle.
867         _allocatingPage = true;
868 
869         ByteBuffer mapPageBuffer = super.allocateNewUsageMapPage(pageIndex);
870 
871         // for the global usage map, all pages are "on" by default.  so
872         // whenever we add a new backing page to the usage map, we need to
873         // turn all the pages that it represents to "on" (we essentially lazy
874         // load this map, which is fine because we should only add pages which
875         // represent the size of the database currently in use).
876         int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA;
877         ByteUtil.fillRange(mapPageBuffer, dataStart,
878                            getFormat().PAGE_SIZE - dataStart);
879 
880         int maxPagesPerUmapPage = getMaxPagesPerUsagePage();
881         int firstNewPage = (pageIndex * maxPagesPerUmapPage);
882         int lastNewPage = firstNewPage + maxPagesPerUmapPage;
883         _pageNumbers.set(firstNewPage, lastNewPage);
884 
885         return mapPageBuffer;
886 
887       } finally {
888         _allocatingPage = false;
889       }
890     }
891   }
892 
893   /**
894    * Utility class to traverse over the pages in the UsageMap.  Remains valid
895    * in the face of usage map modifications.
896    */
897   public final class PageCursor
898   {
899     /** handler for moving the page cursor forward */
900     private final DirHandler _forwardDirHandler = new ForwardDirHandler();
901     /** handler for moving the page cursor backward */
902     private final DirHandler _reverseDirHandler = new ReverseDirHandler();
903     /** the current used page number */
904     private int _curPageNumber;
905     /** the previous used page number */
906     private int _prevPageNumber;
907     /** the last read modification count on the UsageMap.  we track this so
908         that the cursor can detect updates to the usage map while traversing
909         and act accordingly */
910     private int _lastModCount;
911 
912     private PageCursor() {
913       reset();
914     }
915 
916     public UsageMap getUsageMap() {
917       return UsageMap.this;
918     }
919 
920     /**
921      * Returns the DirHandler for the given direction
922      */
923     private DirHandler getDirHandler(boolean moveForward) {
924       return (moveForward ? _forwardDirHandler : _reverseDirHandler);
925     }
926 
927     /**
928      * Returns {@code true} if this cursor is up-to-date with respect to its
929      * usage map.
930      */
931     public boolean isUpToDate() {
932       return(UsageMap.this._modCount == _lastModCount);
933     }
934 
935     /**
936      * @return valid page number if there was another page to read,
937      *         {@link RowIdImpl#LAST_PAGE_NUMBER} otherwise
938      */
939     public int getNextPage() {
940       return getAnotherPage(CursorImpl.MOVE_FORWARD);
941     }
942 
943     /**
944      * @return valid page number if there was another page to read,
945      *         {@link RowIdImpl#FIRST_PAGE_NUMBER} otherwise
946      */
947     public int getPreviousPage() {
948       return getAnotherPage(CursorImpl.MOVE_REVERSE);
949     }
950 
951     /**
952      * Gets another page in the given direction, returning the new page.
953      */
954     private int getAnotherPage(boolean moveForward) {
955       DirHandler handler = getDirHandler(moveForward);
956       if(_curPageNumber == handler.getEndPageNumber()) {
957         if(!isUpToDate()) {
958           restorePosition(_prevPageNumber);
959           // drop through and retry moving to another page
960         } else {
961           // at end, no more
962           return _curPageNumber;
963         }
964       }
965 
966       checkForModification();
967 
968       _prevPageNumber = _curPageNumber;
969       _curPageNumber = handler.getAnotherPageNumber(_curPageNumber);
970       return _curPageNumber;
971     }
972 
973     /**
974      * After calling this method, getNextPage will return the first page in
975      * the map
976      */
977     public void reset() {
978       beforeFirst();
979     }
980 
981     /**
982      * After calling this method, {@link #getNextPage} will return the first
983      * page in the map
984      */
985     public void beforeFirst() {
986       reset(CursorImpl.MOVE_FORWARD);
987     }
988 
989     /**
990      * After calling this method, {@link #getPreviousPage} will return the
991      * last page in the map
992      */
993     public void afterLast() {
994       reset(CursorImpl.MOVE_REVERSE);
995     }
996 
997     /**
998      * Resets this page cursor for traversing the given direction.
999      */
1000     protected void reset(boolean moveForward) {
1001       _curPageNumber = getDirHandler(moveForward).getBeginningPageNumber();
1002       _prevPageNumber = _curPageNumber;
1003       _lastModCount = UsageMap.this._modCount;
1004     }
1005 
1006     /**
1007      * Restores a current position for the cursor (current position becomes
1008      * previous position).
1009      */
1010     private void restorePosition(int curPageNumber)
1011     {
1012       restorePosition(curPageNumber, _curPageNumber);
1013     }
1014 
1015     /**
1016      * Restores a current and previous position for the cursor.
1017      */
1018     protected void restorePosition(int curPageNumber, int prevPageNumber)
1019     {
1020       if((curPageNumber != _curPageNumber) ||
1021          (prevPageNumber != _prevPageNumber))
1022       {
1023         _prevPageNumber = updatePosition(prevPageNumber);
1024         _curPageNumber = updatePosition(curPageNumber);
1025         _lastModCount = UsageMap.this._modCount;
1026       } else {
1027         checkForModification();
1028       }
1029     }
1030 
1031     /**
1032      * Checks the usage map for modifications an updates state accordingly.
1033      */
1034     private void checkForModification() {
1035       if(!isUpToDate()) {
1036         _prevPageNumber = updatePosition(_prevPageNumber);
1037         _curPageNumber = updatePosition(_curPageNumber);
1038         _lastModCount = UsageMap.this._modCount;
1039       }
1040     }
1041 
1042     private int updatePosition(int pageNumber) {
1043       if(pageNumber < UsageMap.this.getFirstPageNumber()) {
1044         pageNumber = RowIdImpl.FIRST_PAGE_NUMBER;
1045       } else if(pageNumber > UsageMap.this.getLastPageNumber()) {
1046         pageNumber = RowIdImpl.LAST_PAGE_NUMBER;
1047       }
1048       return pageNumber;
1049     }
1050 
1051     @Override
1052     public String toString() {
1053       return getClass().getSimpleName() + " CurPosition " + _curPageNumber +
1054         ", PrevPosition " + _prevPageNumber;
1055     }
1056 
1057 
1058     /**
1059      * Handles moving the cursor in a given direction.  Separates cursor
1060      * logic from value storage.
1061      */
1062     private abstract class DirHandler {
1063       public abstract int getAnotherPageNumber(int curPageNumber);
1064       public abstract int getBeginningPageNumber();
1065       public abstract int getEndPageNumber();
1066     }
1067 
1068     /**
1069      * Handles moving the cursor forward.
1070      */
1071     private final class ForwardDirHandler extends DirHandler {
1072       @Override
1073       public int getAnotherPageNumber(int curPageNumber) {
1074         if(curPageNumber == getBeginningPageNumber()) {
1075           return UsageMap.this.getFirstPageNumber();
1076         }
1077         return UsageMap.this.getNextPageNumber(curPageNumber);
1078       }
1079       @Override
1080       public int getBeginningPageNumber() {
1081         return RowIdImpl.FIRST_PAGE_NUMBER;
1082       }
1083       @Override
1084       public int getEndPageNumber() {
1085         return RowIdImpl.LAST_PAGE_NUMBER;
1086       }
1087     }
1088 
1089     /**
1090      * Handles moving the cursor backward.
1091      */
1092     private final class ReverseDirHandler extends DirHandler {
1093       @Override
1094       public int getAnotherPageNumber(int curPageNumber) {
1095         if(curPageNumber == getBeginningPageNumber()) {
1096           return UsageMap.this.getLastPageNumber();
1097         }
1098         return UsageMap.this.getPrevPageNumber(curPageNumber);
1099       }
1100       @Override
1101       public int getBeginningPageNumber() {
1102         return RowIdImpl.LAST_PAGE_NUMBER;
1103       }
1104       @Override
1105       public int getEndPageNumber() {
1106         return RowIdImpl.FIRST_PAGE_NUMBER;
1107       }
1108     }
1109 
1110   }
1111 
1112 }