From f73e6c33c75c8a0ba444ff200fdc234b59e59027 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Thu, 20 Mar 2014 04:17:37 +0000 Subject: [PATCH] add initial ColumnValidator support git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@849 f203690c-595d-4dc9-a70b-905162fa7fd2 --- src/changes/changes.xml | 6 + .../jackcess/Database.java | 16 ++- .../healthmarketscience/jackcess/Table.java | 6 + .../jackcess/impl/ColumnImpl.java | 24 ++++ .../jackcess/impl/DatabaseImpl.java | 15 +++ .../jackcess/impl/TableImpl.java | 113 +++++++++++------- .../jackcess/util/ColumnValidator.java | 41 +++++++ .../jackcess/util/ColumnValidatorFactory.java | 38 ++++++ .../jackcess/util/SimpleColumnValidator.java | 40 +++++++ .../util/SimpleColumnValidatorFactory.java | 39 ++++++ .../jackcess/TableTest.java | 35 +++--- 11 files changed, 312 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java create mode 100644 src/main/java/com/healthmarketscience/jackcess/util/ColumnValidatorFactory.java create mode 100644 src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java create mode 100644 src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 87e24c9..42ee0f1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -4,6 +4,12 @@ Tim McCune + + + Add ColumnValidator interface which allows column values to be easily + manipulated/validated as they are written into the database. + + Fix NullPointerException in RowImpl.toString() when value is null. diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java index ab4b7ca..4e9d136 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Database.java +++ b/src/main/java/com/healthmarketscience/jackcess/Database.java @@ -33,6 +33,7 @@ import java.util.TimeZone; import com.healthmarketscience.jackcess.query.Query; import com.healthmarketscience.jackcess.impl.DatabaseImpl; +import com.healthmarketscience.jackcess.util.ColumnValidatorFactory; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.LinkResolver; @@ -366,7 +367,7 @@ public interface Database extends Iterable, Closeable, Flushable public void setColumnOrder(Table.ColumnOrder newColumnOrder); /** - * Gets currently foreign-key enforcement policy. + * Gets current foreign-key enforcement policy. * @usage _intermediate_method_ */ public boolean isEnforceForeignKeys(); @@ -378,6 +379,19 @@ public interface Database extends Iterable
, Closeable, Flushable */ public void setEnforceForeignKeys(Boolean newEnforceForeignKeys); + /** + * Gets currently configured ColumnValidatorFactory (always non-{@code null}). + * @usage _intermediate_method_ + */ + public ColumnValidatorFactory getColumnValidatorFactory(); + + /** + * Sets a new ColumnValidatorFactory. If {@code null}, resets to the + * default value. + * @usage _intermediate_method_ + */ + public void setColumnValidatorFactory(ColumnValidatorFactory newFactory); + /** * Returns the FileFormat of this database (which may involve inspecting the * database itself). diff --git a/src/main/java/com/healthmarketscience/jackcess/Table.java b/src/main/java/com/healthmarketscience/jackcess/Table.java index 0765e1f..16b559f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Table.java +++ b/src/main/java/com/healthmarketscience/jackcess/Table.java @@ -70,6 +70,12 @@ public interface Table extends Iterable */ public boolean isHidden(); + /** + * Whether or not this table is a system (internal) table. + * @usage _general_method_ + */ + public boolean isSystem(); + /** * @usage _general_method_ */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index ae68a17..b2a1ffc 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -65,6 +65,8 @@ import com.healthmarketscience.jackcess.impl.scsu.Compress; import com.healthmarketscience.jackcess.impl.scsu.EndOfInputException; import com.healthmarketscience.jackcess.impl.scsu.Expand; import com.healthmarketscience.jackcess.impl.scsu.IllegalInputException; +import com.healthmarketscience.jackcess.util.ColumnValidator; +import com.healthmarketscience.jackcess.util.SimpleColumnValidator; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -221,6 +223,8 @@ public class ColumnImpl implements Column, Comparable { private PropertyMap _props; /** Holds additional info for writing long values */ private LongValueBufferHolder _lvalBufferH; + /** Validator for writing new values */ + private ColumnValidator _validator = SimpleColumnValidator.INSTANCE; /** * @usage _advanced_method_ @@ -509,6 +513,18 @@ public class ColumnImpl implements Column, Comparable { public ComplexColumnInfo getComplexInfo() { return _complexInfo; } + + public ColumnValidator getColumnValidator() { + return _validator; + } + + public void setColumnValidator(ColumnValidator newValidator) { + if(newValidator == null) { + newValidator = getDatabase().getColumnValidatorFactory() + .createValidator(this); + } + _validator = newValidator; + } private void setUnknownDataType(byte type) { // slight hack, stash the original type in the _scale @@ -1197,6 +1213,14 @@ public class ColumnImpl implements Column, Comparable { lvalPage.putShort((short)0); // num rows in page } + /** + * Passes the given obj through the currently configured validator for this + * column and returns the result. + */ + public Object validate(Object obj) throws IOException { + return _validator.validate(this, obj); + } + /** * Serialize an Object into a raw byte value for this column in little * endian order diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index 810868c..8ddc4a0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -72,8 +72,10 @@ import com.healthmarketscience.jackcess.Table; import com.healthmarketscience.jackcess.impl.query.QueryImpl; import com.healthmarketscience.jackcess.query.Query; import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher; +import com.healthmarketscience.jackcess.util.ColumnValidatorFactory; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.LinkResolver; +import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -303,6 +305,8 @@ public class DatabaseImpl implements Database private Table.ColumnOrder _columnOrder; /** whether or not enforcement of foreign-keys is enabled */ private boolean _enforceForeignKeys; + /** factory for ColumnValidators */ + private ColumnValidatorFactory _validatorFactory = SimpleColumnValidatorFactory.INSTANCE; /** cache of in-use tables */ private final TableCache _tableCache = new TableCache(); /** handler for reading/writing properteies */ @@ -638,6 +642,17 @@ public class DatabaseImpl implements Database _enforceForeignKeys = newEnforceForeignKeys; } + public ColumnValidatorFactory getColumnValidatorFactory() { + return _validatorFactory; + } + + public void setColumnValidatorFactory(ColumnValidatorFactory newFactory) { + if(newFactory == null) { + newFactory = SimpleColumnValidatorFactory.INSTANCE; + } + _validatorFactory = newFactory; + } + /** * @usage _advanced_method_ */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 15dfaec..f52bb1d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -303,6 +303,11 @@ public class TableImpl implements Table } _fkEnforcer = new FKEnforcer(this); + + // after fully constructed, allow column validator to be configured + for(ColumnImpl col : _columns) { + col.setColumnValidator(null); + } } public String getName() { @@ -313,6 +318,10 @@ public class TableImpl implements Table return((_flags & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0); } + public boolean isSystem() { + return(_tableType != TYPE_USER); + } + /** * @usage _advanced_method_ */ @@ -1528,8 +1537,35 @@ public class TableImpl implements Table dupeRows.set(i, row); } - // fill in autonumbers - handleAutoNumbersForAdd(row); + // handle various value massaging activities + Object complexAutoNumber = null; + for(ColumnImpl column : _columns) { + + Object rowValue = null; + if(column.isAutoNumber()) { + + // fill in autonumbers, ignore given row value, use next + // autonumber + ColumnImpl.AutoNumberGenerator autoNumGen = + column.getAutoNumberGenerator(); + if(autoNumGen.getType() != DataType.COMPLEX_TYPE) { + rowValue = autoNumGen.getNext(null); + } else { + // complex type auto numbers are shared across all complex + // columns in the row + complexAutoNumber = autoNumGen.getNext(complexAutoNumber); + rowValue = complexAutoNumber; + } + + } else { + + // pass input value through column validator + rowValue = column.validate(column.getRowValue(row)); + } + + column.setRowValue(row, rowValue); + } + ++autoNumAssignCount; // write the row of data to a temporary buffer @@ -1732,20 +1768,38 @@ public class TableImpl implements Table Map keepRawVarValues = (!_varColumns.isEmpty() ? new HashMap() : null); + // handle various value massaging activities for(ColumnImpl column : _columns) { - if(_autoNumColumns.contains(column)) { + + Object rowValue = null; + if(column.isAutoNumber()) { + // fill in any auto-numbers (we don't allow autonumber values to be // modified) - column.setRowValue(row, getRowColumn(getFormat(), rowBuffer, column, - rowState, null)); - } else if(column.getRowValue(row) == Column.KEEP_VALUE) { - // fill in any "keep value" fields - column.setRowValue(row, getRowColumn(getFormat(), rowBuffer, column, - rowState, keepRawVarValues)); - } else if(_indexColumns.contains(column)) { - // read row value to help update indexes - getRowColumn(getFormat(), rowBuffer, column, rowState, null); + rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState, null); + + } else { + + rowValue = column.getRowValue(row); + if(rowValue == Column.KEEP_VALUE) { + + // fill in any "keep value" fields (restore old value) + rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState, + keepRawVarValues); + + } else { + + if(_indexColumns.contains(column)) { + // read (old) row value to help update indexes + getRowColumn(getFormat(), rowBuffer, column, rowState, null); + } + + // pass input value through column validator + rowValue = column.validate(rowValue); + } } + + column.setRowValue(row, rowValue); } // generate new row bytes @@ -1969,10 +2023,7 @@ public class TableImpl implements Table return dataPage; } - /** - * @usage _advanced_method_ - */ - public ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer) + protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer) throws IOException { return createRow(rowArray, buffer, 0, @@ -2132,33 +2183,6 @@ public class TableImpl implements Table return buffer; } - /** - * Fill in all autonumber column values. - */ - private void handleAutoNumbersForAdd(Object[] row) - throws IOException - { - if(_autoNumColumns.isEmpty()) { - return; - } - - Object complexAutoNumber = null; - for(ColumnImpl col : _autoNumColumns) { - // ignore given row value, use next autonumber - ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator(); - Object rowValue = null; - if(autoNumGen.getType() != DataType.COMPLEX_TYPE) { - rowValue = autoNumGen.getNext(null); - } else { - // complex type auto numbers are shared across all complex columns - // in the row - complexAutoNumber = autoNumGen.getNext(complexAutoNumber); - rowValue = complexAutoNumber; - } - col.setRowValue(row, rowValue); - } - } - /** * Restores all autonumber column values from a failed add row. */ @@ -2224,8 +2248,7 @@ public class TableImpl implements Table @Override public String toString() { return CustomToStringStyle.builder(this) - .append("type", (_tableType + - ((_tableType == TYPE_USER) ? " (USER)" : " (SYSTEM)"))) + .append("type", (_tableType + (!isSystem() ? " (USER)" : " (SYSTEM)"))) .append("name", _name) .append("rowCount", _rowCount) .append("columnCount", _columns.size()) diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java new file mode 100644 index 0000000..50c8916 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java @@ -0,0 +1,41 @@ +/* +Copyright (c) 2014 James Ahlborn + +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 + +*/ + +package com.healthmarketscience.jackcess.util; + +import java.io.IOException; + +import com.healthmarketscience.jackcess.Column; + +/** + * Interface which allows for data manipulation/validation as values are being + * inserted into a database. + * + * @author James Ahlborn + */ +public interface ColumnValidator +{ + /** + * Validates and/or manipulates the given potential new value for the given + * column. This method may return an entirely different value or throw an + * exception if the input value is not valid. + */ + public Object validate(Column col, Object val) throws IOException; +} diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidatorFactory.java b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidatorFactory.java new file mode 100644 index 0000000..3a8a323 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidatorFactory.java @@ -0,0 +1,38 @@ +/* +Copyright (c) 2014 James Ahlborn + +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 + +*/ + +package com.healthmarketscience.jackcess.util; + +import com.healthmarketscience.jackcess.Column; + +/** + * Factory which generates appropriate ColumnValidators when Column instances + * are created. + * + * @author James Ahlborn + */ +public interface ColumnValidatorFactory +{ + /** + * Returns a ColumnValidator instance for the given column, must be + * non-{@code null}. + */ + public ColumnValidator createValidator(Column col); +} diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java new file mode 100644 index 0000000..7dd8817 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java @@ -0,0 +1,40 @@ +/* +Copyright (c) 2014 James Ahlborn + +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 + +*/ + +package com.healthmarketscience.jackcess.util; + +import java.io.IOException; + +import com.healthmarketscience.jackcess.Column; + +/** + * Simple concrete implementation of ColumnValidator which simply returns the + * given value. + * + * @author James Ahlborn + */ +public class SimpleColumnValidator implements ColumnValidator +{ + public static final SimpleColumnValidator INSTANCE = new SimpleColumnValidator(); + + public Object validate(Column col, Object val) throws IOException { + return val; + } +} diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java new file mode 100644 index 0000000..367f8c7 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java @@ -0,0 +1,39 @@ +/* +Copyright (c) 2014 James Ahlborn + +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 + +*/ + +package com.healthmarketscience.jackcess.util; + +import com.healthmarketscience.jackcess.Column; + +/** + * Simple concrete implementation of ColumnValidatorFactory which returns + * {@link SimpleColumnValidator.INSTANCE} for all columns. + * + * @author James Ahlborn + */ +public class SimpleColumnValidatorFactory implements ColumnValidatorFactory +{ + public static final SimpleColumnValidatorFactory INSTANCE = + new SimpleColumnValidatorFactory(); + + public ColumnValidator createValidator(Column col) { + return SimpleColumnValidator.INSTANCE; + } +} diff --git a/src/test/java/com/healthmarketscience/jackcess/TableTest.java b/src/test/java/com/healthmarketscience/jackcess/TableTest.java index b70c045..29408ef 100644 --- a/src/test/java/com/healthmarketscience/jackcess/TableTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/TableTest.java @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; -import java.util.TimeZone; import com.healthmarketscience.jackcess.impl.ColumnImpl; import com.healthmarketscience.jackcess.impl.JetFormat; @@ -49,7 +48,7 @@ public class TableTest extends TestCase { private final PageChannel _pageChannel = new PageChannel(true) {}; private List _columns = new ArrayList(); - private TableImpl _testTable; + private TestTable _testTable; private int _varLenIdx; private int _fixedOffset; @@ -126,8 +125,7 @@ public class TableTest extends TestCase { private ByteBuffer createRow(Object... row) throws IOException { - return _testTable.createRow( - row, _testTable.getPageChannel().createPageBuffer()); + return _testTable.createRow(row); } private ByteBuffer[] encodeColumns(Object... row) @@ -162,16 +160,7 @@ public class TableTest extends TestCase { private TableImpl newTestTable() throws Exception { - _testTable = new TableImpl(true, _columns) { - @Override - public PageChannel getPageChannel() { - return _pageChannel; - } - @Override - public JetFormat getFormat() { - return JetFormat.VERSION_4; - } - }; + _testTable = new TestTable(); return _testTable; } @@ -217,5 +206,21 @@ public class TableTest extends TestCase { _columns.add(col); } - + + private class TestTable extends TableImpl { + private TestTable() throws IOException { + super(true, _columns); + } + public ByteBuffer createRow(Object... row) throws IOException { + return super.createRow(row, getPageChannel().createPageBuffer()); + } + @Override + public PageChannel getPageChannel() { + return _pageChannel; + } + @Override + public JetFormat getFormat() { + return JetFormat.VERSION_4; + } + } } -- 2.39.5