diff options
Diffstat (limited to 'src/java/com/healthmarketscience/jackcess/UsageMap.java')
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/UsageMap.java | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java new file mode 100644 index 0000000..5639cb4 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -0,0 +1,239 @@ +/* +Copyright (c) 2005 Health Market Science, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +You can contact Health Market Science at info@healthmarketscience.com +or at the following address: + +Health Market Science +2700 Horizon Drive +Suite 200 +King of Prussia, PA 19406 +*/ + +package com.healthmarketscience.jackcess; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Describes which database pages a particular table uses + * @author Tim McCune + */ +public abstract class UsageMap { + + private static final Log LOG = LogFactory.getLog(UsageMap.class); + + /** Inline map type */ + public static final byte MAP_TYPE_INLINE = 0x0; + /** Reference map type, for maps that are too large to fit inline */ + public static final byte MAP_TYPE_REFERENCE = 0x1; + + /** Index of the current page, incremented after calling getNextPage */ + private int _currentPageIndex = 0; + /** Page number of the map declaration */ + private int _dataPageNum; + /** Offset of the data page at which the usage map data starts */ + private int _startOffset; + /** Offset of the data page at which the usage map declaration starts */ + private short _rowStart; + /** Format of the database that contains this usage map */ + private JetFormat _format; + /** List of page numbers used (Integer) */ + private List _pageNumbers = new ArrayList(); + /** Buffer that contains the usage map declaration page */ + private ByteBuffer _dataBuffer; + /** Used to read in pages */ + private PageChannel _pageChannel; + + /** + * @param pageChannel Used to read in pages + * @param pageNum Page number that this usage map is contained in + * @param rowNum Number of the row on the page that contains this usage map + * @param format Format of the database that contains this usage map + * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on which + * type of map is found + */ + public static UsageMap read(PageChannel pageChannel, int pageNum, byte rowNum, JetFormat format) + throws IOException + { + ByteBuffer dataBuffer = pageChannel.createPageBuffer(); + pageChannel.readPage(dataBuffer, pageNum); + short rowStart = dataBuffer.getShort(format.OFFSET_ROW_START + 2 * rowNum); + int rowEnd; + if (rowNum == 0) { + rowEnd = format.PAGE_SIZE - 1; + } else { + rowEnd = (dataBuffer.getShort(format.OFFSET_ROW_START + (rowNum - 1) * 2) & 0x0FFF) - 1; + } + dataBuffer.limit(rowEnd + 1); + byte mapType = dataBuffer.get(rowStart); + UsageMap rtn; + if (mapType == MAP_TYPE_INLINE) { + rtn = new InlineUsageMap(pageChannel, dataBuffer, pageNum, format, rowStart); + } else if (mapType == MAP_TYPE_REFERENCE) { + rtn = new ReferenceUsageMap(pageChannel, dataBuffer, pageNum, format, rowStart); + } else { + throw new IOException("Unrecognized map type: " + mapType); + } + return rtn; + } + + /** + * @param pageChannel Used to read in pages + * @param dataBuffer Buffer that contains this map's declaration + * @param pageNum Page number that this usage map is contained in + * @param format Format of the database that contains this usage map + * @param rowStart Offset at which the declaration starts in the buffer + */ + public UsageMap(PageChannel pageChannel, ByteBuffer dataBuffer, int pageNum, + JetFormat format, short rowStart) + throws IOException + { + _pageChannel = pageChannel; + _dataBuffer = dataBuffer; + _dataPageNum = pageNum; + _format = format; + _rowStart = rowStart; + _dataBuffer.position((int) _rowStart + format.OFFSET_MAP_START); + _startOffset = _dataBuffer.position(); + if (LOG.isDebugEnabled()) { + LOG.debug("Usage map block:\n" + ByteUtil.toHexString(_dataBuffer, _rowStart, + dataBuffer.limit() - _rowStart)); + } + } + + protected short getRowStart() { + return _rowStart; + } + + public List getPageNumbers() { + return _pageNumbers; + } + + protected void setStartOffset(int startOffset) { + _startOffset = startOffset; + } + + protected int getStartOffset() { + return _startOffset; + } + + protected ByteBuffer getDataBuffer() { + return _dataBuffer; + } + + protected int getDataPageNumber() { + return _dataPageNum; + } + + protected PageChannel getPageChannel() { + return _pageChannel; + } + + protected JetFormat getFormat() { + return _format; + } + + /** + * After calling this method, getNextPage will return the first page in the map + */ + public void reset() { + _currentPageIndex = 0; + } + + /** + * @param buffer Buffer to read the next page into + * @return Whether or not there was another page to read + */ + public boolean getNextPage(ByteBuffer buffer) throws IOException { + if (_pageNumbers.size() > _currentPageIndex) { + Integer pageNumber = (Integer) _pageNumbers.get(_currentPageIndex++); + _pageChannel.readPage(buffer, pageNumber.intValue()); + return true; + } else { + return false; + } + } + + /** + * Read in the page numbers in this inline map + */ + protected void processMap(ByteBuffer buffer, int pageIndex, int startPage) { + int byteCount = 0; + while (buffer.hasRemaining()) { + byte b = buffer.get(); + for (int i = 0; i < 8; i++) { + if ((b & (1 << i)) != 0) { + Integer pageNumber = new Integer((startPage + byteCount * 8 + i) + + (pageIndex * _format.PAGES_PER_USAGE_MAP_PAGE)); + _pageNumbers.add(pageNumber); + } + } + byteCount++; + } + } + + /** + * Add a page number to this usage map + */ + public void addPageNumber(int pageNumber) throws IOException { + //Sanity check, only on in debug mode for performance considerations + if (LOG.isDebugEnabled() && _pageNumbers.contains(new Integer(pageNumber))) { + throw new IOException("Page number " + pageNumber + " already in usage map"); + } + addOrRemovePageNumber(pageNumber, true); + } + + /** + * Remove a page number from this usage map + */ + public void removePageNumber(int pageNumber) throws IOException { + addOrRemovePageNumber(pageNumber, false); + } + + protected void updateMap(int absolutePageNumber, int relativePageNumber, + int bitmask, ByteBuffer buffer, boolean add) + { + //Find the byte to apply the bitmask to + int offset = relativePageNumber / 8; + byte b = buffer.get(_startOffset + offset); + //Apply the bitmask + if (add) { + b |= bitmask; + _pageNumbers.add(new Integer(absolutePageNumber)); + } else { + b &= ~bitmask; + } + buffer.put(_startOffset + offset, b); + } + + public String toString() { + return "page numbers: " + _pageNumbers; + } + + /** + * @param pageNumber Page number to add or remove from this map + * @param add True to add it, false to remove it + */ + protected abstract void addOrRemovePageNumber(int pageNumber, boolean add) throws IOException; + +} |