Coverage Report - com.healthmarketscience.jackcess.PageChannel
 
Classes in this File Line Coverage Branch Coverage Complexity
PageChannel
88%
68/77
55%
11/20
1.8
 
 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.Flushable;
 31  
 import java.io.IOException;
 32  
 import java.nio.ByteBuffer;
 33  
 import java.nio.ByteOrder;
 34  
 import java.nio.channels.Channel;
 35  
 import java.nio.channels.FileChannel;
 36  
 
 37  
 import org.apache.commons.logging.Log;
 38  
 import org.apache.commons.logging.LogFactory;
 39  
 
 40  
 /**
 41  
  * Reads and writes individual pages in a database file
 42  
  * @author Tim McCune
 43  
  */
 44  
 public class PageChannel implements Channel, Flushable {
 45  
   
 46  1
   private static final Log LOG = LogFactory.getLog(PageChannel.class);
 47  
   
 48  
   static final int INVALID_PAGE_NUMBER = -1;
 49  
 
 50  1
   static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
 51  
   
 52  
   /** invalid page header, used when deallocating old pages.  data pages
 53  
       generally have 4 interesting bytes at the beginning which we want to
 54  
       reset. */
 55  1
   private static final byte[] INVALID_PAGE_BYTE_HEADER =
 56  
     new byte[]{PageTypes.INVALID, (byte)0, (byte)0, (byte)0};
 57  
   
 58  
   /** Global usage map always lives on page 1 */
 59  
   private static final int PAGE_GLOBAL_USAGE_MAP = 1;
 60  
   /** Global usage map always lives at row 0 */
 61  
   private static final int ROW_GLOBAL_USAGE_MAP = 0;
 62  
   
 63  
   /** Channel containing the database */
 64  
   private final FileChannel _channel;
 65  
   /** Format of the database in the channel */
 66  
   private final JetFormat _format;
 67  
   /** whether or not to force all writes to disk immediately */
 68  
   private final  boolean _autoSync;
 69  
   /** buffer used when deallocating old pages.  data pages generally have 4
 70  
       interesting bytes at the beginning which we want to reset. */
 71  69
   private final ByteBuffer _invalidPageBytes =
 72  
     ByteBuffer.wrap(INVALID_PAGE_BYTE_HEADER);
 73  
   /** dummy buffer used when allocating new pages */
 74  69
   private final ByteBuffer _forceBytes = ByteBuffer.allocate(1);
 75  
   /** Tracks free pages in the database. */
 76  
   private UsageMap _globalUsageMap;
 77  
   
 78  
   /**
 79  
    * @param channel Channel containing the database
 80  
    * @param format Format of the database in the channel
 81  
    */
 82  
   public PageChannel(FileChannel channel, JetFormat format, boolean autoSync)
 83  
     throws IOException
 84  67
   {
 85  67
     _channel = channel;
 86  67
     _format = format;
 87  67
     _autoSync = autoSync;
 88  67
   }
 89  
 
 90  
   /**
 91  
    * Does second-stage initialization, must be called after construction.
 92  
    */
 93  
   public void initialize(Database database)
 94  
     throws IOException
 95  
   {
 96  
     // note the global usage map is a special map where any page outside of
 97  
     // the current range is assumed to be "on"
 98  67
     _globalUsageMap = UsageMap.read(database, PAGE_GLOBAL_USAGE_MAP,
 99  
                                     ROW_GLOBAL_USAGE_MAP, true);
 100  67
   }
 101  
   
 102  
   /**
 103  
    * Only used by unit tests
 104  
    */
 105  2
   PageChannel(boolean testing) {
 106  2
     if(!testing) {
 107  0
       throw new IllegalArgumentException();
 108  
     }
 109  2
     _channel = null;
 110  2
     _format = JetFormat.VERSION_4;
 111  2
     _autoSync = false;
 112  2
   }
 113  
 
 114  
   public JetFormat getFormat() {
 115  337611
     return _format;
 116  
   }
 117  
 
 118  
   /**
 119  
    * Returns the next page number based on the given file size.
 120  
    */
 121  
   private int getNextPageNumber(long size) {
 122  62019
     return (int)(size / getFormat().PAGE_SIZE);
 123  
   }
 124  
 
 125  
   /**
 126  
    * Returns the offset for a page within the file.
 127  
    */
 128  
   private long getPageOffset(int pageNumber) {
 129  49009
     return((long) pageNumber * (long) getFormat().PAGE_SIZE);
 130  
   }
 131  
   
 132  
   /**
 133  
    * Validates that the given pageNumber is valid for this database.
 134  
    */
 135  
   private void validatePageNumber(int pageNumber)
 136  
     throws IOException
 137  
   {
 138  60455
     int nextPageNumber = getNextPageNumber(_channel.size());
 139  60455
     if((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
 140  0
       throw new IllegalStateException("invalid page number " + pageNumber);
 141  
     }
 142  60455
   }
 143  
   
 144  
   /**
 145  
    * @param buffer Buffer to read the page into
 146  
    * @param pageNumber Number of the page to read in (starting at 0)
 147  
    */
 148  
   public void readPage(ByteBuffer buffer, int pageNumber)
 149  
     throws IOException
 150  
   {
 151  11446
     validatePageNumber(pageNumber);
 152  11446
     if (LOG.isDebugEnabled()) {
 153  0
       LOG.debug("Reading in page " + Integer.toHexString(pageNumber));
 154  
     }
 155  11446
     buffer.clear();
 156  11446
     int bytesRead = _channel.read(
 157  
         buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE);
 158  11446
     buffer.flip();
 159  11446
     if(bytesRead != getFormat().PAGE_SIZE) {
 160  0
       throw new IOException("Failed attempting to read " +
 161  
                             getFormat().PAGE_SIZE + " bytes from page " +
 162  
                             pageNumber + ", only read " + bytesRead);
 163  
     }
 164  11446
   }
 165  
   
 166  
   /**
 167  
    * Write a page to disk
 168  
    * @param page Page to write
 169  
    * @param pageNumber Page number to write the page to
 170  
    */
 171  
   public void writePage(ByteBuffer page, int pageNumber) throws IOException {
 172  43683
     writePage(page, pageNumber, 0);
 173  43683
   }
 174  
   
 175  
   /**
 176  
    * Write a page (or part of a page) to disk
 177  
    * @param page Page to write
 178  
    * @param pageNumber Page number to write the page to
 179  
    * @param pageOffset offset within the page at which to start writing the
 180  
    *                   page data
 181  
    */
 182  
   public void writePage(ByteBuffer page, int pageNumber,
 183  
                         int pageOffset)
 184  
     throws IOException
 185  
   {
 186  48778
     validatePageNumber(pageNumber);
 187  
     
 188  48778
     page.rewind();
 189  
 
 190  48778
     if((page.remaining() - pageOffset) > getFormat().PAGE_SIZE) {
 191  0
       throw new IllegalArgumentException(
 192  
           "Page buffer is too large, size " + (page.remaining() - pageOffset));
 193  
     }
 194  
     
 195  48778
     page.position(pageOffset);
 196  48778
     _channel.write(page, (getPageOffset(pageNumber) + pageOffset));
 197  48778
     if(_autoSync) {
 198  33094
       flush();
 199  
     }
 200  48778
   }
 201  
   
 202  
   /**
 203  
    * Write a page to disk as a new page, appending it to the database
 204  
    * @param page Page to write
 205  
    * @return Page number at which the page was written
 206  
    */
 207  
   public int writeNewPage(ByteBuffer page) throws IOException
 208  
   {
 209  1564
     long size = _channel.size();
 210  1564
     if(size >= getFormat().MAX_DATABASE_SIZE) {
 211  0
       throw new IOException("Database is at maximum size " +
 212  
                             getFormat().MAX_DATABASE_SIZE);
 213  
     }
 214  1564
     if((size % getFormat().PAGE_SIZE) != 0L) {
 215  0
       throw new IOException("Database corrupted, file size " + size +
 216  
                             " is not multiple of page size " +
 217  
                             getFormat().PAGE_SIZE);
 218  
     }
 219  
     
 220  1564
     page.rewind();
 221  
 
 222  1564
     if(page.remaining() > getFormat().PAGE_SIZE) {
 223  0
       throw new IllegalArgumentException("Page buffer is too large, size " +
 224  
                                          page.remaining());
 225  
     }
 226  
     
 227  
     // push the buffer to the end of the page, so that a full page's worth of
 228  
     // data is written regardless of the incoming buffer size (we use a tiny
 229  
     // buffer in allocateNewPage)
 230  1564
     long offset = size + (getFormat().PAGE_SIZE - page.remaining());
 231  1564
     _channel.write(page, offset);
 232  1564
     int pageNumber = getNextPageNumber(size);
 233  1564
     _globalUsageMap.removePageNumber(pageNumber);  //force is done here
 234  1564
     return pageNumber;
 235  
   }
 236  
 
 237  
   /**
 238  
    * Allocates a new page in the database.  Data in the page is undefined
 239  
    * until it is written in a call to {@link #writePage(ByteBuffer,int)}.
 240  
    */
 241  
   public int allocateNewPage() throws IOException {
 242  
     // this will force the file to be extended with mostly undefined bytes
 243  1520
     return writeNewPage(_forceBytes);
 244  
   }
 245  
 
 246  
   /**
 247  
    * Deallocate a previously used page in the database.
 248  
    */
 249  
   public void deallocatePage(int pageNumber) throws IOException {
 250  231
     validatePageNumber(pageNumber);
 251  
     
 252  
     // don't write the whole page, just wipe out the header (which should be
 253  
     // enough to let us know if we accidentally try to use an invalid page)
 254  231
     _invalidPageBytes.rewind();
 255  231
     _channel.write(_invalidPageBytes, getPageOffset(pageNumber));
 256  
     
 257  231
     _globalUsageMap.addPageNumber(pageNumber);  //force is done here
 258  231
   }
 259  
   
 260  
   /**
 261  
    * @return A newly-allocated buffer that can be passed to readPage
 262  
    */
 263  
   public ByteBuffer createPageBuffer() {
 264  969
     return createBuffer(getFormat().PAGE_SIZE);
 265  
   }
 266  
 
 267  
   /**
 268  
    * @return A newly-allocated buffer of the given size and LITTLE_ENDIAN byte
 269  
    *         order
 270  
    */
 271  
   public ByteBuffer createBuffer(int size) {
 272  1761
     return createBuffer(size, ByteOrder.LITTLE_ENDIAN);
 273  
   }
 274  
   
 275  
   /**
 276  
    * @return A newly-allocated buffer of the given size and byte order
 277  
    */
 278  
   public ByteBuffer createBuffer(int size, ByteOrder order) {
 279  34079
     ByteBuffer rtn = ByteBuffer.allocate(size);
 280  34079
     rtn.order(order);
 281  34079
     return rtn;
 282  
   }
 283  
   
 284  
   public void flush() throws IOException {
 285  33135
     _channel.force(true);
 286  33135
   }
 287  
   
 288  
   public void close() throws IOException {
 289  40
     flush();
 290  40
     _channel.close();
 291  40
   }
 292  
   
 293  
   public boolean isOpen() {
 294  0
     return _channel.isOpen();
 295  
   }
 296  
 
 297  
   /**
 298  
    * @return a duplicate of the current buffer narrowed to the given position
 299  
    *         and limit.  mark will be set at the current position.
 300  
    */
 301  
   public static ByteBuffer narrowBuffer(ByteBuffer buffer, int position,
 302  
                                         int limit)
 303  
   {
 304  22653
     return (ByteBuffer)buffer.duplicate()
 305  
       .order(buffer.order())
 306  
       .clear()
 307  
       .limit(limit)
 308  
       .position(position)
 309  
       .mark();
 310  
   }
 311  
   
 312  
 }