Explorar el Código

add (initial) read-only support for Access 97 (jet format 3) databases (#3003375); add various ways to override charset and timezone (#2997751)

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@475 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-1.2.1
James Ahlborn hace 14 años
padre
commit
13e81be7d5

+ 7
- 0
src/changes/changes.xml Ver fichero

@@ -17,6 +17,13 @@
Make Database.escapeIdentifier public; add methods to TableBuilder and
ColumnBuilder to optionally escape names.
</action>
<action dev="jahlborn" type="update" issue="2997751">
Add support for overriding charset and tiemzone used when
reading/writing database.
</action>
<action dev="jahlborn" type="add" issue="3003375">
Add support for reading Access 97 (jet format 3) databases.
</action>
</release>
<release version="1.2.0" date="2010-04-18">
<action dev="bhamail" type="update" issue="1451628">

+ 35
- 0
src/java/com/healthmarketscience/jackcess/ByteUtil.java Ver fichero

@@ -256,6 +256,41 @@ public final class ByteUtil {
}
}

/**
* Read an unsigned variable length int from a buffer
* @param buffer Buffer containing the variable length int
* @return The unsigned int
*/
public static int getUnsignedVarInt(ByteBuffer buffer, int numBytes) {
int pos = buffer.position();
int rtn = getUnsignedVarInt(buffer, pos, numBytes);
buffer.position(pos + numBytes);
return rtn;
}

/**
* Read an unsigned variable length int from a buffer
* @param buffer Buffer containing the variable length int
* @param offset Offset at which to read the value
* @return The unsigned int
*/
public static int getUnsignedVarInt(ByteBuffer buffer, int offset,
int numBytes) {
switch(numBytes) {
case 1:
return getUnsignedByte(buffer, offset);
case 2:
return getUnsignedShort(buffer, offset);
case 3:
return get3ByteInt(buffer, offset);
case 4:
return buffer.getInt(offset);
default:
throw new IllegalArgumentException("Invalid num bytes " + numBytes);
}
}

/**
* Sets all bits in the given remaining byte range to 0.
*/

+ 26
- 16
src/java/com/healthmarketscience/jackcess/Column.java Ver fichero

@@ -39,12 +39,14 @@ import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -345,6 +347,14 @@ public class Column implements Comparable<Column> {
return _fixedDataOffset;
}

protected Charset getCharset() {
return getTable().getDatabase().getCharset();
}

protected TimeZone getTimeZone() {
return getTable().getDatabase().getTimeZone();
}

