From acc18a5b2de91ee966e3a288d0b1914bd7cb324a Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 8 Mar 2008 05:00:43 +0000 Subject: [PATCH] more fixes for index writing git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@253 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../healthmarketscience/jackcess/Column.java | 54 +++++- .../healthmarketscience/jackcess/Index.java | 178 ++++++++++++------ 2 files changed, 165 insertions(+), 67 deletions(-) diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index 1912787..c262b84 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -28,6 +28,7 @@ King of Prussia, PA 19406 package com.healthmarketscience.jackcess; import java.io.IOException; +import java.io.ObjectStreamException; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -613,12 +614,14 @@ public class Column implements Comparable { // convert to unscaled BigInteger, big-endian bytes byte[] intValBytes = decVal.unscaledValue().toByteArray(); - if(intValBytes.length > 16) { + int maxByteLen = getType().getFixedSize() - 1; + if(intValBytes.length > maxByteLen) { throw new IOException("Too many bytes for valid BigInteger?"); } - if(intValBytes.length < 16) { - byte[] tmpBytes = new byte[16]; - System.arraycopy(intValBytes, 0, tmpBytes, (16 - intValBytes.length), + if(intValBytes.length < maxByteLen) { + byte[] tmpBytes = new byte[maxByteLen]; + System.arraycopy(intValBytes, 0, tmpBytes, + (maxByteLen - intValBytes.length), intValBytes.length); intValBytes = tmpBytes; } @@ -639,10 +642,12 @@ public class Column implements Comparable { { // seems access stores dates in the local timezone. guess you just hope // you read it in the same timezone in which it was written! - long time = (long)(buffer.getDouble() * MILLISECONDS_PER_DAY); + long dateBits = buffer.getLong(); + long time = (long)(Double.longBitsToDouble(dateBits) + * MILLISECONDS_PER_DAY); time -= MILLIS_BETWEEN_EPOCH_AND_1900; time -= getTimeZoneOffset(time); - return new Date(time); + return new DateExt(time, dateBits); } /** @@ -652,7 +657,14 @@ public class Column implements Comparable { { if(value == null) { buffer.putDouble(0d); + } if(value instanceof DateExt) { + + // this is a Date value previously read from readDateValue(). use the + // original bits to store the value so we don't lose any precision + buffer.putLong(((DateExt)value).getDateBits()); + } else { + // seems access stores dates in the local timezone. guess you just // hope you read it in the same timezone in which it was written! long time = ((value instanceof Date) ? @@ -883,7 +895,8 @@ public class Column implements Comparable { switch(getType()) { case NUMERIC: // don't ask me why numerics are "var length" columns... - ByteBuffer buffer = getPageChannel().createBuffer(getLength(), order); + ByteBuffer buffer = getPageChannel().createBuffer( + getType().getFixedSize(), order); writeNumericValue(buffer, obj); buffer.flip(); return buffer; @@ -1230,4 +1243,31 @@ public class Column implements Comparable { return obj; } + /** + * Date subclass which stashes the original date bits, in case we attempt to + * re-write the value (will not lose precision). + */ + private static final class DateExt extends Date + { + private static final long serialVersionUID = 0L; + + /** cached bits of the original date value */ + private transient final long _dateBits; + + private DateExt(long time, long dateBits) { + super(time); + _dateBits = dateBits; + } + + public long getDateBits() { + return _dateBits; + } + + private Object writeReplace() throws ObjectStreamException { + // if we are going to serialize this Date, convert it back to a normal + // Date (in case it is restored outside of the context of jackcess) + return new Date(super.getTime()); + } + } + } diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index eb57194..a4fc26b 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -45,6 +45,7 @@ import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.healthmarketscience.jackcess.Index.ColumnDescriptor; import static com.healthmarketscience.jackcess.IndexCodes.*; @@ -778,41 +779,6 @@ public class Index implements Comparable { } } - /** - * Determines if the given column is a text based column. - */ - private static boolean isTextColumn(Column col) { - return((col.getType() == DataType.TEXT) || - (col.getType() == DataType.MEMO)); - } - - /** - * Determines if the given column is a boolean column. - */ - private static boolean isBooleanColumn(Column col) { - return(col.getType() == DataType.BOOLEAN); - } - - /** - * Determines if the given column is a integer based column. - */ - private static boolean isIntegerColumn(Column col) { - return((col.getType() == DataType.BYTE) || - (col.getType() == DataType.INT) || - (col.getType() == DataType.LONG)); - } - - /** - * Determines if the given column is a floating point based column. - */ - private static boolean isFloatingPointColumn(Column col) { - return((col.getType() == DataType.NUMERIC) || - (col.getType() == DataType.MONEY) || - (col.getType() == DataType.FLOAT) || - (col.getType() == DataType.DOUBLE) || - (col.getType() == DataType.SHORT_DATE_TIME)); - } - /** * Flips the first bit in the byte at the given index. */ @@ -842,15 +808,6 @@ public class Index implements Comparable { // always write in big endian order return column.write(value, 0, ByteOrder.BIG_ENDIAN).array(); } - - /** - * Updates the given array as appropriate for the given index order and - * returns it. - */ - private static byte[] handleOrder(byte[] value, boolean isAscending) { - // descending order is achieved by negating all the bits - return (isAscending ? value : flipBytes(value)); - } /** * Converts an index value for a text column into the entry value (which @@ -1010,18 +967,30 @@ public class Index implements Comparable { private ColumnDescriptor newColumnDescriptor(Column col, byte flags) throws IOException { - if(isTextColumn(col)) { + switch(col.getType()) { + case TEXT: + case MEMO: return new TextColumnDescriptor(col, flags); - } else if(isIntegerColumn(col)) { + case INT: + case LONG: + case MONEY: return new IntegerColumnDescriptor(col, flags); - } else if(isFloatingPointColumn(col)) { + case FLOAT: + case DOUBLE: + case SHORT_DATE_TIME: return new FloatingPointColumnDescriptor(col, flags); - } else if(isBooleanColumn(col)) { + case NUMERIC: + return new FixedPointColumnDescriptor(col, flags); + case BYTE: + return new ByteColumnDescriptor(col, flags); + case BOOLEAN: return new BooleanColumnDescriptor(col, flags); + + default: + // FIXME we can't modify this index at this point in time + _readOnly = true; + return new ReadOnlyColumnDescriptor(col, flags); } - // FIXME we can't modify this index at this point in time - _readOnly = true; - return new ReadOnlyColumnDescriptor(col, flags); } @@ -1106,11 +1075,18 @@ public class Index implements Comparable { Object value, ByteArrayOutputStream bout) throws IOException { - bout.write( - handleOrder( - flipFirstBitInByte( - encodeNumberColumnValue(value, getColumn()), 0), - isAscending())); + byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); + + // bit twiddling rules: + // - isAsc => flipFirstBit + // - !isAsc => flipFirstBit, flipBytes + + flipFirstBitInByte(valueBytes, 0); + if(!isAscending()) { + flipBytes(valueBytes); + } + + bout.write(valueBytes); } } @@ -1132,12 +1108,94 @@ public class Index implements Comparable { throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - // if the number is negative, the first bit is set. in this case, we - // flip all the bits - if((valueBytes[0] & 0x80) != 0) { + + // determine if the number is negative by testing if the first bit is + // set + boolean isNegative = ((valueBytes[0] & 0x80) != 0); + + // bit twiddling rules: + // isAsc && !isNeg => flipFirstBit + // isAsc && isNeg => flipBytes + // !isAsc && !isNeg => flipFirstBit, flipBytes + // !isAsc && isNeg => nothing + + if(!isNegative) { + flipFirstBitInByte(valueBytes, 0); + } + if(isNegative == isAscending()) { flipBytes(valueBytes); } - bout.write(handleOrder(valueBytes, isAscending())); + + bout.write(valueBytes); + } + } + + /** + * ColumnDescriptor for fixed point based columns. + */ + private static final class FixedPointColumnDescriptor + extends ColumnDescriptor + { + private FixedPointColumnDescriptor(Column column, byte flags) + throws IOException + { + super(column, flags); + } + + @Override + protected void writeNonNullValue( + Object value, ByteArrayOutputStream bout) + throws IOException + { + byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); + + // determine if the number is negative by testing if the first bit is + // set + boolean isNegative = ((valueBytes[0] & 0x80) != 0); + + // bit twiddling rules: + // isAsc && !isNeg => setReverseSignByte + // isAsc && isNeg => flipBytes, setReverseSignByte + // !isAsc && !isNeg => flipBytes, setReverseSignByte + // !isAsc && isNeg => setReverseSignByte + + if(isNegative == isAscending()) { + flipBytes(valueBytes); + } + + // reverse the sign byte (after any previous byte flipping) + valueBytes[0] = (isNegative ? (byte)0x00 : (byte)0xFF); + + bout.write(valueBytes); + } + } + + /** + * ColumnDescriptor for byte based columns. + */ + private static final class ByteColumnDescriptor extends ColumnDescriptor + { + private ByteColumnDescriptor(Column column, byte flags) + throws IOException + { + super(column, flags); + } + + @Override + protected void writeNonNullValue( + Object value, ByteArrayOutputStream bout) + throws IOException + { + byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); + + // bit twiddling rules: + // - isAsc => nothing + // - !isAsc => flipBytes + if(!isAscending()) { + flipBytes(valueBytes); + } + + bout.write(valueBytes); } } -- 2.39.5