From: James Ahlborn Date: Mon, 19 Nov 2007 03:26:40 +0000 (+0000) Subject: change references between major data types; share common utility classes from common... X-Git-Tag: rel_1_1_10~29 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=588da3ba5eb273dc5920173b4a87049223639eb6;p=jackcess.git change references between major data types; share common utility classes from common database git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@175 f203690c-595d-4dc9-a70b-905162fa7fd2 --- diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index 20a26ae..3cfd4fe 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -96,6 +96,8 @@ public class Column implements Comparable { private static final Pattern GUID_PATTERN = Pattern.compile("\\s*[{]([\\p{XDigit}]{8})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{12})[}]\\s*"); + /** owning table */ + private final Table _table; /** For text columns, whether or not they are compressed */ private boolean _compressedUnicode = false; /** Whether or not the column is of variable length */ @@ -108,10 +110,6 @@ public class Column implements Comparable { private byte _scale; /** Data type */ private DataType _type; - /** Format that the containing database is in */ - private JetFormat _format; - /** Used to read in LVAL pages */ - private PageChannel _pageChannel; /** Maximum column length */ private short _columnLength; /** 0-based column number */ @@ -130,7 +128,7 @@ public class Column implements Comparable { } public Column(JetFormat format) { - _format = format; + _table = null; } /** @@ -140,43 +138,54 @@ public class Column implements Comparable { if(!testing) { throw new IllegalArgumentException(); } - _format = JetFormat.VERSION_4; - _pageChannel = new PageChannel(testing); + _table = null; } /** * Read a column definition in from a buffer + * @param table owning table * @param buffer Buffer containing column definition * @param offset Offset in the buffer at which the column definition starts * @param format Format that the containing database is in */ - public Column(ByteBuffer buffer, int offset, PageChannel pageChannel, JetFormat format) - throws IOException + public Column(Table table, ByteBuffer buffer, int offset) + throws IOException { + _table = table; if (LOG.isDebugEnabled()) { LOG.debug("Column def block:\n" + ByteUtil.toHexString(buffer, offset, 25)); } - _pageChannel = pageChannel; - _format = format; - setType(DataType.fromByte(buffer.get(offset + format.OFFSET_COLUMN_TYPE))); - _columnNumber = buffer.getShort(offset + format.OFFSET_COLUMN_NUMBER); - _columnLength = buffer.getShort(offset + format.OFFSET_COLUMN_LENGTH); + setType(DataType.fromByte(buffer.get(offset + getFormat().OFFSET_COLUMN_TYPE))); + _columnNumber = buffer.getShort(offset + getFormat().OFFSET_COLUMN_NUMBER); + _columnLength = buffer.getShort(offset + getFormat().OFFSET_COLUMN_LENGTH); if (_type.getHasScalePrecision()) { - _precision = buffer.get(offset + format.OFFSET_COLUMN_PRECISION); - _scale = buffer.get(offset + format.OFFSET_COLUMN_SCALE); + _precision = buffer.get(offset + getFormat().OFFSET_COLUMN_PRECISION); + _scale = buffer.get(offset + getFormat().OFFSET_COLUMN_SCALE); } - byte flags = buffer.get(offset + format.OFFSET_COLUMN_FLAGS); + byte flags = buffer.get(offset + getFormat().OFFSET_COLUMN_FLAGS); _variableLength = ((flags & FIXED_LEN_FLAG_MASK) == 0); _autoNumber = ((flags & AUTO_NUMBER_FLAG_MASK) != 0); _compressedUnicode = ((buffer.get(offset + - format.OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1); + getFormat().OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1); if(_variableLength) { - _varLenTableIndex = buffer.getShort(offset + format.OFFSET_COLUMN_VARIABLE_TABLE_INDEX); + _varLenTableIndex = buffer.getShort(offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX); } else { - _fixedDataOffset = buffer.getShort(offset + format.OFFSET_COLUMN_FIXED_DATA_OFFSET); + _fixedDataOffset = buffer.getShort(offset + getFormat().OFFSET_COLUMN_FIXED_DATA_OFFSET); } } + + public Table getTable() { + return _table; + } + + public JetFormat getFormat() { + return getTable().getFormat(); + } + + public PageChannel getPageChannel() { + return getTable().getPageChannel(); + } public String getName() { return _name; @@ -307,10 +316,6 @@ public class Column implements Comparable { * @throws IllegalArgumentException if this column definition is invalid. */ public void validate(JetFormat format) { - if(_format != format) { - throw new IllegalArgumentException("format must be " + format + - " but is " + _format); - } if(getType() == null) { throw new IllegalArgumentException("must have type"); } @@ -441,23 +446,23 @@ public class Column implements Comparable { } else { // long value on other page(s) - if (lvalDefinition.length != _format.SIZE_LONG_VALUE_DEF) { - throw new IOException("Expected " + _format.SIZE_LONG_VALUE_DEF + + if (lvalDefinition.length != getFormat().SIZE_LONG_VALUE_DEF) { + throw new IOException("Expected " + getFormat().SIZE_LONG_VALUE_DEF + " bytes in long value definition, but found " + lvalDefinition.length); } byte rowNum = def.get(); int pageNum = ByteUtil.get3ByteInt(def, def.position()); - ByteBuffer lvalPage = _pageChannel.createPageBuffer(); + ByteBuffer lvalPage = getPageChannel().createPageBuffer(); switch (type) { case LONG_VALUE_TYPE_OTHER_PAGE: { - _pageChannel.readPage(lvalPage, pageNum); + getPageChannel().readPage(lvalPage, pageNum); - short rowStart = Table.findRowStart(lvalPage, rowNum, _format); - short rowEnd = Table.findRowEnd(lvalPage, rowNum, _format); + short rowStart = Table.findRowStart(lvalPage, rowNum, getFormat()); + short rowEnd = Table.findRowEnd(lvalPage, rowNum, getFormat()); if((rowEnd - rowStart) != length) { throw new IOException("Unexpected lval row length"); @@ -474,10 +479,10 @@ public class Column implements Comparable { int remainingLen = length; while(remainingLen > 0) { lvalPage.clear(); - _pageChannel.readPage(lvalPage, pageNum); + getPageChannel().readPage(lvalPage, pageNum); - short rowStart = Table.findRowStart(lvalPage, rowNum, _format); - short rowEnd = Table.findRowEnd(lvalPage, rowNum, _format); + short rowStart = Table.findRowStart(lvalPage, rowNum, getFormat()); + short rowEnd = Table.findRowEnd(lvalPage, rowNum, getFormat()); // read next page information lvalPage.position(rowStart); @@ -726,19 +731,19 @@ public class Column implements Comparable { // determine which type to write byte type = 0; - int lvalDefLen = _format.SIZE_LONG_VALUE_DEF; - if((_format.SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength) { + int lvalDefLen = getFormat().SIZE_LONG_VALUE_DEF; + if((getFormat().SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength) { type = LONG_VALUE_TYPE_THIS_PAGE; lvalDefLen += value.length; - } else if(Table.getRowSpaceUsage(value.length, _format) <= - _format.MAX_ROW_SIZE) + } else if(Table.getRowSpaceUsage(value.length, getFormat()) <= + getFormat().MAX_ROW_SIZE) { type = LONG_VALUE_TYPE_OTHER_PAGE; } else { type = LONG_VALUE_TYPE_OTHER_PAGES; } - ByteBuffer def = _pageChannel.createBuffer(lvalDefLen); + ByteBuffer def = getPageChannel().createBuffer(lvalDefLen); ByteUtil.put3ByteInt(def, value.length); def.put(type); @@ -752,7 +757,7 @@ public class Column implements Comparable { int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER; byte firstLvalRow = 0; - ByteBuffer lvalPage = _pageChannel.createPageBuffer(); + ByteBuffer lvalPage = getPageChannel().createPageBuffer(); // write other page(s) switch(type) { @@ -760,9 +765,9 @@ public class Column implements Comparable { writeLongValueHeader(lvalPage); firstLvalRow = (byte)Table.addDataPageRow(lvalPage, value.length, - _format); + getFormat()); lvalPage.put(value); - firstLvalPageNum = _pageChannel.writeNewPage(lvalPage); + firstLvalPageNum = getPageChannel().writeNewPage(lvalPage); break; case LONG_VALUE_TYPE_OTHER_PAGES: @@ -770,7 +775,7 @@ public class Column implements Comparable { ByteBuffer buffer = ByteBuffer.wrap(value); int remainingLen = buffer.remaining(); buffer.limit(0); - int lvalPageNum = _pageChannel.allocateNewPage(); + int lvalPageNum = getPageChannel().allocateNewPage(); byte lvalRow = 0; int nextLvalPageNum = 0; while(remainingLen > 0) { @@ -778,14 +783,14 @@ public class Column implements Comparable { writeLongValueHeader(lvalPage); // figure out how much we will put in this page - int chunkLength = Math.min(_format.MAX_ROW_SIZE - 4, + int chunkLength = Math.min(getFormat().MAX_ROW_SIZE - 4, remainingLen); nextLvalPageNum = ((chunkLength < remainingLen) ? - _pageChannel.allocateNewPage() : 0); + getPageChannel().allocateNewPage() : 0); // add row to this page lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4, - _format); + getFormat()); // write next page info (we'll always be writing into row 0 for // newly created pages) @@ -798,7 +803,7 @@ public class Column implements Comparable { remainingLen -= chunkLength; // write new page to database - _pageChannel.writePage(lvalPage, lvalPageNum); + getPageChannel().writePage(lvalPage, lvalPageNum); // hang onto first page info if(firstLvalPageNum == PageChannel.INVALID_PAGE_NUMBER) { @@ -833,8 +838,8 @@ public class Column implements Comparable { { lvalPage.put(PageTypes.DATA); //Page type lvalPage.put((byte) 1); //Unknown - lvalPage.putShort((short) (_format.PAGE_SIZE - - _format.OFFSET_ROW_START)); //Free space + lvalPage.putShort((short) (getFormat().PAGE_SIZE - + getFormat().OFFSET_ROW_START)); //Free space lvalPage.put((byte) 'L'); lvalPage.put((byte) 'V'); lvalPage.put((byte) 'A'); @@ -874,7 +879,7 @@ public class Column implements Comparable { switch(getType()) { case NUMERIC: // don't ask me why numerics are "var length" columns... - ByteBuffer buffer = _pageChannel.createBuffer(getLength(), order); + ByteBuffer buffer = getPageChannel().createBuffer(getLength(), order); writeNumericValue(buffer, obj); buffer.flip(); return buffer; @@ -931,7 +936,7 @@ public class Column implements Comparable { int size = getType().getFixedSize(); // create buffer for data - ByteBuffer buffer = _pageChannel.createBuffer(size, order); + ByteBuffer buffer = getPageChannel().createBuffer(size, order); obj = booleanToInteger(obj); @@ -1064,7 +1069,7 @@ public class Column implements Comparable { * @return A buffer with the text encoded */ private ByteBuffer encodeUncompressedText(CharSequence text) { - return _format.CHARSET.encode(CharBuffer.wrap(text)); + return getFormat().CHARSET.encode(CharBuffer.wrap(text)); } /** @@ -1081,7 +1086,7 @@ public class Column implements Comparable { */ private CharBuffer decodeUncompressedText(byte[] textBytes, int startPost, int length) { - return _format.CHARSET.decode(ByteBuffer.wrap(textBytes, startPost, + return getFormat().CHARSET.decode(ByteBuffer.wrap(textBytes, startPost, length)); } diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index d161d4e..7c62843 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -172,7 +172,7 @@ public class Database /** ID of the Tables system object */ private Integer _tableParentId; /** Format that the containing database is in */ - private JetFormat _format; + private final JetFormat _format; /** * Map of UPPERCASE table names to page numbers containing their definition * and their stored table name. @@ -182,7 +182,7 @@ public class Database /** set of table names as stored in the mdb file, created on demand */ private Set _tableNames; /** Reads and writes database pages */ - private PageChannel _pageChannel; + private final PageChannel _pageChannel; /** System catalog table */ private Table _systemCatalog; /** System access control entries table */ @@ -287,6 +287,10 @@ public class Database { _format = JetFormat.getFormat(channel); _pageChannel = new PageChannel(channel, _format, autoSync); + // note, it's slighly sketchy to pass ourselves along partially + // constructed, but only our _format and _pageChannel refs should be + // needed + _pageChannel.initialize(this); _buffer = _pageChannel.createPageBuffer(); readSystemCatalog(); } @@ -294,6 +298,10 @@ public class Database public PageChannel getPageChannel() { return _pageChannel; } + + public JetFormat getFormat() { + return _format; + } /** * @return The system catalog table @@ -316,7 +324,8 @@ public class Database throw new IOException("Looking for system catalog at page " + PAGE_SYSTEM_CATALOG + ", but page type is " + pageType); } - _systemCatalog = new Table(_buffer, _pageChannel, _format, PAGE_SYSTEM_CATALOG, "System Catalog"); + _systemCatalog = new Table(this, _buffer, PAGE_SYSTEM_CATALOG, + "System Catalog"); Map row; while ( (row = _systemCatalog.getNextRow(SYSTEM_CATALOG_COLUMNS)) != null) { @@ -349,7 +358,8 @@ public class Database throw new IOException("Looking for MSysACEs at page " + pageNum + ", but page type is " + pageType); } - _accessControlEntries = new Table(buffer, _pageChannel, _format, pageNum, "Access Control Entries"); + _accessControlEntries = new Table(this, buffer, pageNum, + "Access Control Entries"); } /** @@ -390,8 +400,7 @@ public class Database int pageNumber = tableInfo.pageNumber.intValue(); _pageChannel.readPage(_buffer, pageNumber); - return new Table(_buffer, _pageChannel, _format, pageNumber, - tableInfo.tableName); + return new Table(this, _buffer, pageNumber, tableInfo.tableName); } /** diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 073ce57..3dba5ef 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -184,19 +184,18 @@ public class Index implements Comparable { CODES_EXT.put('\'', new byte[]{(byte)6, (byte)128}); CODES_EXT.put('-', new byte[]{(byte)6, (byte)130}); } - + + /** owning table */ + private final Table _table; /** Page number of the index data */ private int _pageNumber; - private int _parentPageNumber; /** Number of rows in the index NOTE: this does not actually seem to be the row count, unclear what the value means*/ private int _rowCount; - private JetFormat _format; private SortedSet _entries; /** Map of columns to flags */ private Map _columns = new LinkedHashMap(); - private PageChannel _pageChannel; /** 0-based index number */ private int _indexNumber; /** Index name */ @@ -209,12 +208,22 @@ public class Index implements Comparable { /** FIXME, for now, we can't write multi-page indexes or indexes using the funky primary key compression scheme */ boolean _readOnly; - public Index(int parentPageNumber, PageChannel channel, JetFormat format) { - _parentPageNumber = parentPageNumber; - _pageChannel = channel; - _format = format; + public Index(Table table) { + _table = table; + } + + public Table getTable() { + return _table; } + public JetFormat getFormat() { + return getTable().getFormat(); + } + + public PageChannel getPageChannel() { + return getTable().getPageChannel(); + } + public void setIndexNumber(int indexNumber) { _indexNumber = indexNumber; } @@ -304,18 +313,18 @@ public class Index implements Comparable { throw new UnsupportedOperationException( "FIXME cannot write indexes of this type yet"); } - _pageChannel.writePage(write(), _pageNumber); + getPageChannel().writePage(write(), _pageNumber); } /** * Write this index out to a buffer */ private ByteBuffer write() throws IOException { - ByteBuffer buffer = _pageChannel.createPageBuffer(); + ByteBuffer buffer = getPageChannel().createPageBuffer(); buffer.put((byte) 0x04); //Page type buffer.put((byte) 0x01); //Unknown buffer.putShort((short) 0); //Free space - buffer.putInt(_parentPageNumber); + buffer.putInt(getTable().getTableDefPageNumber()); buffer.putInt(0); //Prev page buffer.putInt(0); //Next page buffer.putInt(0); //Leaf page @@ -323,7 +332,7 @@ public class Index implements Comparable { buffer.put((byte) 0); // compressed byte count buffer.put((byte) 0); //Unknown buffer.put((byte) 0); //Unknown - byte[] entryMask = new byte[_format.SIZE_INDEX_ENTRY_MASK]; + byte[] entryMask = new byte[getFormat().SIZE_INDEX_ENTRY_MASK]; int totalSize = 0; for(Entry entry : _entries) { int size = entry.size(); @@ -339,7 +348,7 @@ public class Index implements Comparable { for(Entry entry : _entries) { entry.write(buffer); } - buffer.putShort(2, (short) (_format.PAGE_SIZE - buffer.position())); + buffer.putShort(2, (short) (getFormat().PAGE_SIZE - buffer.position())); return buffer; } @@ -371,12 +380,12 @@ public class Index implements Comparable { { _entries = new TreeSet(); - ByteBuffer indexPage = _pageChannel.createPageBuffer(); + ByteBuffer indexPage = getPageChannel().createPageBuffer(); // find first leaf page int leafPageNumber = _pageNumber; while(true) { - _pageChannel.readPage(indexPage, leafPageNumber); + getPageChannel().readPage(indexPage, leafPageNumber); if(indexPage.get(0) == INDEX_NODE_PAGE_TYPE) { // FIXME we can't modify this index at this point in time @@ -400,7 +409,7 @@ public class Index implements Comparable { _readOnly = true; // found another one - _pageChannel.readPage(indexPage, leafPageNumber); + getPageChannel().readPage(indexPage, leafPageNumber); } else { // all done @@ -444,7 +453,7 @@ public class Index implements Comparable { // note, "header" data is in LITTLE_ENDIAN format, entry data is in // BIG_ENDIAN format - int nextLeafPage = leafPage.getInt(_format.OFFSET_NEXT_INDEX_LEAF_PAGE); + int nextLeafPage = leafPage.getInt(getFormat().OFFSET_NEXT_INDEX_LEAF_PAGE); readIndexPage(leafPage, true, _entries, null); return nextLeafPage; @@ -462,10 +471,10 @@ public class Index implements Comparable { // note, "header" data is in LITTLE_ENDIAN format, entry data is in // BIG_ENDIAN format int numCompressedBytes = indexPage.get( - _format.OFFSET_INDEX_COMPRESSED_BYTE_COUNT); - int entryMaskLength = _format.SIZE_INDEX_ENTRY_MASK; - int entryMaskPos = _format.OFFSET_INDEX_ENTRY_MASK; - int entryPos = entryMaskPos + _format.SIZE_INDEX_ENTRY_MASK; + getFormat().OFFSET_INDEX_COMPRESSED_BYTE_COUNT); + int entryMaskLength = getFormat().SIZE_INDEX_ENTRY_MASK; + int entryMaskPos = getFormat().OFFSET_INDEX_ENTRY_MASK; + int entryPos = entryMaskPos + getFormat().SIZE_INDEX_ENTRY_MASK; int lastStart = 0; byte[] valuePrefix = null; boolean firstEntry = true; diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java index 0c823c8..e5c3fed 100644 --- a/src/java/com/healthmarketscience/jackcess/PageChannel.java +++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java @@ -54,13 +54,13 @@ public class PageChannel implements Channel, Flushable { private static final int PAGE_GLOBAL_USAGE_MAP = 1; /** Channel containing the database */ - private FileChannel _channel; + private final FileChannel _channel; /** Format of the database in the channel */ - private JetFormat _format; + private final JetFormat _format; + /** whether or not to force all writes to disk immediately */ + private final boolean _autoSync; /** Tracks free pages in the database. */ private UsageMap _globalUsageMap; - /** whether or not to force all writes to disk immediately */ - private boolean _autoSync; /** * @param channel Channel containing the database @@ -72,15 +72,20 @@ public class PageChannel implements Channel, Flushable { _channel = channel; _format = format; _autoSync = autoSync; - //Null check only exists for unit tests. Channel should never normally be null. - if (channel != null) { - // note the global usage map is a special map where any page outside of - // the current range is assumed to be "on" - _globalUsageMap = UsageMap.read(this, PAGE_GLOBAL_USAGE_MAP, (byte) 0, - format, true); - } } + /** + * Does second-stage initialization, must be called after construction. + */ + public void initialize(Database database) + throws IOException + { + // note the global usage map is a special map where any page outside of + // the current range is assumed to be "on" + _globalUsageMap = UsageMap.read(database, PAGE_GLOBAL_USAGE_MAP, (byte) 0, + true); + } + /** * Only used by unit tests */ @@ -92,6 +97,10 @@ public class PageChannel implements Channel, Flushable { _format = JetFormat.VERSION_4; _autoSync = false; } + + public JetFormat getFormat() { + return _format; + } /** * @param buffer Buffer to read the page into @@ -109,7 +118,7 @@ public class PageChannel implements Channel, Flushable { LOG.debug("Reading in page " + Integer.toHexString(pageNumber)); } buffer.clear(); - boolean rtn = _channel.read(buffer, (long) pageNumber * (long) _format.PAGE_SIZE) != -1; + boolean rtn = _channel.read(buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE) != -1; buffer.flip(); return rtn; } @@ -136,7 +145,7 @@ public class PageChannel implements Channel, Flushable { { page.rewind(); page.position(pageOffset); - _channel.write(page, (((long) pageNumber * (long) _format.PAGE_SIZE) + + _channel.write(page, (((long) pageNumber * (long) getFormat().PAGE_SIZE) + (long) pageOffset)); if(_autoSync) { flush(); @@ -155,9 +164,9 @@ public class PageChannel implements Channel, Flushable { // push the buffer to the end of the page, so that a full page's worth of // data is written regardless of the incoming buffer size (we use a tiny // buffer in allocateNewPage) - long offset = size + (_format.PAGE_SIZE - page.remaining()); + long offset = size + (getFormat().PAGE_SIZE - page.remaining()); _channel.write(page, offset); - int pageNumber = (int) (size / _format.PAGE_SIZE); + int pageNumber = (int) (size / getFormat().PAGE_SIZE); _globalUsageMap.removePageNumber(pageNumber); //force is done here return pageNumber; } @@ -175,7 +184,7 @@ public class PageChannel implements Channel, Flushable { * @return A newly-allocated buffer that can be passed to readPage */ public ByteBuffer createPageBuffer() { - return createBuffer(_format.PAGE_SIZE); + return createBuffer(getFormat().PAGE_SIZE); } /** diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index 6acab8d..9fdc5c5 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -76,7 +76,9 @@ public class Table 0)); } }; - + + /** owning database */ + private final Database _database; /** State used for reading the table rows */ private RowState _rowState; /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */ @@ -92,25 +94,21 @@ public class Table /** last auto number for the table */ private int _lastAutoNumber; /** page number of the definition of this table */ - private int _tableDefPageNumber; + private final int _tableDefPageNumber; /** Number of rows left to be read on the current page */ private short _rowsLeftOnPage = 0; /** max Number of columns in the table (includes previous deletions) */ private short _maxColumnCount; /** max Number of variable columns in the table */ private short _maxVarColumnCount; - /** Format of the database that contains this table */ - private JetFormat _format; /** List of columns in this table, ordered by column number */ private List _columns = new ArrayList(); /** List of variable length columns in this table, ordered by offset */ private List _varColumns = new ArrayList(); /** List of indexes on this table */ private List _indexes = new ArrayList(); - /** Used to read in pages */ - private PageChannel _pageChannel; /** Table name as stored in Database */ - private String _name; + private final String _name; /** Usage map of pages that this table owns */ private UsageMap _ownedPages; /** Iterator over the pages that this table owns */ @@ -121,11 +119,14 @@ public class Table /** * Only used by unit tests */ - Table(boolean testing) throws IOException { + Table(boolean testing, List columns) throws IOException { if(!testing) { throw new IllegalArgumentException(); } - _pageChannel = new PageChannel(testing); + _database = null; + _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER; + _name = null; + setColumns(columns); } /** @@ -135,27 +136,25 @@ public class Table * @param pageNumber Page number of the table definition * @param name Table name */ - protected Table(ByteBuffer tableBuffer, PageChannel pageChannel, - JetFormat format, int pageNumber, String name) + protected Table(Database database, ByteBuffer tableBuffer, + int pageNumber, String name) throws IOException { - _pageChannel = pageChannel; - _format = format; + _database = database; _tableDefPageNumber = pageNumber; _name = name; - int nextPage; + int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE); ByteBuffer nextPageBuffer = null; - nextPage = tableBuffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE); while (nextPage != 0) { if (nextPageBuffer == null) { - nextPageBuffer = _pageChannel.createPageBuffer(); + nextPageBuffer = getPageChannel().createPageBuffer(); } - _pageChannel.readPage(nextPageBuffer, nextPage); - nextPage = nextPageBuffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE); - ByteBuffer newBuffer = _pageChannel.createBuffer( - tableBuffer.capacity() + format.PAGE_SIZE - 8); + getPageChannel().readPage(nextPageBuffer, nextPage); + nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE); + ByteBuffer newBuffer = getPageChannel().createBuffer( + tableBuffer.capacity() + getFormat().PAGE_SIZE - 8); newBuffer.put(tableBuffer); - newBuffer.put(nextPageBuffer.array(), 8, format.PAGE_SIZE - 8); + newBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8); tableBuffer = newBuffer; tableBuffer.flip(); } @@ -171,6 +170,22 @@ public class Table public String getName() { return _name; } + + public Database getDatabase() { + return _database; + } + + public JetFormat getFormat() { + return getDatabase().getFormat(); + } + + public PageChannel getPageChannel() { + return getDatabase().getPageChannel(); + } + + protected int getTableDefPageNumber() { + return _tableDefPageNumber; + } /** * @return All of the columns in this table (unmodifiable List) @@ -182,7 +197,7 @@ public class Table /** * Only called by unit tests */ - void setColumns(List columns) { + private void setColumns(List columns) { _columns = columns; int colIdx = 0; int varLenIdx = 0; @@ -243,9 +258,9 @@ public class Table } // delete flag always gets set in the "root" page (even if overflow row) - ByteBuffer rowBuffer = _rowState.getPage(_pageChannel); + ByteBuffer rowBuffer = _rowState.getPage(getPageChannel()); int pageNumber = _rowState.getPageNumber(); - int rowIndex = getRowStartOffset(_currentRowInPage, _format); + int rowIndex = getRowStartOffset(_currentRowInPage, getFormat()); rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex) | DELETED_ROW_MASK | OVERFLOW_ROW_MASK)); writeDataPage(rowBuffer, pageNumber); @@ -456,13 +471,13 @@ public class Table } // load new page - ByteBuffer rowBuffer = _rowState.setPage(_pageChannel, nextPageNumber); + ByteBuffer rowBuffer = _rowState.setPage(getPageChannel(), nextPageNumber); if(rowBuffer.get() != PageTypes.DATA) { //Only interested in data pages continue; } - _rowsLeftOnPage = rowBuffer.getShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE); + _rowsLeftOnPage = rowBuffer.getShort(getFormat().OFFSET_NUM_ROWS_ON_DATA_PAGE); if(_rowsLeftOnPage == 0) { // no rows on this page? continue; @@ -475,7 +490,7 @@ public class Table _rowsLeftOnPage--; ByteBuffer rowBuffer = - positionAtRow(_rowState, _currentRowInPage, _pageChannel, _format); + positionAtRow(_rowState, _currentRowInPage, getPageChannel(), getFormat()); if(rowBuffer != null) { // we found a non-deleted row, return it return rowBuffer; @@ -862,40 +877,38 @@ public class Table if (LOG.isDebugEnabled()) { tableBuffer.rewind(); LOG.debug("Table def block:\n" + ByteUtil.toHexString(tableBuffer, - _format.SIZE_TDEF_HEADER)); + getFormat().SIZE_TDEF_HEADER)); } - _rowCount = tableBuffer.getInt(_format.OFFSET_NUM_ROWS); - _lastAutoNumber = tableBuffer.getInt(_format.OFFSET_NEXT_AUTO_NUMBER); - _tableType = tableBuffer.get(_format.OFFSET_TABLE_TYPE); - _maxColumnCount = tableBuffer.getShort(_format.OFFSET_MAX_COLS); - _maxVarColumnCount = tableBuffer.getShort(_format.OFFSET_NUM_VAR_COLS); - short columnCount = tableBuffer.getShort(_format.OFFSET_NUM_COLS); - _indexSlotCount = tableBuffer.getInt(_format.OFFSET_NUM_INDEX_SLOTS); - _indexCount = tableBuffer.getInt(_format.OFFSET_NUM_INDEXES); + _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS); + _lastAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER); + _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE); + _maxColumnCount = tableBuffer.getShort(getFormat().OFFSET_MAX_COLS); + _maxVarColumnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_VAR_COLS); + short columnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_COLS); + _indexSlotCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS); + _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES); - byte rowNum = tableBuffer.get(_format.OFFSET_OWNED_PAGES); - int pageNum = ByteUtil.get3ByteInt(tableBuffer, _format.OFFSET_OWNED_PAGES + 1); - _ownedPages = UsageMap.read(_pageChannel, pageNum, rowNum, _format, - false); + byte rowNum = tableBuffer.get(getFormat().OFFSET_OWNED_PAGES); + int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1); + _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false); _ownedPagesIterator = _ownedPages.iterator(); - rowNum = tableBuffer.get(_format.OFFSET_FREE_SPACE_PAGES); - pageNum = ByteUtil.get3ByteInt(tableBuffer, _format.OFFSET_FREE_SPACE_PAGES + 1); - _freeSpacePages = UsageMap.read(_pageChannel, pageNum, rowNum, _format, - false); + rowNum = tableBuffer.get(getFormat().OFFSET_FREE_SPACE_PAGES); + pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1); + _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false); for (int i = 0; i < _indexCount; i++) { - Index index = new Index(_tableDefPageNumber, _pageChannel, _format); + Index index = new Index(this); _indexes.add(index); - index.setRowCount(tableBuffer.getInt(_format.OFFSET_INDEX_DEF_BLOCK + - i * _format.SIZE_INDEX_DEFINITION + 4)); + index.setRowCount(tableBuffer.getInt(getFormat().OFFSET_INDEX_DEF_BLOCK + + i * getFormat().SIZE_INDEX_DEFINITION + 4)); } - int offset = _format.OFFSET_INDEX_DEF_BLOCK + - _indexCount * _format.SIZE_INDEX_DEFINITION; + int offset = getFormat().OFFSET_INDEX_DEF_BLOCK + + _indexCount * getFormat().SIZE_INDEX_DEFINITION; Column column; for (int i = 0; i < columnCount; i++) { - column = new Column(tableBuffer, - offset + i * _format.SIZE_COLUMN_HEADER, _pageChannel, _format); + column = new Column(this, tableBuffer, + offset + i * getFormat().SIZE_COLUMN_HEADER); _columns.add(column); if(column.isVariableLength()) { // also shove it in the variable columns list, which is ordered @@ -903,7 +916,7 @@ public class Table _varColumns.add(column); } } - offset += columnCount * _format.SIZE_COLUMN_HEADER; + offset += columnCount * getFormat().SIZE_COLUMN_HEADER; for (int i = 0; i < columnCount; i++) { column = (Column) _columns.get(i); short nameLength = tableBuffer.getShort(offset); @@ -911,7 +924,7 @@ public class Table byte[] nameBytes = new byte[nameLength]; tableBuffer.position(offset); tableBuffer.get(nameBytes, 0, (int) nameLength); - column.setName(_format.CHARSET.decode(ByteBuffer.wrap(nameBytes)).toString()); + column.setName(getFormat().CHARSET.decode(ByteBuffer.wrap(nameBytes)).toString()); offset += nameLength; } Collections.sort(_columns); @@ -928,7 +941,7 @@ public class Table int idxOffset = tableBuffer.position(); tableBuffer.position(idxOffset + - (_format.OFFSET_INDEX_NUMBER_BLOCK * _indexCount)); + (getFormat().OFFSET_INDEX_NUMBER_BLOCK * _indexCount)); // there are _indexSlotCount blocks here, we ignore any slot with an index // number greater than the number of actual indexes @@ -960,7 +973,7 @@ public class Table for (int i = 0; i < _indexCount; i++) { byte[] nameBytes = new byte[tableBuffer.getShort()]; tableBuffer.get(nameBytes); - _indexes.get(i).setName(_format.CHARSET.decode(ByteBuffer.wrap( + _indexes.get(i).setName(getFormat().CHARSET.decode(ByteBuffer.wrap( nameBytes)).toString()); } int idxEndOffset = tableBuffer.position(); @@ -986,7 +999,7 @@ public class Table throws IOException { // write the page data - _pageChannel.writePage(pageBuffer, pageNumber); + getPageChannel().writePage(pageBuffer, pageNumber); // if the overflow buffer is this page, invalidate it _rowState.possiblyInvalidate(pageNumber, pageBuffer); @@ -1006,12 +1019,12 @@ public class Table * @param rows List of Object[] row values */ public void addRows(List rows) throws IOException { - ByteBuffer dataPage = _pageChannel.createPageBuffer(); + ByteBuffer dataPage = getPageChannel().createPageBuffer(); ByteBuffer[] rowData = new ByteBuffer[rows.size()]; Iterator iter = rows.iterator(); for (int i = 0; iter.hasNext(); i++) { - rowData[i] = createRow(iter.next(), _format.MAX_ROW_SIZE); - if (rowData[i].limit() > _format.MAX_ROW_SIZE) { + rowData[i] = createRow(iter.next(), getFormat().MAX_ROW_SIZE); + if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) { throw new IOException("Row size " + rowData[i].limit() + " is too large"); } @@ -1026,7 +1039,7 @@ public class Table revPageIter.hasNextPage(); ) { int tmpPageNumber = revPageIter.getNextPage(); - _pageChannel.readPage(dataPage, tmpPageNumber); + getPageChannel().readPage(dataPage, tmpPageNumber); if(dataPage.get() == PageTypes.DATA) { // found last data page pageNumber = tmpPageNumber; @@ -1041,8 +1054,8 @@ public class Table for (int i = 0; i < rowData.length; i++) { rowSize = rowData[i].remaining(); - int rowSpaceUsage = getRowSpaceUsage(rowSize, _format); - short freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE); + int rowSpaceUsage = getRowSpaceUsage(rowSize, getFormat()); + short freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE); if (freeSpaceInPage < rowSpaceUsage) { //Last data page is full. Create a new one. @@ -1051,11 +1064,11 @@ public class Table _freeSpacePages.removePageNumber(pageNumber); pageNumber = newDataPage(dataPage); - freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE); + freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE); } // write out the row data - int rowNum = addDataPageRow(dataPage, rowSize, _format); + int rowNum = addDataPageRow(dataPage, rowSize, getFormat()); dataPage.put(rowData[i]); // update the indexes @@ -1076,24 +1089,24 @@ public class Table private void updateTableDefinition() throws IOException { // load table definition - ByteBuffer tdefPage = _pageChannel.createPageBuffer(); - _pageChannel.readPage(tdefPage, _tableDefPageNumber); + ByteBuffer tdefPage = getPageChannel().createPageBuffer(); + getPageChannel().readPage(tdefPage, _tableDefPageNumber); // make sure rowcount and autonumber are up-to-date - tdefPage.putInt(_format.OFFSET_NUM_ROWS, _rowCount); - tdefPage.putInt(_format.OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber); + tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount); + tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber); // write any index changes Iterator indIter = _indexes.iterator(); for (int i = 0; i < _indexes.size(); i++) { - tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK + - (i * _format.SIZE_INDEX_DEFINITION) + 4, _rowCount); + tdefPage.putInt(getFormat().OFFSET_INDEX_DEF_BLOCK + + (i * getFormat().SIZE_INDEX_DEFINITION) + 4, _rowCount); Index index = indIter.next(); index.update(); } // write modified table definition - _pageChannel.writePage(tdefPage, _tableDefPageNumber); + getPageChannel().writePage(tdefPage, _tableDefPageNumber); } /** @@ -1106,12 +1119,12 @@ public class Table } dataPage.put(PageTypes.DATA); //Page type dataPage.put((byte) 1); //Unknown - dataPage.putShort((short)getRowSpaceUsage(_format.MAX_ROW_SIZE, - _format)); //Free space in this page + dataPage.putShort((short)getRowSpaceUsage(getFormat().MAX_ROW_SIZE, + getFormat())); //Free space in this page dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition dataPage.putInt(0); //Unknown dataPage.putInt(0); //Number of records on this page - int pageNumber = _pageChannel.writeNewPage(dataPage); + int pageNumber = getPageChannel().writeNewPage(dataPage); _ownedPages.addPageNumber(pageNumber); _freeSpacePages.addPageNumber(pageNumber); return pageNumber; @@ -1121,7 +1134,7 @@ public class Table * Serialize a row of Objects into a byte buffer */ ByteBuffer createRow(Object[] rowArray, int maxRowSize) throws IOException { - ByteBuffer buffer = _pageChannel.createPageBuffer(); + ByteBuffer buffer = getPageChannel().createPageBuffer(); buffer.putShort((short) _maxColumnCount); NullMask nullMask = new NullMask(_maxColumnCount); diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java index ec3bc36..3305f11 100644 --- a/src/java/com/healthmarketscience/jackcess/UsageMap.java +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -48,15 +48,15 @@ public class UsageMap 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; - + + /** owning database */ + private final Database _database; /** Page number of the map table declaration */ - private int _tablePageNum; + private final int _tablePageNum; /** 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; /** First page that this usage map applies to */ private int _startPage; /** Last page that this usage map applies to */ @@ -64,9 +64,7 @@ public class UsageMap /** bits representing page numbers used, offset from _startPage */ private BitSet _pageNumbers = new BitSet(); /** Buffer that contains the usage map table declaration page */ - private ByteBuffer _tableBuffer; - /** Used to read in pages */ - private PageChannel _pageChannel; + private final ByteBuffer _tableBuffer; /** modification count on the usage map, used to keep the iterators in sync */ private int _modCount = 0; @@ -75,22 +73,20 @@ public class UsageMap private Handler _handler; /** - * @param pageChannel Used to read in pages + * @param database database that contains this usage map * @param tableBuffer 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 */ - private UsageMap(PageChannel pageChannel, ByteBuffer tableBuffer, - int pageNum, JetFormat format, short rowStart) + private UsageMap(Database database, ByteBuffer tableBuffer, + int pageNum, short rowStart) throws IOException { - _pageChannel = pageChannel; + _database = database; _tableBuffer = tableBuffer; _tablePageNum = pageNum; - _format = format; _rowStart = rowStart; - _tableBuffer.position((int) _rowStart + format.OFFSET_USAGE_MAP_START); + _tableBuffer.position((int) _rowStart + getFormat().OFFSET_USAGE_MAP_START); _startOffset = _tableBuffer.position(); if (LOG.isDebugEnabled()) { LOG.debug("Usage map block:\n" + ByteUtil.toHexString(_tableBuffer, _rowStart, @@ -98,27 +94,38 @@ public class UsageMap } } + public Database getDatabase() { + return _database; + } + + public JetFormat getFormat() { + return getDatabase().getFormat(); + } + + public PageChannel getPageChannel() { + return getDatabase().getPageChannel(); + } + /** - * @param pageChannel Used to read in pages + * @param database database that contains this usage map * @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, - boolean assumeOutOfRangeBitsOn) + public static UsageMap read(Database database, int pageNum, + byte rowNum, boolean assumeOutOfRangeBitsOn) throws IOException { + JetFormat format = database.getFormat(); + PageChannel pageChannel = database.getPageChannel(); ByteBuffer tableBuffer = pageChannel.createPageBuffer(); pageChannel.readPage(tableBuffer, pageNum); short rowStart = Table.findRowStart(tableBuffer, rowNum, format); int rowEnd = Table.findRowEnd(tableBuffer, rowNum, format); tableBuffer.limit(rowEnd); byte mapType = tableBuffer.get(rowStart); - UsageMap rtn = new UsageMap(pageChannel, tableBuffer, pageNum, format, - rowStart); + UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart); rtn.initHandler(mapType, assumeOutOfRangeBitsOn); return rtn; } @@ -163,14 +170,6 @@ public class UsageMap return _tablePageNum; } - protected PageChannel getPageChannel() { - return _pageChannel; - } - - protected JetFormat getFormat() { - return _format; - } - protected int getStartPage() { return _startPage; } @@ -251,7 +250,7 @@ public class UsageMap throws IOException { // note, we only want to write the row data with which we are working - _pageChannel.writePage(_tableBuffer, _tablePageNum, _rowStart); + getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart); } /** diff --git a/test/src/java/com/healthmarketscience/jackcess/TableTest.java b/test/src/java/com/healthmarketscience/jackcess/TableTest.java index 041600a..5b22db0 100644 --- a/test/src/java/com/healthmarketscience/jackcess/TableTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/TableTest.java @@ -18,19 +18,24 @@ public class TableTest extends TestCase { } public void testCreateRow() throws Exception { - JetFormat format = JetFormat.VERSION_4; - Table table = new Table(true); + final JetFormat format = JetFormat.VERSION_4; + final PageChannel pageChannel = new PageChannel(true); List columns = new ArrayList(); - Column col = new Column(true); + Column col = newTestColumn(pageChannel); col.setType(DataType.INT); columns.add(col); - col = new Column(true); + col = newTestColumn(pageChannel); col.setType(DataType.TEXT); columns.add(col); - col = new Column(true); + col = newTestColumn(pageChannel); col.setType(DataType.TEXT); columns.add(col); - table.setColumns(columns); + Table table = new Table(true, columns) { + @Override + public PageChannel getPageChannel() { + return pageChannel; + } + }; int colCount = 3; Object[] row = new Object[colCount]; row[0] = new Short((short) 9); @@ -46,5 +51,18 @@ public class TableTest extends TestCase { assertEquals((short) 2, buffer.getShort(28)); assertEquals((byte) 7, buffer.get(30)); } + + private static Column newTestColumn(final PageChannel pageChannel) { + return new Column(true) { + @Override + public PageChannel getPageChannel() { + return pageChannel; + } + @Override + public JetFormat getFormat() { + return JetFormat.VERSION_4; + } + }; + } }