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