]> source.dussan.org Git - jackcess.git/commitdiff
some minor cleanups for reading and writing calculated columns (issue #105)
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 8 Sep 2014 01:31:44 +0000 (01:31 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 8 Sep 2014 01:31:44 +0000 (01:31 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@869 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java
src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/TempBufferHolder.java

index 59af706453aa27528a9fe05d73beae2cc6f77136..ccc033ac52902be481a6afb2f96a1f293c7f72fc 100644 (file)
@@ -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">
index 930a0d7696a72c378cfe8ba32e9ba833a3db7de2..e548da20d47ac228dfeca6dab4fd3ab96948ad17 100644 (file)
@@ -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);
       }
     }
-
   }
 
 }
index c9c67e7e8d3eedc30e17a26b8c6877e578ac94aa..4dc1afdbc52818bad61c4ca0f8fba794c408c025 100644 (file)
@@ -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;
   }
   
index 1f89b384a9ebb91880f6076f38823c4444db3da7..b648a6a08f2c24a329ed13a6cabbd5ca00b74e4e 100644 (file)
@@ -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);
index 89e952dc6e962f3212878583c3b1d9a716eadd95..48ee23060ffb045a8fb3dbf53a8ecaf1ddecc432 100644 (file)
@@ -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);
-}
+  }
 }
index bff4472ac223d73f72139f75d4a8a035894d5a54..593798e09637a9a5c30e8828c74fe68919fe24b0 100644 (file)
@@ -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;
index 3ab142ce248b283f8c05c8c3c7cd878f87071432..a23886402e9b60ebf67369a6df824e34cfafd378 100644 (file)
@@ -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);
index 4e2b6f6ec759b547d3a02fdb1353b2afc6b9889a..ce47e944176a6398907866223059fb6fc4635d14 100644 (file)
@@ -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 {