]> source.dussan.org Git - jackcess.git/commitdiff
add ability to customize column value matching in cursor findRow (fixes #3105829)
authorJames Ahlborn <jtahlborn@yahoo.com>
Fri, 12 Nov 2010 04:05:30 +0000 (04:05 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Fri, 12 Nov 2010 04:05:30 +0000 (04:05 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@498 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/ColumnMatcher.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/CursorBuilder.java
src/java/com/healthmarketscience/jackcess/SimpleColumnMatcher.java [new file with mode: 0644]
test/src/java/com/healthmarketscience/jackcess/CursorTest.java

index e5f4c95c01185e04cc18d7fe18f1e06c0099cd35..1ff860932ea44246666b9cf333c3edde8054880e 100644 (file)
       <action dev="jahlborn" type="update" issue="3097387">
         Allow column order in tables to be configured.
       </action>
+      <action dev="jahlborn" type="update" issue="3105829">
+        Add support for custom column value matching when finding rows using a
+        Cursor.
+      </action>
     </release>
     <release version="1.2.1" date="2010-08-01">
       <action dev="jahlborn" type="add" issue="3005272">
diff --git a/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java b/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java
new file mode 100644 (file)
index 0000000..8aa3fa0
--- /dev/null
@@ -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 (file)
index 0000000..5532e7a
--- /dev/null
@@ -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);
+}
index ed081609ba24dad0bad9d826247d634c19c8533b..d7839258d316533040a078023bf6c3ee1e084fdf 100644 (file)
@@ -87,7 +87,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
   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<Map<String, Object>>
     _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<Map<String, Object>>
   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<Map<String, Object>>
   public boolean currentRowMatches(Map<String,Object> rowPattern)
     throws IOException
   {
-    return ObjectUtils.equals(rowPattern, getCurrentRow(rowPattern.keySet()));
+    Map<String,Object> row = getCurrentRow(rowPattern.keySet());
+
+    if(rowPattern.size() != row.size()) {
+      return false;
+    }
+
+    for(Map.Entry<String,Object> e : row.entrySet()) {
+      String columnName = e.getKey();
+      if(!_columnMatcher.matches(getTable(), columnName,
+                                 rowPattern.get(columnName), e.getValue())) {
+        return false;
+      }
+    }
+
+    return true;
   }
   
   /**
index 043e9549d49bc88655f2471226291d7a2517dde6..68df492c6d3d023d87361a364ae0cde09772815b 100644 (file)
@@ -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 (file)
index 0000000..ff65317
--- /dev/null
@@ -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);
+  }
+}
index 4fda31aa57de1fff22dd8c18929252cc0cd27a51..c8e5d7efc1f6779413c6d86bfef5f40759c1eff6 100644 (file)
@@ -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());
+    }
+  }
+
 }