]> source.dussan.org Git - jackcess.git/commitdiff
Add ErrorHandler utility for customizing error handling during row
authorJames Ahlborn <jtahlborn@yahoo.com>
Sat, 8 Nov 2008 01:47:49 +0000 (01:47 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sat, 8 Nov 2008 01:47:49 +0000 (01:47 +0000)
        parsing.

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@381 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/DebugErrorHandler.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/ErrorHandler.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/ReplacementErrorHandler.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/Table.java
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java
test/src/java/com/healthmarketscience/jackcess/ErrorHandlerTest.java [new file with mode: 0644]
test/src/java/com/healthmarketscience/jackcess/TableTest.java

index d04f7253b33ffa4d00cda338741304d74bc62a1e..3fc2e4bfb683c6e22f1984cac52cc1fb05af5fb9 100644 (file)
@@ -6,6 +6,10 @@
   </properties>
   <body>
     <release version="1.1.18" date="TBD">
+      <action dev="jahlborn" type="add">
+        Add ErrorHandler utility for customizing error handling during row
+        parsing.
+      </action>
       <action dev="jahlborn" type="add">
         Add RowFilter contributed by Patricia Donaldson.
       </action>
index c27a699ee39e3284c40b125cc3480973fbc703e1..437250eb505b078edfe64559a9cf472dc86b3904 100644 (file)
@@ -144,11 +144,11 @@ public class Column implements Comparable<Column> {
   /**
    * Only used by unit tests
    */
-  Column(boolean testing) {
+  Column(boolean testing, Table table) {
     if(!testing) {
       throw new IllegalArgumentException();
     }
-    _table = null;
+    _table = table;
   }
     
   /**
index 317330877247b445e84c4c4db58ff4fe4c298d48..5804eeaef975006f0da2df019e209729b6d3b978 100644 (file)
@@ -298,6 +298,22 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
     return getTable().getPageChannel();
   }
 
+  /**
+   * Gets the currently configured ErrorHandler (always non-{@code null}).
+   * This will be used to handle all errors.
+   */
+  public ErrorHandler getErrorHandler() {
+    return _rowState.getErrorHandler();
+  }
+
+  /**
+   * Sets a new ErrorHandler.  If {@code null}, resets to using the
+   * ErrorHandler configured at the Table level.
+   */
+  public void setErrorHandler(ErrorHandler newErrorHandler) {
+    _rowState.setErrorHandler(newErrorHandler);
+  }    
+
   /**
    * Returns the current state of the cursor which can be restored at a future
    * point in time by a call to {@link #restoreSavepoint}.
index df4b0c19a1cd1aed28f4885b4b213d261c355eee..75113e13df3cc5d3bcaae2b3e8965be396aed221 100644 (file)
@@ -99,6 +99,22 @@ public class Database
       default. */
   public static final String USE_BIG_INDEX_PROPERTY =
     "com.healthmarketscience.jackcess.bigIndex";
+
+  /** default error handler used if none provided (just rethrows exception) */
+  public static final ErrorHandler DEFAULT_ERROR_HANDLER = new ErrorHandler() {
+      public Object handleRowError(Column column,
+                                   byte[] columnData,
+                                   Table.RowState rowState,
+                                   Exception error)
+        throws IOException
+      {
+        // really can only be RuntimeException or IOException
+        if(error instanceof IOException) {
+          throw (IOException)error;
+        }
+        throw (RuntimeException)error;
+      }
+    };
   
   /** Batch commit size for copying other result sets into this database */
   private static final int COPY_TABLE_BATCH_SIZE = 200;
@@ -243,6 +259,8 @@ public class Database
   private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
   /** for now, "big index support" is optional */
   private boolean _useBigIndex;
+  /** optional error handler to use when row errors are encountered */
+  private ErrorHandler _dbErrorHandler;
   
   /**
    * Open an existing Database.  If the existing file is not writeable, the
@@ -387,6 +405,24 @@ public class Database
   public void setUseBigIndex(boolean useBigIndex) {
     _useBigIndex = useBigIndex;
   }
+
+  /**
+   * Gets the currently configured ErrorHandler (always non-{@code null}).
+   * This will be used to handle all errors unless overridden at the Table or
+   * Cursor level.
+   */
+  public ErrorHandler getErrorHandler() {
+    return((_dbErrorHandler != null) ? _dbErrorHandler :
+           DEFAULT_ERROR_HANDLER);
+  }
+
+  /**
+   * Sets a new ErrorHandler.  If {@code null}, resets to the
+   * {@link #DEFAULT_ERROR_HANDLER}.
+   */
+  public void setErrorHandler(ErrorHandler newErrorHandler) {
+    _dbErrorHandler = newErrorHandler;
+  }    
   
   /**
    * Read the system catalog
diff --git a/src/java/com/healthmarketscience/jackcess/DebugErrorHandler.java b/src/java/com/healthmarketscience/jackcess/DebugErrorHandler.java
new file mode 100644 (file)
index 0000000..2fbd478
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implementation of ErrorHandler which is useful for generating debug
+ * information about bad row data (great for bug reports!).  After logging a
+ * debug entry for the failed column, it will return some sort of replacement
+ * value, see {@link ReplacementErrorHandler}.
+ * 
+ * @author James Ahlborn
+ */
+public class DebugErrorHandler extends ReplacementErrorHandler
+{
+  private static final Log LOG = LogFactory.getLog(DebugErrorHandler.class); 
+
+  /**
+   * Constructs a DebugErrorHandler which replaces all errored values with
+   * {@code null}.
+   */
+  public DebugErrorHandler() {
+  }
+
+  /**
+   * Constructs a DebugErrorHandler which replaces all errored values with the
+   * given Object.
+   */
+  public DebugErrorHandler(Object replacement) {
+    super(replacement);
+  }
+
+  @Override
+  public Object handleRowError(Column column,
+                               byte[] columnData,
+                               Table.RowState rowState,
+                               Exception error)
+    throws IOException
+  {
+    if(LOG.isDebugEnabled()) {
+      LOG.debug("Failed reading column " + column + ", row " +
+                rowState + ", bytes " +
+                ((columnData != null) ?
+                 ByteUtil.toHexString(columnData) : "null"),
+                error);
+    }
+
+    return super.handleRowError(column, columnData, rowState, error);
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/ErrorHandler.java b/src/java/com/healthmarketscience/jackcess/ErrorHandler.java
new file mode 100644 (file)
index 0000000..d68c856
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+
+/**
+ * Handler for errors encountered while reading a column of row data from a
+ * Table.  An instance of this class may be configured at the Database, Table,
+ * or Cursor level to customize error handling as desired.  The default
+ * instance used is {@link Database#DEFAULT_ERROR_HANDLER}, which just
+ * rethrows any exceptions encountered.
+ * 
+ * @author James Ahlborn
+ */
+public interface ErrorHandler 
+{
+
+  /**
+   * Handles an error encountered while reading a column of data from a Table
+   * row.  Handler may either throw an exception (which will be propagated
+   * back to the caller) or return a replacement for this row's column value
+   * (in which case the row will continue to be read normally).
+   *
+   * @param column the info for the column being read
+   * @param columnData the actual column data for the column being read (which
+   *                   may be {@code null} dependening on when the exception
+   *                   was thrown during the reading process)
+   * @param rowState the current row state for the caller
+   * @param error the error that was encountered
+   *
+   * @return replacement for this row's column
+   */
+  public Object handleRowError(Column column,
+                               byte[] columnData,
+                               Table.RowState rowState,
+                               Exception error)
+    throws IOException;
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/ReplacementErrorHandler.java b/src/java/com/healthmarketscience/jackcess/ReplacementErrorHandler.java
new file mode 100644 (file)
index 0000000..bdb003c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+
+/**
+ * Simple implementation of an ErrorHandler which always returns the
+ * configured object.
+ * 
+ * @author James Ahlborn
+ */
+public class ReplacementErrorHandler implements ErrorHandler
+{
+
+  private final Object _replacement;
+
+  /**
+   * Constructs a ReplacementErrorHandler which replaces all errored values
+   * with {@code null}.
+   */
+  public ReplacementErrorHandler() {
+    this(null);
+  }
+
+  /**
+   * Constructs a ReplacementErrorHandler which replaces all errored values
+   * with the given Object.
+   */
+  public ReplacementErrorHandler(Object replacement) {
+    _replacement = replacement;
+  }
+
+  public Object handleRowError(Column column,
+                               byte[] columnData,
+                               Table.RowState rowState,
+                               Exception error)
+    throws IOException
+  {
+    return _replacement;
+  }
+
+}
index b4424dc6045fc3380ba9637d04913294e9c218c5..874f2b0df591194f3b9676a6ce085730a85f3c1f 100644 (file)
@@ -127,6 +127,8 @@ public class Table
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
   /** for now, "big index support" is optional */
   private final boolean _useBigIndex;
+  /** optional error handler to use when row errors are encountered */
+  private ErrorHandler _tableErrorHandler;
   
   /** common cursor for iterating through the table, kept here for historic
       reasons */
@@ -215,6 +217,24 @@ public class Table
     return getDatabase().getPageChannel();
   }
 
+  /**
+   * Gets the currently configured ErrorHandler (always non-{@code null}).
+   * This will be used to handle all errors unless overridden at the Cursor
+   * level.
+   */
+  public ErrorHandler getErrorHandler() {
+    return((_tableErrorHandler != null) ? _tableErrorHandler :
+           getDatabase().getErrorHandler());
+  }
+
+  /**
+   * Sets a new ErrorHandler.  If {@code null}, resets to using the
+   * ErrorHandler configured at the Database level.
+   */
+  public void setErrorHandler(ErrorHandler newErrorHandler) {
+    _tableErrorHandler = newErrorHandler;
+  }    
+
   protected int getTableDefPageNumber() {
     return _tableDefPageNumber;
   }
@@ -383,7 +403,8 @@ public class Table
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
     requireNonDeletedRow(rowState, rowId);
     
-    Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column);
+    Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column,
+                                rowState);
 
     // cache the row values in order to be able to update the index on row
     // deletion.  note, most of the returned values are immutable, except
@@ -430,7 +451,7 @@ public class Table
 
       if((columnNames == null) || (columnNames.contains(column.getName()))) {
         // Add the value to the row data
-        Object value = getRowColumn(rowBuffer, nullMask, column);
+        Object value = getRowColumn(rowBuffer, nullMask, column, rowState);
         rtn.put(column.getName(), value);
 
         // cache the row values in order to be able to update the index on row
@@ -448,51 +469,59 @@ public class Table
    */
   private static Object getRowColumn(ByteBuffer rowBuffer,
                                      NullMask nullMask,
-                                     Column column)
+                                     Column column,
+                                     RowState rowState)
     throws IOException
   {
-    boolean isNull = nullMask.isNull(column);
-    if(column.getType() == DataType.BOOLEAN) {
-      return Boolean.valueOf(!isNull);  //Boolean values are stored in the null mask
-    } else if(isNull) {
-      // well, that's easy!
-      return null;
-    }
+    byte[] columnData = null;
+    try {
+
+      boolean isNull = nullMask.isNull(column);
+      if(column.getType() == DataType.BOOLEAN) {
+        return Boolean.valueOf(!isNull);  //Boolean values are stored in the null mask
+      } else if(isNull) {
+        // well, that's easy!
+        return null;
+      }
 
-    // reset position to row start
-    rowBuffer.reset();
+      // reset position to row start
+      rowBuffer.reset();
     
-    // locate the column data bytes
-    int rowStart = rowBuffer.position();
-    int colDataPos = 0;
-    int colDataLen = 0;
-    if(!column.isVariableLength()) {
-
-      // read fixed length value (non-boolean at this point)
-      int dataStart = rowStart + 2;
-      colDataPos = dataStart + column.getFixedDataOffset();
-      colDataLen = column.getType().getFixedSize();
+      // locate the column data bytes
+      int rowStart = rowBuffer.position();
+      int colDataPos = 0;
+      int colDataLen = 0;
+      if(!column.isVariableLength()) {
+
+        // read fixed length value (non-boolean at this point)
+        int dataStart = rowStart + 2;
+        colDataPos = dataStart + column.getFixedDataOffset();
+        colDataLen = column.getType().getFixedSize();
       
-    } else {
+      } else {
 
-      // read var length value
-      int varColumnOffsetPos =
-        (rowBuffer.limit() - nullMask.byteSize() - 4) -
-        (column.getVarLenTableIndex() * 2);
+        // read var length value
+        int varColumnOffsetPos =
+          (rowBuffer.limit() - nullMask.byteSize() - 4) -
+          (column.getVarLenTableIndex() * 2);
 
-      short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
-      short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
-      colDataPos = rowStart + varDataStart;
-      colDataLen = varDataEnd - varDataStart;
-    }
+        short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
+        short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
+        colDataPos = rowStart + varDataStart;
+        colDataLen = varDataEnd - varDataStart;
+      }
 
-    // grab the column data
-    byte[] columnData = new byte[colDataLen];
-    rowBuffer.position(colDataPos);
-    rowBuffer.get(columnData);
+      // grab the column data
+      columnData = new byte[colDataLen];
+      rowBuffer.position(colDataPos);
+      rowBuffer.get(columnData);
 
-    // parse the column data
-    return column.read(columnData);
+      // parse the column data
+      return column.read(columnData);
+
+    } catch(Exception e) {
+      return rowState.handleRowError(column, columnData, e);
+    }
   }
 
   /**
@@ -1710,7 +1739,9 @@ public class Table
         rowState can detect updates to the table and re-read any buffered
         data */
     private int _lastModCount;
-    
+    /** optional error handler to use when row errors are encountered */
+    private ErrorHandler _errorHandler;
+  
     private RowState(TempBufferHolder.Type headerType) {
       _headerRowBufferH = TempPageHolder.newHolder(headerType);
       _rowValues = new Object[Table.this.getColumnCount()];
@@ -1720,6 +1751,15 @@ public class Table
     public Table getTable() {
       return Table.this;
     }
+
+    public ErrorHandler getErrorHandler() {
+      return((_errorHandler != null) ? _errorHandler :
+             getTable().getErrorHandler());
+    }
+
+    public void setErrorHandler(ErrorHandler newErrorHandler) {
+      _errorHandler = newErrorHandler;
+    }
     
     public void reset() {
       _finalRowId = null;
@@ -1875,6 +1915,21 @@ public class Table
       return _finalRowBuffer;
     }
 
+    private Object handleRowError(Column column,
+                                  byte[] columnData,
+                                  Exception error)
+      throws IOException
+    {
+      return getErrorHandler().handleRowError(column, columnData,
+                                              this, error);
+    }  
+
+    @Override
+    public String toString()
+    {
+      return "RowState: headerRowId = " + _headerRowId + ", finalRowId = " +
+        _finalRowId;
+    }
   }
   
 }
index 136780e1d4eb5637c70171b43ec3d6c593a5ab1b..aedf3e7b4e59168a95203225ca73d81da1b785a5 100644 (file)
@@ -959,10 +959,16 @@ public class DatabaseTest extends TestCase {
   }
 
   static void assertTable(List<Map<String, Object>> expectedTable, Table table)
+  {
+    assertCursor(expectedTable, Cursor.createCursor(table));
+  }
+  
+  static void assertCursor(List<Map<String, Object>> expectedTable, 
+                           Cursor cursor)
   {
     List<Map<String, Object>> foundTable =
       new ArrayList<Map<String, Object>>();
-    for(Map<String, Object> row : Cursor.createCursor(table)) {
+    for(Map<String, Object> row : cursor) {
       foundTable.add(row);
     }
     assertEquals(expectedTable, foundTable);
diff --git a/test/src/java/com/healthmarketscience/jackcess/ErrorHandlerTest.java b/test/src/java/com/healthmarketscience/jackcess/ErrorHandlerTest.java
new file mode 100644 (file)
index 0000000..8b46a8a
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+Copyright (c) 2007 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.ByteOrder;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import static com.healthmarketscience.jackcess.DatabaseTest.*;
+
+/**
+ * @author James Ahlborn
+ */
+public class ErrorHandlerTest extends TestCase 
+{
+
+  public ErrorHandlerTest(String name) {
+    super(name);
+  }
+
+  public void testErrorHandler() throws Exception
+  {
+    Database db = create();
+
+    Table table = 
+      new TableBuilder("test")
+      .addColumn(new ColumnBuilder("col", DataType.TEXT).toColumn())
+      .addColumn(new ColumnBuilder("val", DataType.LONG).toColumn())
+      .toTable(db);
+
+    table.addRow("row1", 1);
+    table.addRow("row2", 2);
+    table.addRow("row3", 3);
+
+    assertTable(createExpectedTable(
+                    createExpectedRow("col", "row1",
+                                      "val", 1),
+                    createExpectedRow("col", "row2",
+                                      "val", 2),
+                    createExpectedRow("col", "row3",
+                                      "val", 3)),
+                table);
+
+
+    replaceColumn(table, "val");
+
+    table.reset();
+    try {
+      table.getNextRow();
+      fail("IOException should have been thrown");
+    } catch(IOException e) {
+      // success
+    }
+
+    table.reset();
+    table.setErrorHandler(new ReplacementErrorHandler());
+
+    assertTable(createExpectedTable(
+                    createExpectedRow("col", "row1",
+                                      "val", null),
+                    createExpectedRow("col", "row2",
+                                      "val", null),
+                    createExpectedRow("col", "row3",
+                                      "val", null)),
+                table);
+
+    Cursor c1 = Cursor.createCursor(table);
+    Cursor c2 = Cursor.createCursor(table);
+    Cursor c3 = Cursor.createCursor(table);
+
+    c2.setErrorHandler(new DebugErrorHandler("#error"));
+    c3.setErrorHandler(Database.DEFAULT_ERROR_HANDLER);
+
+    assertCursor(createExpectedTable(
+                    createExpectedRow("col", "row1",
+                                      "val", null),
+                    createExpectedRow("col", "row2",
+                                      "val", null),
+                    createExpectedRow("col", "row3",
+                                      "val", null)),
+                c1);
+
+    assertCursor(createExpectedTable(
+                    createExpectedRow("col", "row1",
+                                      "val", "#error"),
+                    createExpectedRow("col", "row2",
+                                      "val", "#error"),
+                    createExpectedRow("col", "row3",
+                                      "val", "#error")),
+                c2);
+
+    try {
+      c3.getNextRow();
+      fail("IOException should have been thrown");
+    } catch(IOException e) {
+      // success
+    }
+
+    table.setErrorHandler(null);
+    c1.setErrorHandler(null);
+    c1.reset();
+    try {
+      c1.getNextRow();
+      fail("IOException should have been thrown");
+    } catch(IOException e) {
+      // success
+    }
+
+
+    db.close();
+  }
+
+  @SuppressWarnings("unchecked")
+  private void replaceColumn(Table t, String colName) throws Exception
+  {
+    Field colsField = Table.class.getDeclaredField("_columns");
+    colsField.setAccessible(true);
+    List<Column> cols = (List<Column>)colsField.get(t);
+
+    Column srcCol = null;
+    Column destCol = new BogusColumn(t);
+    for(int i = 0; i < cols.size(); ++i) {
+      srcCol = cols.get(i);
+      if(srcCol.getName().equals(colName)) {
+        cols.set(i, destCol);
+        break;
+      }
+    }
+
+    // copy fields from source to dest
+    for(Field f : Column.class.getDeclaredFields()) {
+      if(!Modifier.isFinal(f.getModifiers())) {
+        f.setAccessible(true);
+        f.set(destCol, f.get(srcCol));
+      }
+    }
+    
+  }
+
+  private static class BogusColumn extends Column
+  {
+    private BogusColumn(Table table) {
+      super(true, table);
+    }
+    
+    @Override
+    public Object read(byte[] data, ByteOrder order) throws IOException {
+      throw new IOException("bogus column");
+    }
+  }
+
+}
index de8de5cf1af5456a5397ebd2fbaf834c6c40f451..31908469ab0e29664561830a344a53581130b6bd 100644 (file)
@@ -168,7 +168,7 @@ public class TableTest extends TestCase {
   }
 
   private Column newTestColumn() {
-    return new Column(true) {
+    return new Column(true, null) {
         @Override
         public Table getTable() {
           return _testTable;