summaryrefslogtreecommitdiffstats
path: root/src/java/com/healthmarketscience/jackcess/UsageMap.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/healthmarketscience/jackcess/UsageMap.java')
-rw-r--r--src/java/com/healthmarketscience/jackcess/UsageMap.java239
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;
+
+}