From: James Ahlborn Date: Fri, 12 Nov 2010 04:05:30 +0000 (+0000) Subject: add ability to customize column value matching in cursor findRow (fixes #3105829) X-Git-Tag: jackcess-1.2.2~6 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=daa13d12fe6e38a4ab4f42340469f42a054ba4b1;p=jackcess.git add ability to customize column value matching in cursor findRow (fixes #3105829) git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@498 f203690c-595d-4dc9-a70b-905162fa7fd2 --- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e5f4c95..1ff8609 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -23,6 +23,10 @@ Allow column order in tables to be configured. + + Add support for custom column value matching when finding rows using a + Cursor. + diff --git a/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java b/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java new file mode 100644 index 0000000..8aa3fa0 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java @@ -0,0 +1,70 @@ +/* +Copyright (c) 2010 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; + +import java.io.IOException; + +/** + * Concrete implementation of ColumnMatcher which tests textual columns + * case-insensitively ({@link DataType#TEXT} and {@link DataType#MEMO}), and + * all other columns using simple equality. + * + * @author James Ahlborn + */ +public class CaseInsensitiveColumnMatcher implements ColumnMatcher { + + public static final CaseInsensitiveColumnMatcher INSTANCE = + new CaseInsensitiveColumnMatcher(); + + + public CaseInsensitiveColumnMatcher() { + } + + public boolean matches(Table table, String columnName, Object value1, + Object value2) + { + if(!isTextual(table.getColumn(columnName))) { + // use simple equality + return SimpleColumnMatcher.INSTANCE.matches(table, columnName, + value1, value2); + } + + // convert both values to Strings and compare case-insensitively + try { + CharSequence cs1 = Column.toCharSequence(value1); + CharSequence cs2 = Column.toCharSequence(value2); + + return((cs1 == cs2) || + ((cs1 != null) && (cs2 != null) && + cs1.toString().equalsIgnoreCase(cs2.toString()))); + } catch(IOException e) { + throw new IllegalStateException("Could not read column " + columnName + + " value", e); + } + } + + private static boolean isTextual(Column col) + { + DataType type = col.getType(); + return((type == DataType.TEXT) || (type == DataType.MEMO)); + } + +} diff --git a/src/java/com/healthmarketscience/jackcess/ColumnMatcher.java b/src/java/com/healthmarketscience/jackcess/ColumnMatcher.java new file mode 100644 index 0000000..5532e7a --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/ColumnMatcher.java @@ -0,0 +1,43 @@ +/* +Copyright (c) 2010 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; + +/** + * Interface for handling comparisons between column values. + * + * @author James Ahlborn + */ +public interface ColumnMatcher +{ + + /** + * Returns {@code true} if the given value1 should be considered a match for + * the given value2 for the given column in the given table, {@code false} + * otherwise. + * + * @param table the relevant table + * @param columnName the name of the relevant column within the table + * @param value1 the first value to match (may be {@code null}) + * @param value2 the second value to match (may be {@code null}) + */ + public boolean matches(Table table, String columnName, Object value1, + Object value2); +} diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index ed08160..d783925 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -87,7 +87,8 @@ public abstract class Cursor implements Iterable> private Position _prevPos; /** the current row */ private Position _curPos; - + /** ColumnMatcher to be used when matching column values */ + private ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE; protected Cursor(Id id, Table table, Position firstPos, Position lastPos) { _id = id; @@ -320,6 +321,24 @@ public abstract class Cursor implements Iterable> _rowState.setErrorHandler(newErrorHandler); } + /** + * Returns the currently configured ColumnMatcher, always non-{@code null}. + */ + public ColumnMatcher getColumnMatcher() { + return _columnMatcher; + } + + /** + * Sets a new ColumnMatcher. If {@code null}, resets to using the + * default matcher, {@link SimpleColumnMatcher#INSTANCE}. + */ + public void setColumnMatcher(ColumnMatcher columnMatcher) { + if(columnMatcher == null) { + columnMatcher = SimpleColumnMatcher.INSTANCE; + } + _columnMatcher = columnMatcher; + } + /** * Returns the current state of the cursor which can be restored at a future * point in time by a call to {@link #restoreSavepoint}. @@ -765,7 +784,9 @@ public abstract class Cursor implements Iterable> public boolean currentRowMatches(Column columnPattern, Object valuePattern) throws IOException { - return ObjectUtils.equals(valuePattern, getCurrentRowValue(columnPattern)); + return _columnMatcher.matches(getTable(), columnPattern.getName(), + valuePattern, + getCurrentRowValue(columnPattern)); } /** @@ -776,7 +797,21 @@ public abstract class Cursor implements Iterable> public boolean currentRowMatches(Map rowPattern) throws IOException { - return ObjectUtils.equals(rowPattern, getCurrentRow(rowPattern.keySet())); + Map row = getCurrentRow(rowPattern.keySet()); + + if(rowPattern.size() != row.size()) { + return false; + } + + for(Map.Entry e : row.entrySet()) { + String columnName = e.getKey(); + if(!_columnMatcher.matches(getTable(), columnName, + rowPattern.get(columnName), e.getValue())) { + return false; + } + } + + return true; } /** diff --git a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java index 043e954..68df492 100644 --- a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java @@ -59,6 +59,8 @@ public class CursorBuilder { private boolean _beforeFirst = true; /** optional save point to restore to the cursor */ private Cursor.Savepoint _savepoint; + /** ColumnMatcher to be used when matching column values */ + private ColumnMatcher _columnMatcher; public CursorBuilder(Table table) { _table = table; @@ -238,6 +240,14 @@ public class CursorBuilder { return this; } + /** + * Sets the ColumnMatcher to use for matching row patterns. + */ + public CursorBuilder setColumnMatcher(ColumnMatcher columnMatcher) { + _columnMatcher = columnMatcher; + return this; + } + /** * Returns a new cursor for the table, constructed to the given * specifications. @@ -253,6 +263,7 @@ public class CursorBuilder { _startRow, _startRowInclusive, _endRow, _endRowInclusive); } + cursor.setColumnMatcher(_columnMatcher); if(_savepoint == null) { if(!_beforeFirst) { cursor.afterLast(); diff --git a/src/java/com/healthmarketscience/jackcess/SimpleColumnMatcher.java b/src/java/com/healthmarketscience/jackcess/SimpleColumnMatcher.java new file mode 100644 index 0000000..ff65317 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/SimpleColumnMatcher.java @@ -0,0 +1,42 @@ +/* +Copyright (c) 2010 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; + +import org.apache.commons.lang.ObjectUtils; + +/** + * Simple concrete implementation of ColumnMatcher which test for equality. + * + * @author James Ahlborn + */ +public class SimpleColumnMatcher implements ColumnMatcher { + + public static final SimpleColumnMatcher INSTANCE = new SimpleColumnMatcher(); + + public SimpleColumnMatcher() { + } + + public boolean matches(Table table, String columnName, Object value1, + Object value2) + { + return ObjectUtils.equals(value1, value2); + } +} diff --git a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java index 4fda31a..c8e5d7e 100644 --- a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java @@ -71,7 +71,9 @@ public class CursorTest extends TestCase { return expectedRows; } - private static Database createTestTable(final FileFormat fileFormat) throws Exception { + private static Database createTestTable(final FileFormat fileFormat) + throws Exception + { Database db = create(fileFormat); Table table = new TableBuilder("test") @@ -720,4 +722,103 @@ public class CursorTest extends TestCase { } } + public void testColmnMatcher() throws Exception { + + + for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) { + Database db = createTestTable(fileFormat); + + Table table = db.getTable("test"); + + doTestMatchers(table, SimpleColumnMatcher.INSTANCE, false); + doTestMatchers(table, CaseInsensitiveColumnMatcher.INSTANCE, true); + + Cursor cursor = Cursor.createCursor(table); + doTestMatcher(table, cursor, SimpleColumnMatcher.INSTANCE, false); + doTestMatcher(table, cursor, CaseInsensitiveColumnMatcher.INSTANCE, + true); + db.close(); + } + } + + private void doTestMatchers(Table table, ColumnMatcher columnMatcher, + boolean caseInsensitive) + throws Exception + { + assertTrue(columnMatcher.matches(table, "value", null, null)); + assertFalse(columnMatcher.matches(table, "value", "foo", null)); + assertFalse(columnMatcher.matches(table, "value", null, "foo")); + assertTrue(columnMatcher.matches(table, "value", "foo", "foo")); + assertTrue(columnMatcher.matches(table, "value", "foo", "Foo") + == caseInsensitive); + + assertFalse(columnMatcher.matches(table, "value", 13, null)); + assertFalse(columnMatcher.matches(table, "value", null, 13)); + assertTrue(columnMatcher.matches(table, "value", 13, 13)); + } + + private void doTestMatcher(Table table, Cursor cursor, + ColumnMatcher columnMatcher, + boolean caseInsensitive) + throws Exception + { + cursor.setColumnMatcher(columnMatcher); + + assertTrue(cursor.findRow(table.getColumn("id"), 3)); + assertEquals(createExpectedRow("id", 3, + "value", "data" + 3), + cursor.getCurrentRow()); + + assertTrue(cursor.findRow(createExpectedRow( + "id", 6, + "value", "data" + 6))); + assertEquals(createExpectedRow("id", 6, + "value", "data" + 6), + cursor.getCurrentRow()); + + assertTrue(cursor.findRow(createExpectedRow( + "id", 6, + "value", "Data" + 6)) == caseInsensitive); + if(caseInsensitive) { + assertEquals(createExpectedRow("id", 6, + "value", "data" + 6), + cursor.getCurrentRow()); + } + + assertFalse(cursor.findRow(createExpectedRow( + "id", 8, + "value", "data" + 13))); + assertFalse(cursor.findRow(table.getColumn("id"), 13)); + assertEquals(createExpectedRow("id", 6, + "value", "data" + 6), + cursor.getCurrentRow()); + + assertTrue(cursor.findRow(createExpectedRow( + "value", "data" + 7))); + assertEquals(createExpectedRow("id", 7, + "value", "data" + 7), + cursor.getCurrentRow()); + + assertTrue(cursor.findRow(createExpectedRow( + "value", "Data" + 7)) == caseInsensitive); + if(caseInsensitive) { + assertEquals(createExpectedRow("id", 7, + "value", "data" + 7), + cursor.getCurrentRow()); + } + + assertTrue(cursor.findRow(table.getColumn("value"), "data" + 4)); + assertEquals(createExpectedRow("id", 4, + "value", "data" + 4), + cursor.getCurrentRow()); + + assertTrue(cursor.findRow(table.getColumn("value"), "Data" + 4) + == caseInsensitive); + if(caseInsensitive) { + assertEquals(createExpectedRow("id", 4, + "value", "data" + 4), + cursor.getCurrentRow()); + } + } + }