]> source.dussan.org Git - jackcess.git/commitdiff
more fixes for index writing
authorJames Ahlborn <jtahlborn@yahoo.com>
Sat, 8 Mar 2008 05:00:43 +0000 (05:00 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sat, 8 Mar 2008 05:00:43 +0000 (05:00 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@253 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Index.java

index 19127875c34aeeaa62142a3b1dd5c1c0d3976405..c262b84ba6734d9979ade9f97fabad525269770b 100644 (file)
@@ -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<Column> {
     
       // 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<Column> {
   {
     // 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<Column> {
   {
     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<Column> {
       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<Column> {
     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());
+    }
+  }
+  
 }
index eb57194b19029c04397dd667c6ed7351cb9bf97b..a4fc26be4ccb74597f68f0dd9e886c025191e291 100644 (file)
@@ -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<Index> {
     }
   }
 
-  /**
-   * 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<Index> {
     // 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<Index> {
   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<Index> {
         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<Index> {
       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);
     }    
   }