View Javadoc

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