From: James Ahlborn Date: Mon, 8 Sep 2014 01:31:44 +0000 (+0000) Subject: some minor cleanups for reading and writing calculated columns (issue #105) X-Git-Tag: jackcess-2.0.5~10 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b9dcd48b4310add23261dc9cfe0fb2833e1c933d;p=jackcess.git some minor cleanups for reading and writing calculated columns (issue #105) git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@869 f203690c-595d-4dc9-a70b-905162fa7fd2 --- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 59af706..ccc033a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -9,6 +9,11 @@ Add Cursor.findRow(RowId) for moving to a specific Table row using only the RowId. + + Add support for reading and writing calculated column values. + Jackcess will not evaluate the actual expressions, but the column + values can be written directly. + diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java index 930a0d7..e548da2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java @@ -35,12 +35,17 @@ import java.nio.ByteOrder; */ class CalculatedColumnUtil { + // offset to the int which specifies the length of the actual data private static final int CALC_DATA_LEN_OFFSET = 16; + // offset to the actual data private static final int CALC_DATA_OFFSET = CALC_DATA_LEN_OFFSET + 4; + // total amount of extra bytes added for calculated values private static final int CALC_EXTRA_DATA_LEN = 23; + // fully encode calculated BOOLEAN "true" value private static final byte[] CALC_BOOL_TRUE = wrapCalculatedValue( new byte[]{(byte)0xFF}); + // fully encode calculated BOOLEAN "false" value private static final byte[] CALC_BOOL_FALSE = wrapCalculatedValue( new byte[]{0}); @@ -73,6 +78,9 @@ class CalculatedColumnUtil return new CalcColImpl(args); } + /** + * Grabs the real data bytes from a calculated value. + */ private static byte[] unwrapCalculatedValue(byte[] data) { if(data.length < CALC_DATA_OFFSET) { return data; @@ -86,15 +94,22 @@ class CalculatedColumnUtil return newData; } + /** + * Wraps the given data bytes with the extra calculated value data and + * returns a new ByteBuffer containing the final data. + */ private static ByteBuffer wrapCalculatedValue(ByteBuffer buffer) { - int dataLen = buffer.remaining(); - byte[] data = new byte[dataLen + CALC_EXTRA_DATA_LEN]; - buffer.get(data, CALC_DATA_OFFSET, dataLen); - buffer = PageChannel.wrap(data); - buffer.putInt(CALC_DATA_LEN_OFFSET, dataLen); - return buffer; + ByteBuffer newBuf = prepareWrappedCalcValue( + buffer.remaining(), buffer.order()); + newBuf.put(buffer); + newBuf.rewind(); + return newBuf; } + /** + * Wraps the given data bytes with the extra calculated value data and + * returns a new byte[] containing the final data. + */ private static byte[] wrapCalculatedValue(byte[] data) { int dataLen = data.length; data = ByteUtil.copyOf(data, 0, dataLen + CALC_EXTRA_DATA_LEN, @@ -102,7 +117,10 @@ class CalculatedColumnUtil PageChannel.wrap(data).putInt(CALC_DATA_LEN_OFFSET, dataLen); return data; } - + + /** + * Prepares a calculated value buffer for data of the given length. + */ private static ByteBuffer prepareWrappedCalcValue(int dataLen, ByteOrder order) { ByteBuffer buffer = ByteBuffer.allocate( @@ -113,6 +131,9 @@ class CalculatedColumnUtil } + /** + * General calculated column implementation. + */ private static class CalcColImpl extends ColumnImpl { CalcColImpl(InitArgs args) throws IOException { @@ -130,11 +151,16 @@ class CalculatedColumnUtil throws IOException { // we should only be working with fixed length types - return writeFixedLengthField( + ByteBuffer buffer = writeFixedLengthField( obj, prepareWrappedCalcValue(getType().getFixedSize(), order)); + buffer.rewind(); + return buffer; } } + /** + * Calculated BOOLEAN column implementation. + */ private static class CalcBooleanColImpl extends ColumnImpl { CalcBooleanColImpl(InitArgs args) throws IOException { @@ -163,12 +189,22 @@ class CalculatedColumnUtil } } + /** + * Calculated TEXT column implementation. + */ private static class CalcTextColImpl extends TextColumnImpl { CalcTextColImpl(InitArgs args) throws IOException { super(args); } + @Override + public short getLengthInUnits() { + // the byte "length" includes the calculated field overhead. remove + // that to get the _actual_ data length (in units) + return (short)getType().toUnitSize(getLength() - CALC_EXTRA_DATA_LEN); + } + @Override public Object read(byte[] data, ByteOrder order) throws IOException { return decodeTextValue(unwrapCalculatedValue(data)); @@ -179,17 +215,27 @@ class CalculatedColumnUtil ByteOrder order) throws IOException { - int maxChars = getType().toUnitSize(getLength() - CALC_EXTRA_DATA_LEN); - return wrapCalculatedValue(encodeTextValue(obj, 0, maxChars, false)); + return wrapCalculatedValue(super.writeRealData( + obj, remainingRowLength, order)); } } + /** + * Calculated MEMO column implementation. + */ private static class CalcMemoColImpl extends MemoColumnImpl { CalcMemoColImpl(InitArgs args) throws IOException { super(args); } + @Override + protected int getMaxLengthInUnits() { + // the byte "length" includes the calculated field overhead. remove + // that to get the _actual_ data length (in units) + return getType().toUnitSize(getType().getMaxSize() - CALC_EXTRA_DATA_LEN); + } + @Override protected byte[] readLongValue(byte[] lvalDefinition) throws IOException @@ -201,10 +247,14 @@ class CalculatedColumnUtil protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength) throws IOException { - return super.writeLongValue(wrapCalculatedValue(value), remainingRowLength); + return super.writeLongValue( + wrapCalculatedValue(value), remainingRowLength); } } + /** + * Calculated NUMERIC column implementation. + */ private static class CalcNumericColImpl extends NumericColumnImpl { CalcNumericColImpl(InitArgs args) throws IOException { @@ -233,8 +283,7 @@ class CalculatedColumnUtil writeCalcNumericValue(buffer, obj, dataLen); - buffer.flip(); - + buffer.rewind(); return buffer; } @@ -245,6 +294,7 @@ class CalculatedColumnUtil // most 16 bytes int numByteLen = ((totalLen > 0) ? totalLen : buffer.remaining()) - 2; numByteLen = Math.min((numByteLen / 4) * 4, 16); + byte scale = buffer.get(); boolean negate = (buffer.get() != 0); byte[] tmpArr = ByteUtil.getBytes(buffer, numByteLen); @@ -295,7 +345,7 @@ class CalculatedColumnUtil buffer.putShort((short)(dataLen - 2)); buffer.put((byte)scale); // write sign byte - buffer.put(signum < 0 ? (byte)0x80 : (byte)0); + buffer.put((signum < 0) ? NUMERIC_NEGATIVE_BYTE : 0); buffer.put(intValBytes); } catch(ArithmeticException e) { @@ -308,7 +358,7 @@ class CalculatedColumnUtil private static void fixNumericByteOrder(byte[] bytes) { // this is a little weird. it looks like they decided to truncate - // leading 0 bytes and _then_ swapp endian, which ends up kind of odd. + // leading 0 bytes and _then_ swap endian, which ends up kind of odd. int pos = 0; if((bytes.length % 8) != 0) { // leading 4 bytes are swapped @@ -321,7 +371,6 @@ class CalculatedColumnUtil ByteUtil.swap8Bytes(bytes, pos); } } - } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index c9c67e7..4dc1afd 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -137,6 +137,8 @@ public class ColumnImpl implements Column, Comparable { protected static final byte COMPRESSED_UNICODE_EXT_FLAG_MASK = (byte)0x01; private static final byte CALCULATED_EXT_FLAG_MASK = (byte)0xC0; + static final byte NUMERIC_NEGATIVE_BYTE = (byte)0x80; + /** the value for the "general" sort order */ private static final short GENERAL_SORT_ORDER_VALUE = 1033; @@ -730,7 +732,7 @@ public class ColumnImpl implements Column, Comparable { } // write sign byte - buffer.put(signum < 0 ? (byte)0x80 : (byte)0); + buffer.put((signum < 0) ? NUMERIC_NEGATIVE_BYTE : 0); // adjust scale according to this column type (will cause the an // ArithmeticException if number has too many decimal places) @@ -996,7 +998,7 @@ public class ColumnImpl implements Column, Comparable { switch(getType()) { case NUMERIC: // don't ask me why numerics are "var length" columns... - ByteBuffer buffer = getPageChannel().createBuffer( + ByteBuffer buffer = PageChannel.createBuffer( getType().getFixedSize(), order); writeNumericValue(buffer, obj); buffer.flip(); @@ -1032,8 +1034,10 @@ public class ColumnImpl implements Column, Comparable { { int size = getType().getFixedSize(_columnLength); - return writeFixedLengthField( - obj, getPageChannel().createBuffer(size, order)); + ByteBuffer buffer = writeFixedLengthField( + obj, PageChannel.createBuffer(size, order)); + buffer.flip(); + return buffer; } protected ByteBuffer writeFixedLengthField(Object obj, ByteBuffer buffer) @@ -1100,7 +1104,6 @@ public class ColumnImpl implements Column, Comparable { default: throw new IOException("Unsupported data type: " + getType()); } - buffer.flip(); return buffer; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java index 1f89b38..b648a6a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import com.healthmarketscience.jackcess.DataType; /** * ColumnImpl subclass which is used for long value data types. @@ -81,6 +80,10 @@ class LongValueColumnImpl extends ColumnImpl super.postTableLoadInit(); } + protected int getMaxLengthInUnits() { + return getType().toUnitSize(getType().getMaxSize()); + } + @Override public Object read(byte[] data, ByteOrder order) throws IOException { switch(getType()) { @@ -110,8 +113,7 @@ class LongValueColumnImpl extends ColumnImpl // should already be "encoded" break; case MEMO: - int maxMemoChars = DataType.MEMO.toUnitSize(DataType.MEMO.getMaxSize()); - obj = encodeTextValue(obj, 0, maxMemoChars, false).array(); + obj = encodeTextValue(obj, 0, getMaxLengthInUnits(), false).array(); break; default: throw new RuntimeException("unexpected var length, long value type: " + @@ -268,7 +270,7 @@ class LongValueColumnImpl extends ColumnImpl type = LONG_VALUE_TYPE_OTHER_PAGES; } - ByteBuffer def = getPageChannel().createBuffer(lvalDefLen); + ByteBuffer def = PageChannel.createBuffer(lvalDefLen); // take length and apply type to first byte int lengthWithFlags = value.length | (type << 24); def.putInt(lengthWithFlags); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java b/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java index 89e952d..48ee230 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java @@ -382,14 +382,14 @@ public class PageChannel implements Channel, Flushable { * @return A newly-allocated buffer of the given size and DEFAULT_BYTE_ORDER * byte order */ - public ByteBuffer createBuffer(int size) { + public static ByteBuffer createBuffer(int size) { return createBuffer(size, DEFAULT_BYTE_ORDER); } /** * @return A newly-allocated buffer of the given size and byte order */ - public ByteBuffer createBuffer(int size, ByteOrder order) { + public static ByteBuffer createBuffer(int size, ByteOrder order) { return ByteBuffer.allocate(size).order(order); } @@ -442,5 +442,5 @@ public class PageChannel implements Channel, Flushable { */ public static ByteBuffer wrap(byte[] bytes) { return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER); -} + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java index bff4472..593798e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java @@ -476,7 +476,7 @@ public class PropertyMaps implements Iterable public ByteBuffer write(Object obj, int remainingRowLength) throws IOException { - ByteBuffer buffer = getPageChannel().createBuffer(1); + ByteBuffer buffer = PageChannel.createBuffer(1); buffer.put(((Number)booleanToInteger(obj)).byteValue()); buffer.flip(); return buffer; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 3ab142c..a238864 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -995,7 +995,7 @@ public class TableImpl implements Table // now, create the table definition PageChannel pageChannel = creator.getPageChannel(); - ByteBuffer buffer = pageChannel.createBuffer(Math.max(totalTableDefSize, + ByteBuffer buffer = PageChannel.createBuffer(Math.max(totalTableDefSize, format.PAGE_SIZE)); writeTableDefinitionHeader(creator, buffer, totalTableDefSize); @@ -1288,7 +1288,7 @@ public class TableImpl implements Table } getPageChannel().readPage(nextPageBuffer, nextPage); nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE); - ByteBuffer newBuffer = getPageChannel().createBuffer( + ByteBuffer newBuffer = PageChannel.createBuffer( tableBuffer.capacity() + getFormat().PAGE_SIZE - 8); newBuffer.put(tableBuffer); newBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java b/src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java index 4e2b6f6..ce47e94 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java @@ -125,7 +125,7 @@ public abstract class TempBufferHolder { public final ByteBuffer getBuffer(PageChannel pageChannel, int size) { ByteBuffer buffer = getExistingBuffer(); if((buffer == null) || (buffer.capacity() < size)) { - buffer = pageChannel.createBuffer(size, _order); + buffer = PageChannel.createBuffer(size, _order); ++_modCount; setNewBuffer(buffer); } else {