aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/changes/changes.xml5
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java81
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java13
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java10
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java2
8 files changed, 91 insertions, 32 deletions
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.
</action>
+ <action dev="jahlborn" type="fix" system="SourceForge2" issue="105">
+ Add support for reading and writing calculated column values.
+ Jackcess will not evaluate the actual expressions, but the column
+ values can be written directly.
+ </action>
</release>
<release version="2.0.4" date="2014-04-05">
<action dev="jahlborn" type="add">
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,6 +189,9 @@ class CalculatedColumnUtil
}
}
+ /**
+ * Calculated TEXT column implementation.
+ */
private static class CalcTextColImpl extends TextColumnImpl
{
CalcTextColImpl(InitArgs args) throws IOException {
@@ -170,6 +199,13 @@ class CalculatedColumnUtil
}
@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,11 +215,14 @@ 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 {
@@ -191,6 +230,13 @@ class CalculatedColumnUtil
}
@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<ColumnImpl> {
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<ColumnImpl> {
}
// 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<ColumnImpl> {
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<ColumnImpl> {
{
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<ColumnImpl> {
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<PropertyMapImpl>
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 {