private void setAutoNumberGenerator()
{
if(!_autoNumber || (_type == null)) {
@@ -732,7 +742,7 @@ public class Column implements Comparable<Column> {
/**
* Returns a java long time value converted from an access date double.
*/
private static long fromDateDouble(double value)
private long fromDateDouble(double value)
{
long time = Math.round(value * MILLISECONDS_PER_DAY);
time -= MILLIS_BETWEEN_EPOCH_AND_1900;
@@ -763,7 +773,7 @@ public class Column implements Comparable<Column> {
* Returns an access date double converted from a java Date/Calendar/Number
* time value.
*/
private static double toDateDouble(Object value)
private double toDateDouble(Object value)
{
// seems access stores dates in the local timezone. guess you just
// hope you read it in the same timezone in which it was written!
@@ -780,9 +790,9 @@ public class Column implements Comparable<Column> {
/**
* Gets the timezone offset from UTC for the given time (including DST).
*/
private static long getTimeZoneOffset(long time)
private long getTimeZoneOffset(long time)
{
Calendar c = Calendar.getInstance();
Calendar c = Calendar.getInstance(getTimeZone());
c.setTimeInMillis(time);
return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
}
@@ -1239,7 +1249,7 @@ public class Column implements Comparable<Column> {
}
return decodeUncompressedText(data, getFormat());
return decodeUncompressedText(data, getCharset());
} catch (IllegalInputException e) {
throw (IOException)
@@ -1273,7 +1283,7 @@ public class Column implements Comparable<Column> {
} else {
// handle uncompressed data
textBuf.append(decodeUncompressedText(data, dataStart, dataLength,
getFormat()));
getCharset()));
}
}

@@ -1282,9 +1292,9 @@ public class Column implements Comparable<Column> {
* @return the decoded string
*/
private static CharBuffer decodeUncompressedText(
byte[] textBytes, int startPos, int length, JetFormat format)
byte[] textBytes, int startPos, int length, Charset charset)
{
return format.CHARSET.decode(ByteBuffer.wrap(textBytes, startPos, length));
return charset.decode(ByteBuffer.wrap(textBytes, startPos, length));
}

/**
@@ -1320,7 +1330,7 @@ public class Column implements Comparable<Column> {
}
}

return encodeUncompressedText(text, getFormat());
return encodeUncompressedText(text, getCharset());
}

/**
@@ -1364,25 +1374,25 @@ public class Column implements Comparable<Column> {
/**
* @param textBytes bytes of text to decode
* @param format relevant db format
* @param charset relevant charset
* @return the decoded string
*/
public static String decodeUncompressedText(byte[] textBytes,
JetFormat format)
public static String decodeUncompressedText(byte[] textBytes,
Charset charset)
{
return decodeUncompressedText(textBytes, 0, textBytes.length, format)
return decodeUncompressedText(textBytes, 0, textBytes.length, charset)
.toString();
}

/**
* @param text Text to encode
* @param format relevant db format
* @param db relevant db
* @return A buffer with the text encoded
*/
public static ByteBuffer encodeUncompressedText(CharSequence text,
JetFormat format)
Charset charset)
{
return format.CHARSET.encode(CharBuffer.wrap(text));
return charset.encode(CharBuffer.wrap(text));
}


+ 189
- 7
src/java/com/healthmarketscience/jackcess/Database.java Ver fichero

@@ -37,6 +37,7 @@ import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -52,6 +53,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;

import com.healthmarketscience.jackcess.query.Query;
@@ -100,6 +102,16 @@ public class Database
public static final String USE_BIG_INDEX_PROPERTY =
"com.healthmarketscience.jackcess.bigIndex";

/** system property which can be used to set the default TimeZone used for
date calculations. */
public static final String TIMEZONE_PROPERTY =
"com.healthmarketscience.jackcess.timeZone";

/** system property prefix which can be used to set the default Charset
used for text data (full property includes the JetFormat version). */
public static final String CHARSET_PROPERTY_PREFIX =
"com.healthmarketscience.jackcess.charset.";

/** default error handler used if none provided (just rethrows exception) */
public static final ErrorHandler DEFAULT_ERROR_HANDLER = new ErrorHandler() {
public Object handleRowError(Column column,
@@ -173,6 +185,7 @@ public class Database
public static enum FileFormat {

V1997(null, JetFormat.VERSION_3),
V2000("com/healthmarketscience/jackcess/empty.mdb", JetFormat.VERSION_4),
V2003("com/healthmarketscience/jackcess/empty2003.mdb", JetFormat.VERSION_4),
V2007("com/healthmarketscience/jackcess/empty2007.accdb", JetFormat.VERSION_5, ".accdb");
@@ -301,6 +314,10 @@ public class Database
private ErrorHandler _dbErrorHandler;
/** the file format of the database */
private FileFormat _fileFormat;
/** charset to use when handling text */
private Charset _charset;
/** timezone to use when handling dates */
private TimeZone _timeZone;
/**
* Open an existing Database. If the existing file is not writeable, the
@@ -355,13 +372,58 @@ public class Database
*/
public static Database open(File mdbFile, boolean readOnly, boolean autoSync)
throws IOException
{
return open(mdbFile, readOnly, autoSync, null, null);
}

/**
* Open an existing Database. If the existing file is not writeable or the
* readOnly flag is <code>true</code>, the file will be opened read-only.
* @param mdbFile File containing the database
* @param readOnly iff <code>true</code>, force opening file in read-only
* mode
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing.
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
*/
public static Database open(File mdbFile, boolean readOnly, boolean autoSync,
Charset charset, TimeZone timeZone)
throws IOException
{
if(!mdbFile.exists() || !mdbFile.canRead()) {
throw new FileNotFoundException("given file does not exist: " + mdbFile);
}
return new Database(openChannel(mdbFile,
(!mdbFile.canWrite() || readOnly)),
autoSync, null);

// force read-only for non-writable files
readOnly |= !mdbFile.canWrite();

// open file channel
FileChannel channel = openChannel(mdbFile, readOnly);

if(!readOnly) {

// verify that format supports writing
JetFormat jetFormat = JetFormat.getFormat(channel);

if(jetFormat.READ_ONLY) {
// shutdown the channel (quietly)
try {
channel.close();
} catch(Exception ignored) {
// we don't care
}
throw new IOException("jet format '" + jetFormat + "' does not support writing");
}
}

return new Database(channel, autoSync, null, charset, timeZone);
}
/**
@@ -438,12 +500,40 @@ public class Database
boolean autoSync)
throws IOException
{
return create(fileFormat, mdbFile, autoSync, null, null);
}

/**
* Create a new Database for the given fileFormat
* @param fileFormat version of new database.
* @param mdbFile Location to write the new database to. <b>If this file
* already exists, it will be overwritten.</b>
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing.
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
*/
public static Database create(FileFormat fileFormat, File mdbFile,
boolean autoSync, Charset charset,
TimeZone timeZone)
throws IOException
{
if (fileFormat.getJetFormat().READ_ONLY) {
throw new IOException("jet format '" + fileFormat.getJetFormat() + "' does not support writing");
}

FileChannel channel = openChannel(mdbFile, false);
channel.truncate(0);
channel.transferFrom(Channels.newChannel(
Thread.currentThread().getContextClassLoader().getResourceAsStream(
fileFormat._emptyFile)), 0, Integer.MAX_VALUE);
return new Database(channel, autoSync, fileFormat);
return new Database(channel, autoSync, fileFormat, charset, timeZone);
}

/**
@@ -471,17 +561,30 @@ public class Database
* @param channel File channel of the database. This needs to be a
* FileChannel instead of a ReadableByteChannel because we need to
* randomly jump around to various points in the file.
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing.
* @param fileFormat version of new database (if known)
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
*/
protected Database(FileChannel channel, boolean autoSync,
FileFormat fileFormat)
protected Database(FileChannel channel, boolean autoSync,
FileFormat fileFormat, Charset charset, TimeZone timeZone)
throws IOException
{
boolean success = false;
try {

_format = JetFormat.getFormat(channel);
_charset = ((charset == null) ? getDefaultCharset(_format) : charset);
_fileFormat = fileFormat;
_pageChannel = new PageChannel(channel, _format, autoSync);
_timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone);
// note, it's slighly sketchy to pass ourselves along partially
// constructed, but only our _format and _pageChannel refs should be
// needed
@@ -556,6 +659,44 @@ public class Database
_dbErrorHandler = newErrorHandler;
}

/**
* Gets currently configured TimeZone (always non-{@code null}).
*/
public TimeZone getTimeZone()
{
return _timeZone;
}

/**
* Sets a new TimeZone. If {@code null}, resets to the value returned by
* {@link #getDefaultTimeZone}.
*/
public void setTimeZone(TimeZone newTimeZone) {
if(newTimeZone == null) {
newTimeZone = getDefaultTimeZone();
}
_timeZone = newTimeZone;
}

/**
* Gets currently configured Charset (always non-{@code null}).
*/
public Charset getCharset()
{
return _charset;
}

/**
* Sets a new Charset. If {@code null}, resets to the value returned by
* {@link #getDefaultCharset}.
*/
public void setCharset(Charset newCharset) {
if(newCharset == null) {
newCharset = getDefaultCharset(getFormat());
}
_charset = newCharset;
}

/**
* Returns the FileFormat of this database (which may involve inspecting the
* database itself).
@@ -747,7 +888,7 @@ public class Database
//Write the tdef page to disk.
int tdefPageNumber = Table.writeTableDefinition(columns, _pageChannel,
_format);
_format, getCharset());
//Add this table to our internal list.
addTable(name, Integer.valueOf(tdefPageNumber));
@@ -1255,6 +1396,47 @@ public class Database
}
return true;
}

/**
* Returns the default TimeZone. This is normally the platform default
* TimeZone as returned by {@link TimeZone#getDefault}, but can be
* overridden using the system property {@value #TIMEZONE_PROPERTY}.
*/
public static TimeZone getDefaultTimeZone()
{
String tzProp = System.getProperty(TIMEZONE_PROPERTY);
if(tzProp != null) {
tzProp = tzProp.trim();
if(tzProp.length() > 0) {
return TimeZone.getTimeZone(tzProp);
}
}

// use system default
return TimeZone.getDefault();
}
/**
* Returns the default Charset for the given JetFormat. This may or may not
* be platform specific, depending on the format, but can be overridden
* using a system property composed of the prefix
* {@value #CHARSET_PROPERTY_PREFIX} followed by the JetFormat version to
* which the charset should apply, e.g. {@code
* "com.healthmarketscience.jackcess.charset.VERSION_3"}.
*/
public static Charset getDefaultCharset(JetFormat format)
{
String csProp = System.getProperty(CHARSET_PROPERTY_PREFIX + format);
if(csProp != null) {
csProp = csProp.trim();
if(csProp.length() > 0) {
return Charset.forName(csProp);
}
}

// use format default
return format.CHARSET;
}
/**
* Utility class for storing table page number and actual name.

+ 2
- 2
src/java/com/healthmarketscience/jackcess/Index.java Ver fichero

@@ -366,9 +366,9 @@ public abstract class Index implements Comparable<Index> {
umapRowNum, false);

_rootPageNumber = tableBuffer.getInt();
tableBuffer.getInt(); //Forward past Unknown
ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX_FLAGS); //Forward past Unknown
_indexFlags = tableBuffer.get();
ByteUtil.forward(tableBuffer, 5); //Forward past other stuff
ByteUtil.forward(tableBuffer, getFormat().SKIP_AFTER_INDEX_FLAGS); //Forward past other stuff
}

/**

+ 234
- 4
src/java/com/healthmarketscience/jackcess/JetFormat.java Ver fichero

@@ -70,6 +70,9 @@ public abstract class JetFormat {

// use nested inner class to avoid problematic static init loops
private static final class PossibleFileFormats {
private static final Map<Database.FileFormat,byte[]> POSSIBLE_VERSION_3 =
Collections.singletonMap(Database.FileFormat.V1997, (byte[])null);

private static final Map<Database.FileFormat,byte[]> POSSIBLE_VERSION_4 =
new EnumMap<Database.FileFormat,byte[]>(Database.FileFormat.class);

@@ -89,6 +92,9 @@ public abstract class JetFormat {
/** the name of this format */
private final String _name;
/** the read/write mode of this format */
public final boolean READ_ONLY;
/** Database page size in bytes */
public final int PAGE_SIZE;
public final long MAX_DATABASE_SIZE;
@@ -120,6 +126,7 @@ public abstract class JetFormat {
public final int OFFSET_COLUMN_LENGTH;
public final int OFFSET_COLUMN_VARIABLE_TABLE_INDEX;
public final int OFFSET_COLUMN_FIXED_DATA_OFFSET;
public final int OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
public final int OFFSET_TABLE_DEF_LOCATION;
@@ -150,6 +157,14 @@ public abstract class JetFormat {
public final int SIZE_TDEF_TRAILER;
public final int SIZE_COLUMN_DEF_BLOCK;
public final int SIZE_INDEX_ENTRY_MASK;
public final int SKIP_BEFORE_INDEX_FLAGS;
public final int SKIP_AFTER_INDEX_FLAGS;
public final int SKIP_BEFORE_INDEX_SLOT;
public final int SKIP_AFTER_INDEX_SLOT;
public final int SKIP_BEFORE_INDEX;
public final int SIZE_NAME_LENGTH;
public final int SIZE_ROW_COLUMN_COUNT;
public final int SIZE_ROW_VAR_COL_OFFSET;
public final int USAGE_MAP_TABLE_BYTE_LENGTH;

@@ -162,6 +177,7 @@ public abstract class JetFormat {
public final Charset CHARSET;
public static final JetFormat VERSION_3 = new Jet3Format();
public static final JetFormat VERSION_4 = new Jet4Format();
public static final JetFormat VERSION_5 = new Jet5Format();

@@ -178,19 +194,23 @@ public abstract class JetFormat {
}
buffer.flip();
byte version = buffer.get();
if (version == CODE_VERSION_4) {
if (version == CODE_VERSION_3) {
return VERSION_3;
} else if (version == CODE_VERSION_4) {
return VERSION_4;
} else if (version == CODE_VERSION_5) {
return VERSION_5;
}
throw new IOException("Unsupported " +
((version < CODE_VERSION_4) ? "older" : "newer") +
((version < CODE_VERSION_3) ? "older" : "newer") +
" version: " + version);
}
private JetFormat(String name) {
_name = name;
READ_ONLY = defineReadOnly();
PAGE_SIZE = definePageSize();
MAX_DATABASE_SIZE = defineMaxDatabaseSize();
@@ -221,6 +241,7 @@ public abstract class JetFormat {
OFFSET_COLUMN_LENGTH = defineOffsetColumnLength();
OFFSET_COLUMN_VARIABLE_TABLE_INDEX = defineOffsetColumnVariableTableIndex();
OFFSET_COLUMN_FIXED_DATA_OFFSET = defineOffsetColumnFixedDataOffset();
OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET = defineOffsetColumnFixedDataRowOffset();
OFFSET_TABLE_DEF_LOCATION = defineOffsetTableDefLocation();
@@ -251,7 +272,15 @@ public abstract class JetFormat {
SIZE_TDEF_TRAILER = defineSizeTdefTrailer();
SIZE_COLUMN_DEF_BLOCK = defineSizeColumnDefBlock();
SIZE_INDEX_ENTRY_MASK = defineSizeIndexEntryMask();
SKIP_BEFORE_INDEX_FLAGS = defineSkipBeforeIndexFlags();
SKIP_AFTER_INDEX_FLAGS = defineSkipAfterIndexFlags();
SKIP_BEFORE_INDEX_SLOT = defineSkipBeforeIndexSlot();
SKIP_AFTER_INDEX_SLOT = defineSkipAfterIndexSlot();
SKIP_BEFORE_INDEX = defineSkipBeforeIndex();
SIZE_NAME_LENGTH = defineSizeNameLength();
SIZE_ROW_COLUMN_COUNT = defineSizeRowColumnCount();
SIZE_ROW_VAR_COL_OFFSET = defineSizeRowVarColOffset();

USAGE_MAP_TABLE_BYTE_LENGTH = defineUsageMapTableByteLength();

MAX_COLUMNS_PER_TABLE = defineMaxColumnsPerTable();
@@ -264,6 +293,8 @@ public abstract class JetFormat {
CHARSET = defineCharset();
}
protected abstract boolean defineReadOnly();
protected abstract int definePageSize();
protected abstract long defineMaxDatabaseSize();
@@ -294,6 +325,7 @@ public abstract class JetFormat {
protected abstract int defineOffsetColumnLength();
protected abstract int defineOffsetColumnVariableTableIndex();
protected abstract int defineOffsetColumnFixedDataOffset();
protected abstract int defineOffsetColumnFixedDataRowOffset();
protected abstract int defineOffsetTableDefLocation();
@@ -324,7 +356,15 @@ public abstract class JetFormat {
protected abstract int defineSizeTdefTrailer();
protected abstract int defineSizeColumnDefBlock();
protected abstract int defineSizeIndexEntryMask();
protected abstract int defineSkipBeforeIndexFlags();
protected abstract int defineSkipAfterIndexFlags();
protected abstract int defineSkipBeforeIndexSlot();
protected abstract int defineSkipAfterIndexSlot();
protected abstract int defineSkipBeforeIndex();
protected abstract int defineSizeNameLength();
protected abstract int defineSizeRowColumnCount();
protected abstract int defineSizeRowVarColOffset();

protected abstract int defineUsageMapTableByteLength();

protected abstract int defineMaxColumnsPerTable();
@@ -343,6 +383,175 @@ public abstract class JetFormat {
return _name;
}
private static class Jet3Format extends JetFormat {

private Jet3Format() {
super("VERSION_3");
}

@Override
protected boolean defineReadOnly() { return true; }
@Override
protected int definePageSize() { return 2048; }
@Override
protected long defineMaxDatabaseSize() {
return (1L * 1024L * 1024L * 1024L);
}
@Override
protected int defineMaxRowSize() { return 2012; }
@Override
protected int definePageInitialFreeSpace() { return PAGE_SIZE - 14; }
@Override
protected int defineOffsetNextTableDefPage() { return 4; }
@Override
protected int defineOffsetNumRows() { return 12; }
@Override
protected int defineOffsetNextAutoNumber() { return 20; }
@Override
protected int defineOffsetTableType() { return 20; }
@Override
protected int defineOffsetMaxCols() { return 21; }
@Override
protected int defineOffsetNumVarCols() { return 23; }
@Override
protected int defineOffsetNumCols() { return 25; }
@Override
protected int defineOffsetNumIndexSlots() { return 27; }
@Override
protected int defineOffsetNumIndexes() { return 31; }
@Override
protected int defineOffsetOwnedPages() { return 35; }
@Override
protected int defineOffsetFreeSpacePages() { return 39; }
@Override
protected int defineOffsetIndexDefBlock() { return 43; }

@Override
protected int defineOffsetIndexNumberBlock() { return 39; }
@Override
protected int defineOffsetColumnType() { return 0; }
@Override
protected int defineOffsetColumnNumber() { return 1; }
@Override
protected int defineOffsetColumnPrecision() { return 11; }
@Override
protected int defineOffsetColumnScale() { return 12; }
@Override
protected int defineOffsetColumnFlags() { return 13; }
@Override
protected int defineOffsetColumnCompressedUnicode() { return 16; }
@Override
protected int defineOffsetColumnLength() { return 16; }
@Override
protected int defineOffsetColumnVariableTableIndex() { return 3; }
@Override
protected int defineOffsetColumnFixedDataOffset() { return 14; }
@Override
protected int defineOffsetColumnFixedDataRowOffset() { return 1; }
@Override
protected int defineOffsetTableDefLocation() { return 4; }
@Override
protected int defineOffsetRowStart() { return 10; }
@Override
protected int defineOffsetUsageMapStart() { return 5; }
@Override
protected int defineOffsetUsageMapPageData() { return 4; }
@Override
protected int defineOffsetReferenceMapPageNumbers() { return 1; }
@Override
protected int defineOffsetFreeSpace() { return 2; }
@Override
protected int defineOffsetNumRowsOnDataPage() { return 8; }
@Override
protected int defineMaxNumRowsOnDataPage() { return 255; }
@Override
protected int defineOffsetIndexCompressedByteCount() { return 20; }
@Override
protected int defineOffsetIndexEntryMask() { return 22; }
@Override
protected int defineOffsetPrevIndexPage() { return 8; }
@Override
protected int defineOffsetNextIndexPage() { return 12; }
@Override
protected int defineOffsetChildTailIndexPage() { return 16; }
@Override
protected int defineSizeIndexDefinition() { return 8; }
@Override
protected int defineSizeColumnHeader() { return 18; }
@Override
protected int defineSizeRowLocation() { return 2; }
@Override
protected int defineSizeLongValueDef() { return 12; }
@Override
protected int defineMaxInlineLongValueSize() { return 64; }
@Override
protected int defineMaxLongValueRowSize() { return 2032; }
@Override
protected int defineSizeTdefHeader() { return 63; }
@Override
protected int defineSizeTdefTrailer() { return 2; }
@Override
protected int defineSizeColumnDefBlock() { return 25; }
@Override
protected int defineSizeIndexEntryMask() { return 226; }
@Override
protected int defineSkipBeforeIndexFlags() { return 0; }
@Override
protected int defineSkipAfterIndexFlags() { return 0; }
@Override
protected int defineSkipBeforeIndexSlot() { return 0; }
@Override
protected int defineSkipAfterIndexSlot() { return 0; }
@Override
protected int defineSkipBeforeIndex() { return 0; }
@Override
protected int defineSizeNameLength() { return 1; }
@Override
protected int defineSizeRowColumnCount() { return 1; }
@Override
protected int defineSizeRowVarColOffset() { return 1; }
@Override
protected int defineUsageMapTableByteLength() { return 128; }
@Override
protected int defineMaxColumnsPerTable() { return 255; }
@Override
protected int defineMaxTableNameLength() { return 64; }
@Override
protected int defineMaxColumnNameLength() { return 64; }
@Override
protected int defineMaxIndexNameLength() { return 64; }
@Override
protected boolean defineReverseFirstByteInDescNumericIndexes() { return false; }

@Override
protected Charset defineCharset() { return Charset.defaultCharset(); }

@Override
protected Map<Database.FileFormat,byte[]> getPossibleFileFormats()
{
return PossibleFileFormats.POSSIBLE_VERSION_3;
}

}
private static class Jet4Format extends JetFormat {

private Jet4Format() {
@@ -353,6 +562,9 @@ public abstract class JetFormat {
super(name);
}

@Override
protected boolean defineReadOnly() { return false; }
@Override
protected int definePageSize() { return 4096; }
@@ -412,6 +624,8 @@ public abstract class JetFormat {
protected int defineOffsetColumnVariableTableIndex() { return 7; }
@Override
protected int defineOffsetColumnFixedDataOffset() { return 21; }
@Override
protected int defineOffsetColumnFixedDataRowOffset() { return 2; }
@Override
protected int defineOffsetTableDefLocation() { return 4; }
@@ -465,6 +679,22 @@ public abstract class JetFormat {
protected int defineSizeColumnDefBlock() { return 25; }
@Override
protected int defineSizeIndexEntryMask() { return 453; }
@Override
protected int defineSkipBeforeIndexFlags() { return 4; }
@Override
protected int defineSkipAfterIndexFlags() { return 5; }
@Override
protected int defineSkipBeforeIndexSlot() { return 4; }
@Override
protected int defineSkipAfterIndexSlot() { return 4; }
@Override
protected int defineSkipBeforeIndex() { return 4; }
@Override
protected int defineSizeNameLength() { return 2; }
@Override
protected int defineSizeRowColumnCount() { return 2; }
@Override
protected int defineSizeRowVarColOffset() { return 2; }
@Override
protected int defineUsageMapTableByteLength() { return 64; }

+ 117
- 32
src/java/com/healthmarketscience/jackcess/Table.java Ver fichero

@@ -29,6 +29,7 @@ package com.healthmarketscience.jackcess;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -61,6 +62,8 @@ public class Table
private static final short OVERFLOW_ROW_MASK = (short)0x4000;

private static final int MAX_BYTE = 256;

/** Table type code for system tables */
public static final byte TYPE_SYSTEM = 0x53;
/** Table type code for user tables */
@@ -403,7 +406,7 @@ public class Table
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
requireNonDeletedRow(rowState, rowId);
return getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column,
return getRowColumn(getFormat(), rowBuffer, getRowNullMask(rowBuffer), column,
rowState);
}

@@ -421,7 +424,7 @@ public class Table
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
requireNonDeletedRow(rowState, rowId);

return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
return getRow(getFormat(), rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
columnNames);
}

@@ -430,6 +433,7 @@ public class Table
* Saves parsed row values to the given rowState.
*/
private static Map<String, Object> getRow(
JetFormat format,
RowState rowState,
ByteBuffer rowBuffer,
NullMask nullMask,
@@ -444,7 +448,7 @@ public class Table
if((columnNames == null) || (columnNames.contains(column.getName()))) {
// Add the value to the row data
rtn.put(column.getName(),
getRowColumn(rowBuffer, nullMask, column, rowState));
getRowColumn(format, rowBuffer, nullMask, column, rowState));
}
}
return rtn;
@@ -454,7 +458,8 @@ public class Table
* Reads the column data from the given row buffer. Leaves limit unchanged.
* Caches the returned value in the rowState.
*/
private static Object getRowColumn(ByteBuffer rowBuffer,
private static Object getRowColumn(JetFormat format,
ByteBuffer rowBuffer,
NullMask nullMask,
Column column,
RowState rowState)
@@ -484,19 +489,34 @@ public class Table
if(!column.isVariableLength()) {

// read fixed length value (non-boolean at this point)
int dataStart = rowStart + 2;
int dataStart = rowStart + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
colDataPos = dataStart + column.getFixedDataOffset();
colDataLen = column.getType().getFixedSize(column.getLength());
} else {
int varDataStart;
int varDataEnd;

if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {

// read simple var length value
int varColumnOffsetPos =
(rowBuffer.limit() - nullMask.byteSize() - 4) -
(column.getVarLenTableIndex() * 2);

varDataStart = rowBuffer.getShort(varColumnOffsetPos);
varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);

} else {

// read var length value
int varColumnOffsetPos =
(rowBuffer.limit() - nullMask.byteSize() - 4) -
(column.getVarLenTableIndex() * 2);
// read jump-table based var length values
short[] varColumnOffsets = readJumpTableVarColOffsets(
rowState, rowBuffer, rowStart, nullMask);

varDataStart = varColumnOffsets[column.getVarLenTableIndex()];
varDataEnd = varColumnOffsets[column.getVarLenTableIndex() + 1];
}

short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
colDataPos = rowStart + varDataStart;
colDataLen = varDataEnd - varDataStart;
}
@@ -523,16 +543,61 @@ public class Table
}
}

static short[] readJumpTableVarColOffsets(
RowState rowState, ByteBuffer rowBuffer, int rowStart,
NullMask nullMask)
{
short[] varColOffsets = rowState.getVarColOffsets();
if(varColOffsets != null) {
return varColOffsets;
}

// calculate offsets using jump-table info
int nullMaskSize = nullMask.byteSize();
int rowEnd = rowStart + rowBuffer.remaining() - 1;
int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
rowEnd - nullMaskSize);
varColOffsets = new short[numVarCols + 1];
int rowLen = rowEnd - rowStart + 1;
int numJumps = (rowLen - 1) / MAX_BYTE;
int colOffset = rowEnd - nullMaskSize - numJumps - 1;
// If last jump is a dummy value, ignore it
if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
numJumps--;
}

int jumpsUsed = 0;
for(int i = 0; i < numVarCols + 1; i++) {

if((jumpsUsed < numJumps) &&
(i == ByteUtil.getUnsignedByte(
rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
jumpsUsed++;
}
varColOffsets[i] = (short)
(ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
+ (jumpsUsed * MAX_BYTE));
}
rowState.setVarColOffsets(varColOffsets);
return varColOffsets;
}

/**
* Reads the null mask from the given row buffer. Leaves limit unchanged.
*/
private static NullMask getRowNullMask(ByteBuffer rowBuffer)
private NullMask getRowNullMask(ByteBuffer rowBuffer)
throws IOException
{
// reset position to row start
rowBuffer.reset();
short columnCount = rowBuffer.getShort(); // Number of columns in this row

// Number of columns in this row
int columnCount = ByteUtil.getUnsignedVarInt(
rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
// read null mask
NullMask nullMask = new NullMask(columnCount);
@@ -685,7 +750,8 @@ public class Table
* @return the first page of the new table's definition
*/
public static int writeTableDefinition(
List<Column> columns, PageChannel pageChannel, JetFormat format)
List<Column> columns, PageChannel pageChannel, JetFormat format,
Charset charset)
throws IOException
{
// first, create the usage map page
@@ -710,7 +776,7 @@ public class Table
format.PAGE_SIZE));
writeTableDefinitionHeader(buffer, columns, usageMapPageNumber,
totalTableDefSize, format);
writeColumnDefinitions(buffer, columns, format);
writeColumnDefinitions(buffer, columns, format, charset);
//End of tabledef
buffer.put((byte) 0xff);
@@ -834,7 +900,8 @@ public class Table
* @param columns List of Columns to write definitions for
*/
private static void writeColumnDefinitions(
ByteBuffer buffer, List<Column> columns, JetFormat format)
ByteBuffer buffer, List<Column> columns, JetFormat format,
Charset charset)
throws IOException
{
short columnNumber = (short) 0;
@@ -896,7 +963,7 @@ public class Table
}
}
for (Column col : columns) {
writeName(buffer, col.getName(), format);
writeName(buffer, col.getName(), charset);
}
}

@@ -905,10 +972,9 @@ public class Table
* {@link #readName}.
*/
private static void writeName(ByteBuffer buffer, String name,
JetFormat format)
Charset charset)
{
ByteBuffer encName = Column.encodeUncompressedText(
name, format);
ByteBuffer encName = Column.encodeUncompressedText(name, charset);
buffer.putShort((short) encName.remaining());
buffer.put(encName);
}
@@ -1041,12 +1107,12 @@ public class Table
for (int i = 0; i < _indexSlotCount; i++) {

tableBuffer.getInt(); //Forward past Unknown
ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown
tableBuffer.getInt(); //Forward past alternate index number
int indexNumber = tableBuffer.getInt();
ByteUtil.forward(tableBuffer, 11);
byte indexType = tableBuffer.get();
ByteUtil.forward(tableBuffer, 4);
ByteUtil.forward(tableBuffer, getFormat().SKIP_AFTER_INDEX_SLOT); //Skip past Unknown

if(i < firstRealIdx) {
// ignore this info
@@ -1077,7 +1143,7 @@ public class Table
// go back to index column info after sorting
tableBuffer.position(idxOffset);
for (int i = 0; i < _indexCount; i++) {
tableBuffer.getInt(); //Forward past Unknown
ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX); //Forward past Unknown
_indexes.get(i).read(tableBuffer, _columns);
}

@@ -1115,15 +1181,16 @@ public class Table
}

/**
* Returns a name read from the buffer at the current position. The
* expected name format is the name length as a short followed by (length *
* 2) bytes encoded using the {@link JetFormat#CHARSET}
* Returns a name read from the buffer at the current position. The
* expected name format is the name length followed by the name
* encoded using the {@link JetFormat#CHARSET}
*/
private String readName(ByteBuffer buffer) {
int nameLength = ByteUtil.getUnsignedShort(buffer);
private String readName(ByteBuffer buffer) {
int nameLength = readNameLength(buffer);
byte[] nameBytes = new byte[nameLength];
buffer.get(nameBytes);
return Column.decodeUncompressedText(nameBytes, getFormat());
return Column.decodeUncompressedText(nameBytes,
getDatabase().getCharset());
}
/**
@@ -1131,10 +1198,17 @@ public class Table
* expected name format is the same as that for {@link #readName}.
*/
private void skipName(ByteBuffer buffer) {
int nameLength = ByteUtil.getUnsignedShort(buffer);
int nameLength = readNameLength(buffer);
ByteUtil.forward(buffer, nameLength);
}
/**
* Returns a name length read from the buffer at the current position.
*/
private int readNameLength(ByteBuffer buffer) {
return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
}
/**
* Converts a map of columnName -> columnValue to an array of row values
* appropriate for a call to {@link #addRow(Object...)}.
@@ -1307,7 +1381,7 @@ public class Table
for(Column column : _columns) {
if(column.isAutoNumber() ||
(row[column.getColumnIndex()] == Column.KEEP_VALUE)) {
row[column.getColumnIndex()] = getRowColumn(rowBuffer, nullMask,
row[column.getColumnIndex()] = getRowColumn(getFormat(), rowBuffer, nullMask,
column, rowState);
}
}
@@ -1929,6 +2003,8 @@ public class Table
private int _lastModCount;
/** optional error handler to use when row errors are encountered */
private ErrorHandler _errorHandler;
/** cached variable column offsets for jump-table based rows */
private short[] _varColOffsets;
private RowState(TempBufferHolder.Type headerType) {
_headerRowBufferH = TempPageHolder.newHolder(headerType);
@@ -1955,6 +2031,7 @@ public class Table
_rowsOnHeaderPage = 0;
_status = RowStateStatus.INIT;
_rowStatus = RowStatus.INIT;
_varColOffsets = null;
if(_haveRowValues) {
Arrays.fill(_rowValues, null);
_haveRowValues = false;
@@ -2036,6 +2113,14 @@ public class Table
public Object[] getRowValues() {
return dupeRow(_rowValues, _rowValues.length);
}

private short[] getVarColOffsets() {
return _varColOffsets;
}

private void setVarColOffsets(short[] varColOffsets) {
_varColOffsets = varColOffsets;
}
public RowId getHeaderRowId() {
return _headerRowId;

BIN
test/data/V1997/compIndexTestV1997.mdb Ver fichero


BIN
test/data/V1997/delColTestV1997.mdb Ver fichero


BIN
test/data/V1997/delTestV1997.mdb Ver fichero


BIN
test/data/V1997/test2V1997.mdb Ver fichero


+ 1
- 1
test/src/java/com/healthmarketscience/jackcess/BigIndexTest.java Ver fichero

@@ -67,7 +67,7 @@ public class BigIndexTest extends TestCase {
public void testComplexIndex() throws Exception
{
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMP_INDEX)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMP_INDEX, true)) {
// this file has an index with "compressed" entries and node pages
Database db = open(testDB);
Table t = db.getTable("Table1");

+ 7
- 7
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java Ver fichero

@@ -176,7 +176,7 @@ public class DatabaseTest extends TestCase {
}
public void testReadDeletedRows() throws Exception {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.DEL)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.DEL, true)) {
Table table = open(testDB).getTable("Table");
int rows = 0;
while (table.getNextRow() != null) {
@@ -187,7 +187,7 @@ public class DatabaseTest extends TestCase {
}
public void testGetColumns() throws Exception {
for (final TestDB testDB : SUPPORTED_DBS_TEST) {
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {

List<Column> columns = open(testDB).getTable("Table1").getColumns();
assertEquals(9, columns.size());
@@ -213,7 +213,7 @@ public class DatabaseTest extends TestCase {
}
public void testGetNextRow() throws Exception {
for (final TestDB testDB : SUPPORTED_DBS_TEST) {
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {
final Database db = open(testDB);
assertEquals(4, db.getTableNames().size());
final Table table = db.getTable("Table1");
@@ -272,7 +272,7 @@ public class DatabaseTest extends TestCase {
assertEquals(Boolean.FALSE, row.get("I"));
}

public void testCreate() throws Exception {
public void testCreate() throws Exception {
for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
Database db = create(fileFormat);
assertEquals(0, db.getTableNames().size());
@@ -396,7 +396,7 @@ public class DatabaseTest extends TestCase {

public void testReadLongValue() throws Exception {

for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.TEST2)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.TEST2, true)) {
Database db = open(testDB);
Table table = db.getTable("MSP_PROJECTS");
Map<String, Object> row = table.getNextRow();
@@ -528,7 +528,7 @@ public class DatabaseTest extends TestCase {
}

public void testReadWithDeletedCols() throws Exception {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.DEL_COL)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.DEL_COL, true)) {
Table table = open(testDB).getTable("Table1");

Map<String, Object> expectedRow0 = new LinkedHashMap<String, Object>();
@@ -733,7 +733,7 @@ public class DatabaseTest extends TestCase {

public void testMultiPageTableDef() throws Exception
{
for (final TestDB testDB : SUPPORTED_DBS_TEST) {
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {
List<Column> columns = open(testDB).getTable("Table2").getColumns();
assertEquals(89, columns.size());
}

+ 2
- 2
test/src/java/com/healthmarketscience/jackcess/IndexTest.java Ver fichero

@@ -84,7 +84,7 @@ public class IndexTest extends TestCase {
}

public void testPrimaryKey() throws Exception {
for (final TestDB testDB : SUPPORTED_DBS_TEST) {
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {
Table table = open(testDB).getTable("Table1");
Map<String, Boolean> foundPKs = new HashMap<String, Boolean>();
for(Index index : table.getIndexes()) {
@@ -100,7 +100,7 @@ public class IndexTest extends TestCase {
public void testIndexSlots() throws Exception
{
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
Database mdb = open(testDB);

Table table = mdb.getTable("Table1");

+ 98
- 50
test/src/java/com/healthmarketscience/jackcess/JetFormatTest.java Ver fichero

@@ -4,7 +4,6 @@ import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@@ -12,6 +11,7 @@ import java.util.Set;
import junit.framework.TestCase;

import static com.healthmarketscience.jackcess.Database.*;
import static com.healthmarketscience.jackcess.DatabaseTest.*;

/**
* @author Dan Rollo
@@ -28,21 +28,20 @@ public class JetFormatTest extends TestCase {
public static enum Basename {

BIG_INDEX("bigIndexTest"),
COMP_INDEX("compIndexTest"),
DEL_COL("delColTest"),
DEL("delTest"),
FIXED_NUMERIC("fixedNumericTest"),
FIXED_TEXT("fixedTextTest"),
INDEX_CURSOR("indexCursorTest"),
INDEX("indexTest"),
OVERFLOW("overflowTest"),
QUERY("queryTest"),
TEST("test"),
TEST2("test2"),
INDEX_CODES("testIndexCodes"),
INDEX_PROPERTIES("testIndexProperties"),
PROMOTION("testPromotion"),
;
COMP_INDEX("compIndexTest"),
DEL_COL("delColTest"),
DEL("delTest"),
FIXED_NUMERIC("fixedNumericTest"),
FIXED_TEXT("fixedTextTest"),
INDEX_CURSOR("indexCursorTest"),
INDEX("indexTest"),
OVERFLOW("overflowTest"),
QUERY("queryTest"),
TEST("test"),
TEST2("test2"),
INDEX_CODES("testIndexCodes"),
INDEX_PROPERTIES("testIndexProperties"),
PROMOTION("testPromotion");

private final String _basename;

@@ -58,6 +57,7 @@ public class JetFormatTest extends TestCase {
runtime via the system property
"com.healthmarketscience.jackcess.testFormats") */
final static FileFormat[] SUPPORTED_FILEFORMATS;
final static FileFormat[] SUPPORTED_FILEFORMATS_FOR_READ;

static {
String testFormatStr = System.getProperty("com.healthmarketscience.jackcess.testFormats");
@@ -70,15 +70,21 @@ public class JetFormatTest extends TestCase {
}

List<FileFormat> supported = new ArrayList<FileFormat>();
for(FileFormat ff : Arrays.asList(FileFormat.V2000, FileFormat.V2003,
FileFormat.V2007)) {
List<FileFormat> supportedForRead = new ArrayList<FileFormat>();
for(FileFormat ff : FileFormat.values()) {
if(!testFormats.contains(ff)) {
continue;
}
supportedForRead.add(ff);
if(ff.getJetFormat().READ_ONLY) {
continue;
}
supported.add(ff);
}

SUPPORTED_FILEFORMATS = supported.toArray(new FileFormat[0]);
SUPPORTED_FILEFORMATS_FOR_READ =
supportedForRead.toArray(new FileFormat[0]);
}

/**
@@ -113,12 +119,36 @@ public class JetFormatTest extends TestCase {
}

public static List<TestDB> getSupportedForBasename(Basename basename) {
return getSupportedForBasename(basename, false);
}

public static List<TestDB> getSupportedForBasename(Basename basename,
boolean readOnly) {

List<TestDB> supportedTestDBs = new ArrayList<TestDB>();
for (FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
supportedTestDBs.add(new TestDB(
getFileForBasename(basename, fileFormat),
fileFormat));
for (FileFormat fileFormat :
(readOnly ? SUPPORTED_FILEFORMATS_FOR_READ :
SUPPORTED_FILEFORMATS)) {
File testFile = getFileForBasename(basename, fileFormat);
if(!testFile.exists()) {
continue;
}
// verify that the db is the file format expected
try {
// System.out.println("FOO checking " + testFile);
Database db = Database.open(testFile, true);
FileFormat dbFileFormat = db.getFileFormat();
db.close();
if(dbFileFormat != fileFormat) {
throw new IllegalStateException("Expected " + fileFormat +
" was " + dbFileFormat);
}
} catch(Exception e) {
throw new RuntimeException(e);
}

supportedTestDBs.add(new TestDB(testFile, fileFormat));
}
return supportedTestDBs;
}
@@ -133,12 +163,10 @@ public class JetFormatTest extends TestCase {
}
}

private static final File UNSUPPORTED_TEST_V1997 =
new File(DIR_TEST_DATA, "V1997" + File.separator +
Basename.TEST + "V1997.mdb");

static final List<TestDB> SUPPORTED_DBS_TEST =
TestDB.getSupportedForBasename(Basename.TEST);
static final List<TestDB> SUPPORTED_DBS_TEST_FOR_READ =
TestDB.getSupportedForBasename(Basename.TEST, true);


public void testGetFormat() throws Exception {
@@ -149,41 +177,61 @@ public class JetFormatTest extends TestCase {
// success
}

checkUnsupportedJetFormat(UNSUPPORTED_TEST_V1997);
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {

final FileChannel channel = Database.openChannel(testDB.dbFile, false);
try {

JetFormat fmtActual = JetFormat.getFormat(channel);
assertEquals("Unexpected JetFormat for dbFile: " +
testDB.dbFile.getAbsolutePath(),
testDB.expectedFileFormat.getJetFormat(), fmtActual);

} finally {
channel.close();
}

for (final TestDB testDB : SUPPORTED_DBS_TEST) {
checkJetFormat(testDB);
}
}

private static void checkJetFormat(final TestDB testDB)
throws IOException {
public void testReadOnlyFormat() throws Exception {

final FileChannel channel = Database.openChannel(testDB.dbFile, false);
try {
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {

Database db = null;
IOException failure = null;
try {
db = openCopy(testDB);
} catch(IOException e) {
failure = e;
} finally {
if(db != null) {
db.close();
}
}

JetFormat fmtActual = JetFormat.getFormat(channel);
assertEquals("Unexpected JetFormat for dbFile: " +
testDB.dbFile.getAbsolutePath(),
testDB.expectedFileFormat.getJetFormat(), fmtActual);
if(!testDB.getExpectedFormat().READ_ONLY) {
assertNull(failure);
} else {
assertTrue(failure.getMessage().contains("does not support writing"));
}

} finally {
channel.close();
}
}

private static void checkUnsupportedJetFormat(File testDB)
throws IOException {
public void testFileFormat() throws Exception {

final FileChannel channel = Database.openChannel(testDB, false);
try {
JetFormat.getFormat(channel);
fail("Unexpected JetFormat for dbFile: " +
testDB.getAbsolutePath());
} catch(IOException ignored) {
// success
} finally {
channel.close();
for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {

Database db = null;
try {
db = open(testDB);
assertEquals(testDB.getExpectedFileFormat(), db.getFileFormat());
} finally {
if(db != null) {
db.close();
}
}
}
}


+ 1
- 1
test/src/java/com/healthmarketscience/jackcess/RelationshipTest.java Ver fichero

@@ -45,7 +45,7 @@ public class RelationshipTest extends TestCase {
}

public void testSimple() throws Exception {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX)) {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
Database db = open(testDB);
Table t1 = db.getTable("Table1");
Table t2 = db.getTable("Table2");

+ 10
- 0
test/src/java/com/healthmarketscience/jackcess/TableTest.java Ver fichero

@@ -29,9 +29,11 @@ package com.healthmarketscience.jackcess;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;

import junit.framework.TestCase;

@@ -173,6 +175,14 @@ public class TableTest extends TestCase {
public Table getTable() {
return _testTable;
}
@Override
protected Charset getCharset() {
return getFormat().CHARSET;
}
@Override
protected TimeZone getTimeZone() {
return TimeZone.getDefault();
}
};
}

Cargando…
Cancelar
Guardar