Pārlūkot izejas kodu

Add ErrorHandler utility for customizing error handling during row

        parsing.

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@381 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/rel_1_1_18
James Ahlborn pirms 15 gadiem
vecāks
revīzija
57b7412966

+ 4
- 0
src/changes/changes.xml Parādīt failu

@@ -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>

+ 2
- 2
src/java/com/healthmarketscience/jackcess/Column.java Parādīt failu

@@ -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;
}
/**

+ 16
- 0
src/java/com/healthmarketscience/jackcess/Cursor.java Parādīt failu

@@ -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}.

+ 36
- 0
src/java/com/healthmarketscience/jackcess/Database.java Parādīt failu

@@ -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

+ 80
- 0
src/java/com/healthmarketscience/jackcess/DebugErrorHandler.java Parādīt failu

@@ -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);
}

}

+ 65
- 0
src/java/com/healthmarketscience/jackcess/ErrorHandler.java Parādīt failu

@@ -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;

}

+ 68
- 0
src/java/com/healthmarketscience/jackcess/ReplacementErrorHandler.java Parādīt failu

@@ -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;
}

}

+ 94
- 39
src/java/com/healthmarketscience/jackcess/Table.java Parādīt failu

@@ -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;
}
}
}

+ 7
- 1
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java Parādīt failu

@@ -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);

+ 181
- 0
test/src/java/com/healthmarketscience/jackcess/ErrorHandlerTest.java Parādīt failu

@@ -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");
}
}

}

+ 1
- 1
test/src/java/com/healthmarketscience/jackcess/TableTest.java Parādīt failu

@@ -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;

Notiek ielāde…
Atcelt
Saglabāt