diff options
15 files changed, 577 insertions, 115 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c9ec469..659eaef 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -4,6 +4,11 @@ <author email="javajedi@users.sf.net">Tim McCune</author> </properties> <body> + <release version="1.2.7" date="TBD"> + <action dev="jahlborn" type="update"> + Rename and refactor the Cursor find methods to improve usability. + </action> + </release> <release version="1.2.6" date="2011-12-16"> <action dev="jahlborn" type="update" issue="3358272"> Add support for reading/writing complex column data (version history, diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index 32a67ce..ce6050d 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -787,7 +787,7 @@ public class Column implements Comparable<Column> { return rowArray[_columnIndex]; } - public Object getRowValue(Map<String,Object> rowMap) { + public Object getRowValue(Map<String,?> rowMap) { return rowMap.get(_name); } diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index 623bebd..042241a 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -178,19 +178,22 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern". See {@link #findRow(Map)} for details on the + * given row "pattern". See {@link #findFirstRow(Map)} for details on the * rowPattern. + * <p> + * Warning, this method <i>always</i> starts searching from the beginning of + * the Table (you cannot use it to find successive matches). * * @param table the table to search * @param rowPattern pattern to be used to find the row * @return the matching row or {@code null} if a match could not be found. */ public static Map<String,Object> findRow(Table table, - Map<String,Object> rowPattern) + Map<String,?> rowPattern) throws IOException { Cursor cursor = createCursor(table); - if(cursor.findRow(rowPattern)) { + if(cursor.findFirstRow(rowPattern)) { return cursor.getCurrentRow(); } return null; @@ -198,8 +201,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern". See {@link #findRow(Column,Object)} for details on - * the pattern. + * given row "pattern". See {@link #findFirstRow(Column,Object)} for + * details on the pattern. * <p> * Note, a {@code null} result value is ambiguous in that it could imply no * match or a matching row with {@code null} for the desired value. If @@ -218,7 +221,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> throws IOException { Cursor cursor = createCursor(table); - if(cursor.findRow(columnPattern, valuePattern)) { + if(cursor.findFirstRow(columnPattern, valuePattern)) { return cursor.getCurrentRowValue(column); } return null; @@ -226,8 +229,11 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in an indexed table which - * matches a given row "pattern". See {@link #findRow(Map)} for details on - * the rowPattern. + * matches a given row "pattern". See {@link #findFirstRow(Map)} for + * details on the rowPattern. + * <p> + * Warning, this method <i>always</i> starts searching from the beginning of + * the Table (you cannot use it to find successive matches). * * @param table the table to search * @param index index to assist the search @@ -235,11 +241,11 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * @return the matching row or {@code null} if a match could not be found. */ public static Map<String,Object> findRow(Table table, Index index, - Map<String,Object> rowPattern) + Map<String,?> rowPattern) throws IOException { Cursor cursor = createIndexCursor(table, index); - if(cursor.findRow(rowPattern)) { + if(cursor.findFirstRow(rowPattern)) { return cursor.getCurrentRow(); } return null; @@ -247,8 +253,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern". See {@link #findRow(Column,Object)} for details on - * the pattern. + * given row "pattern". See {@link #findFirstRow(Column,Object)} for + * details on the pattern. * <p> * Note, a {@code null} result value is ambiguous in that it could imply no * match or a matching row with {@code null} for the desired value. If @@ -268,7 +274,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> throws IOException { Cursor cursor = createIndexCursor(table, index); - if(cursor.findRow(columnPattern, valuePattern)) { + if(cursor.findFirstRow(columnPattern, valuePattern)) { return cursor.getCurrentRowValue(column); } return null; @@ -519,6 +525,128 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #columnMatchIterable(Column,Object)} + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Map<String, Object>> columnMatchIterable( + Column columnPattern, Object valuePattern) + { + return columnMatchIterable(null, columnPattern, valuePattern); + } + + /** + * Calls <code>beforeFirst</code> on this cursor and returns a modifiable + * Iterator which will iterate through all the rows of this table which + * match the given column pattern. Use of the Iterator follows the same + * restrictions as a call to <code>getNextRow</code>. See + * {@link #findFirstRow(Column,Object)} for details on the columnPattern. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> columnMatchIterator( + Column columnPattern, Object valuePattern) + { + return columnMatchIterator(null, columnPattern, valuePattern); + } + + /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #columnMatchIterator(Collection,Column,Object)} + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Map<String, Object>> columnMatchIterable( + final Collection<String> columnNames, + final Column columnPattern, final Object valuePattern) + { + return new Iterable<Map<String, Object>>() { + public Iterator<Map<String, Object>> iterator() { + return Cursor.this.columnMatchIterator( + columnNames, columnPattern, valuePattern); + } + }; + } + + /** + * Calls <code>beforeFirst</code> on this table and returns a modifiable + * Iterator which will iterate through all the rows of this table which + * match the given column pattern, returning only the given columns. Use of + * the Iterator follows the same restrictions as a call to + * <code>getNextRow</code>. See {@link #findFirstRow(Column,Object)} for + * details on the columnPattern. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> columnMatchIterator( + Collection<String> columnNames, Column columnPattern, Object valuePattern) + { + return new ColumnMatchIterator(columnNames, columnPattern, valuePattern); + } + + /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #rowMatchIterator(Map)} + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Map<String, Object>> rowMatchIterable( + Map<String,?> rowPattern) + { + return rowMatchIterable(null, rowPattern); + } + + /** + * Calls <code>beforeFirst</code> on this cursor and returns a modifiable + * Iterator which will iterate through all the rows of this table which + * match the given row pattern. Use of the Iterator follows the same + * restrictions as a call to <code>getNextRow</code>. See + * {@link #findFirstRow(Map)} for details on the rowPattern. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> rowMatchIterator( + Map<String,?> rowPattern) + { + return rowMatchIterator(null, rowPattern); + } + + /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #rowMatchIterator(Collection,Map)} + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Map<String, Object>> rowMatchIterable( + final Collection<String> columnNames, + final Map<String,?> rowPattern) + { + return new Iterable<Map<String, Object>>() { + public Iterator<Map<String, Object>> iterator() { + return Cursor.this.rowMatchIterator( + columnNames, rowPattern); + } + }; + } + + /** + * Calls <code>beforeFirst</code> on this table and returns a modifiable + * Iterator which will iterate through all the rows of this table which + * match the given row pattern, returning only the given columns. Use of + * the Iterator follows the same restrictions as a call to + * <code>getNextRow</code>. See {@link #findFirstRow(Map)} for details on + * the rowPattern. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> rowMatchIterator( + Collection<String> columnNames, Map<String,?> rowPattern) + { + return new RowMatchIterator(columnNames, rowPattern); + } + + /** * Delete the current row. * @throws IllegalStateException if the current row is not valid (at * beginning or end of table), or already deleted. @@ -703,10 +831,23 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** + * @deprecated renamed to {@link #findFirstRow(Column,Object)} to be more clear + */ + @Deprecated + public boolean findRow(Column columnPattern, Object valuePattern) + throws IOException + { + return findFirstRow(columnPattern, valuePattern); + } + + /** * Moves to the first row (as defined by the cursor) where the given column * has the given value. This may be more efficient on some cursors than * others. If a match is not found (or an exception is thrown), the cursor * is restored to its previous state. + * <p> + * Warning, this method <i>always</i> starts searching from the beginning of + * the Table (you cannot use it to find successive matches). * * @param columnPattern column from the table for this cursor which is being * matched by the valuePattern @@ -715,14 +856,15 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * @return {@code true} if a valid row was found with the given value, * {@code false} if no row was found */ - public boolean findRow(Column columnPattern, Object valuePattern) + public boolean findFirstRow(Column columnPattern, Object valuePattern) throws IOException { Position curPos = _curPos; Position prevPos = _prevPos; boolean found = false; try { - found = findRowImpl(columnPattern, valuePattern); + beforeFirst(); + found = findNextRowImpl(columnPattern, valuePattern); return found; } finally { if(!found) { @@ -736,24 +878,102 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** + * Moves to the next row (as defined by the cursor) where the given column + * has the given value. This may be more efficient on some cursors than + * others. If a match is not found (or an exception is thrown), the cursor + * is restored to its previous state. + * + * @param columnPattern column from the table for this cursor which is being + * matched by the valuePattern + * @param valuePattern value which is equal to the corresponding value in + * the matched row + * @return {@code true} if a valid row was found with the given value, + * {@code false} if no row was found + */ + public boolean findNextRow(Column columnPattern, Object valuePattern) + throws IOException + { + Position curPos = _curPos; + Position prevPos = _prevPos; + boolean found = false; + try { + found = findNextRowImpl(columnPattern, valuePattern); + return found; + } finally { + if(!found) { + try { + restorePosition(curPos, prevPos); + } catch(IOException e) { + LOG.error("Failed restoring position", e); + } + } + } + } + + /** + * @deprecated renamed to {@link #findFirstRow(Map)} to be more clear + */ + @Deprecated + public boolean findRow(Map<String,?> rowPattern) + throws IOException + { + return findFirstRow(rowPattern); + } + + /** * Moves to the first row (as defined by the cursor) where the given columns * have the given values. This may be more efficient on some cursors than * others. If a match is not found (or an exception is thrown), the cursor * is restored to its previous state. + * <p> + * Warning, this method <i>always</i> starts searching from the beginning of + * the Table (you cannot use it to find successive matches). * * @param rowPattern column names and values which must be equal to the * corresponding values in the matched row * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - public boolean findRow(Map<String,Object> rowPattern) + public boolean findFirstRow(Map<String,?> rowPattern) throws IOException { Position curPos = _curPos; Position prevPos = _prevPos; boolean found = false; try { - found = findRowImpl(rowPattern); + beforeFirst(); + found = findNextRowImpl(rowPattern); + return found; + } finally { + if(!found) { + try { + restorePosition(curPos, prevPos); + } catch(IOException e) { + LOG.error("Failed restoring position", e); + } + } + } + } + + /** + * Moves to the next row (as defined by the cursor) where the given columns + * have the given values. This may be more efficient on some cursors than + * others. If a match is not found (or an exception is thrown), the cursor + * is restored to its previous state. + * + * @param rowPattern column names and values which must be equal to the + * corresponding values in the matched row + * @return {@code true} if a valid row was found with the given values, + * {@code false} if no row was found + */ + public boolean findNextRow(Map<String,?> rowPattern) + throws IOException + { + Position curPos = _curPos; + Position prevPos = _prevPos; + boolean found = false; + try { + found = findNextRowImpl(rowPattern); return found; } finally { if(!found) { @@ -786,7 +1006,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * @param rowPattern column names and values which must be equal to the * corresponding values in the current row */ - public boolean currentRowMatches(Map<String,Object> rowPattern) + public boolean currentRowMatches(Map<String,?> rowPattern) throws IOException { Map<String,Object> row = getCurrentRow(rowPattern.keySet()); @@ -807,7 +1027,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** - * Moves to the first row (as defined by the cursor) where the given column + * Moves to the next row (as defined by the cursor) where the given column * has the given value. Caller manages save/restore on failure. * <p> * Default implementation scans the table from beginning to end. @@ -819,10 +1039,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * @return {@code true} if a valid row was found with the given value, * {@code false} if no row was found */ - protected boolean findRowImpl(Column columnPattern, Object valuePattern) + protected boolean findNextRowImpl(Column columnPattern, Object valuePattern) throws IOException { - beforeFirst(); while(moveToNextRow()) { if(currentRowMatches(columnPattern, valuePattern)) { return true; @@ -832,7 +1051,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** - * Moves to the first row (as defined by the cursor) where the given columns + * Moves to the next row (as defined by the cursor) where the given columns * have the given values. Caller manages save/restore on failure. * <p> * Default implementation scans the table from beginning to end. @@ -842,10 +1061,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - protected boolean findRowImpl(Map<String,Object> rowPattern) + protected boolean findNextRowImpl(Map<String,?> rowPattern) throws IOException { - beforeFirst(); while(moveToNextRow()) { if(currentRowMatches(rowPattern)) { return true; @@ -1040,6 +1258,53 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } } + + /** + * Row iterator for this cursor, modifiable. + */ + private final class ColumnMatchIterator extends BaseIterator + { + private final Column _columnPattern; + private final Object _valuePattern; + + private ColumnMatchIterator(Collection<String> columnNames, + Column columnPattern, Object valuePattern) + { + super(columnNames); + _columnPattern = columnPattern; + _valuePattern = valuePattern; + beforeFirst(); + } + + @Override + protected boolean findNext() throws IOException { + return findNextRow(_columnPattern, _valuePattern); + } + } + + + /** + * Row iterator for this cursor, modifiable. + */ + private final class RowMatchIterator extends BaseIterator + { + private final Map<String,?> _rowPattern; + + private RowMatchIterator(Collection<String> columnNames, + Map<String,?> rowPattern) + { + super(columnNames); + _rowPattern = rowPattern; + beforeFirst(); + } + + @Override + protected boolean findNext() throws IOException { + return findNextRow(_rowPattern); + } + } + + /** * Handles moving the cursor in a given direction. Separates cursor * logic from value storage. diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index 5b5e9d5..174f95f 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -2168,7 +2168,7 @@ public class Database protected Cursor findRow(Integer parentId, String name) throws IOException { - return (_systemCatalogCursor.findRowByEntry(parentId, name) ? + return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ? _systemCatalogCursor : null); } @@ -2181,7 +2181,7 @@ public class Database .toIndexCursor(); } - return (_systemCatalogIdCursor.findRowByEntry(objectId) ? + return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ? _systemCatalogIdCursor : null); } @@ -2234,7 +2234,7 @@ public class Database Map<String,Object> rowPat = new HashMap<String,Object>(); rowPat.put(CAT_COL_PARENT_ID, parentId); rowPat.put(CAT_COL_NAME, name); - return (_systemCatalogCursor.findRow(rowPat) ? + return (_systemCatalogCursor.findFirstRow(rowPat) ? _systemCatalogCursor : null); } @@ -2242,7 +2242,7 @@ public class Database protected Cursor findRow(Integer objectId) throws IOException { Column idCol = _systemCatalog.getColumn(CAT_COL_ID); - return (_systemCatalogCursor.findRow(idCol, objectId) ? + return (_systemCatalogCursor.findFirstRow(idCol, objectId) ? _systemCatalogCursor : null); } diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 88b5237..9fc24c3 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -356,7 +356,7 @@ public class Index implements Comparable<Index> { * @return the appropriate sparse array of data or {@code null} if not all * columns for this index were provided */ - public Object[] constructIndexRow(Map<String,Object> row) + public Object[] constructIndexRow(Map<String,?> row) { return getIndexData().constructIndexRow(row); } diff --git a/src/java/com/healthmarketscience/jackcess/IndexCursor.java b/src/java/com/healthmarketscience/jackcess/IndexCursor.java index 6d02fa2..aa77b65 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexCursor.java +++ b/src/java/com/healthmarketscience/jackcess/IndexCursor.java @@ -48,7 +48,7 @@ public class IndexCursor extends Cursor new ReverseIndexDirHandler(); /** logical index which this cursor is using */ private final Index _index; - /** Cursor over the entries of the relvant index */ + /** Cursor over the entries of the relevant index */ private final IndexData.EntryCursor _entryCursor; /** column names for the index entry columns */ private Set<String> _indexEntryPattern; @@ -157,22 +157,36 @@ public class IndexCursor extends Cursor } /** + * @deprecated renamed to {@link #findFirstRowByEntry(Object...)} to be more + * clear + */ + @Deprecated + public boolean findRowByEntry(Object... entryValues) + throws IOException + { + return findFirstRowByEntry(entryValues); + } + + /** * Moves to the first row (as defined by the cursor) where the index entries * match the given values. If a match is not found (or an exception is * thrown), the cursor is restored to its previous state. + * <p> + * Warning, this method <i>always</i> starts searching from the beginning of + * the Table (you cannot use it to find successive matches). * * @param entryValues the column values for the index's columns. * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - public boolean findRowByEntry(Object... entryValues) + public boolean findFirstRowByEntry(Object... entryValues) throws IOException { Position curPos = _curPos; Position prevPos = _prevPos; boolean found = false; try { - found = findRowByEntryImpl(toRowValues(entryValues), true); + found = findFirstRowByEntryImpl(toRowValues(entryValues), true); return found; } finally { if(!found) { @@ -199,7 +213,7 @@ public class IndexCursor extends Cursor Position prevPos = _prevPos; boolean found = false; try { - findRowByEntryImpl(toRowValues(entryValues), false); + findFirstRowByEntryImpl(toRowValues(entryValues), false); found = true; } finally { if(!found) { @@ -305,15 +319,21 @@ public class IndexCursor extends Cursor } @Override - protected boolean findRowImpl(Column columnPattern, Object valuePattern) + protected boolean findNextRowImpl(Column columnPattern, Object valuePattern) throws IOException { + if(!isBeforeFirst()) { + // use the default table scan for finding rows mid-cursor + return super.findNextRowImpl(columnPattern, valuePattern); + } + + // searching for the first match Object[] rowValues = _entryCursor.getIndexData().constructIndexRow( columnPattern.getName(), valuePattern); if(rowValues == null) { // bummer, use the default table scan - return super.findRowImpl(columnPattern, valuePattern); + return super.findNextRowImpl(columnPattern, valuePattern); } // sweet, we can use our index @@ -335,8 +355,8 @@ public class IndexCursor extends Cursor * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - protected boolean findRowByEntryImpl(Object[] rowValues, - boolean requireMatch) + protected boolean findFirstRowByEntryImpl(Object[] rowValues, + boolean requireMatch) throws IOException { if(!findPotentialRow(rowValues, requireMatch)) { @@ -350,17 +370,23 @@ public class IndexCursor extends Cursor } @Override - protected boolean findRowImpl(Map<String,Object> rowPattern) + protected boolean findNextRowImpl(Map<String,?> rowPattern) throws IOException { + if(!isBeforeFirst()) { + // use the default table scan for finding rows mid-cursor + return super.findNextRowImpl(rowPattern); + } + + // searching for the first match IndexData indexData = _entryCursor.getIndexData(); Object[] rowValues = indexData.constructIndexRow(rowPattern); if(rowValues == null) { // bummer, use the default table scan - return super.findRowImpl(rowPattern); + return super.findNextRowImpl(rowPattern); } - + // sweet, we can use our index if(!findPotentialRow(rowValues, true)) { // at end of index, no potential matches @@ -368,7 +394,7 @@ public class IndexCursor extends Cursor } // find actual matching row - Map<String,Object> indexRowPattern = null; + Map<String,?> indexRowPattern = null; if(rowPattern.size() == indexData.getColumns().size()) { // the rowPattern matches our index columns exactly, so we can // streamline our testing below @@ -376,10 +402,10 @@ public class IndexCursor extends Cursor } else { // the rowPattern has more columns than just the index, so we need to // do more work when testing below - indexRowPattern = new LinkedHashMap<String,Object>(); + Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>(); + indexRowPattern = tmpRowPattern; for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) { - indexRowPattern.put(idxCol.getName(), - rowValues[idxCol.getColumnIndex()]); + tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]); } } @@ -562,7 +588,7 @@ public class IndexCursor extends Cursor super(columnNames); _rowValues = rowValues; try { - _hasNext = findRowByEntryImpl(rowValues, true); + _hasNext = findFirstRowByEntryImpl(rowValues, true); _validRow = _hasNext; } catch(IOException e) { throw new IllegalStateException(e); diff --git a/src/java/com/healthmarketscience/jackcess/IndexData.java b/src/java/com/healthmarketscience/jackcess/IndexData.java index 383bf24..637f763 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexData.java +++ b/src/java/com/healthmarketscience/jackcess/IndexData.java @@ -817,14 +817,14 @@ public abstract class IndexData { { return constructIndexRow(Collections.singletonMap(colName, value)); } - + /** * Constructs an array of values appropriate for this index from the given * column values. * @return the appropriate sparse array of data or {@code null} if not all * columns for this index were provided */ - public Object[] constructIndexRow(Map<String,Object> row) + public Object[] constructIndexRow(Map<String,?> row) { for(ColumnDescriptor col : _columns) { if(!row.containsKey(col.getName())) { diff --git a/src/java/com/healthmarketscience/jackcess/Joiner.java b/src/java/com/healthmarketscience/jackcess/Joiner.java index 89da959..3b51854 100644 --- a/src/java/com/healthmarketscience/jackcess/Joiner.java +++ b/src/java/com/healthmarketscience/jackcess/Joiner.java @@ -121,7 +121,7 @@ public class Joiner * @param fromRow row from the "from" table (which must include the relevant * columns for this join relationship) */ - public Map<String,Object> findFirstRow(Map<String,Object> fromRow) + public Map<String,Object> findFirstRow(Map<String,?> fromRow) throws IOException { return findFirstRow(fromRow, null); @@ -136,12 +136,12 @@ public class Joiner * columns for this join relationship) * @param columnNames desired columns in the from table row */ - public Map<String,Object> findFirstRow(Map<String,Object> fromRow, + public Map<String,Object> findFirstRow(Map<String,?> fromRow, Collection<String> columnNames) throws IOException { toEntryValues(fromRow); - return ((_toCursor.findRowByEntry(_entryValues) ? + return ((_toCursor.findFirstRowByEntry(_entryValues) ? _toCursor.getCurrentRow(columnNames) : null)); } @@ -152,7 +152,7 @@ public class Joiner * @param fromRow row from the "from" table (which must include the relevant * columns for this join relationship) */ - public Iterator<Map<String,Object>> findRows(Map<String,Object> fromRow) + public Iterator<Map<String,Object>> findRows(Map<String,?> fromRow) { return findRows(fromRow, null); } @@ -165,7 +165,7 @@ public class Joiner * columns for this join relationship) * @param columnNames desired columns in the from table row */ - public Iterator<Map<String,Object>> findRows(Map<String,Object> fromRow, + public Iterator<Map<String,Object>> findRows(Map<String,?> fromRow, Collection<String> columnNames) { toEntryValues(fromRow); @@ -181,8 +181,7 @@ public class Joiner * @throws IllegalStateException if an IOException is thrown by one of the * operations, the actual exception will be contained within */ - public Iterable<Map<String,Object>> findRowsIterable( - Map<String,Object> fromRow) + public Iterable<Map<String,Object>> findRowsIterable(Map<String,?> fromRow) { return findRowsIterable(fromRow, null); } @@ -198,7 +197,7 @@ public class Joiner * operations, the actual exception will be contained within */ public Iterable<Map<String,Object>> findRowsIterable( - final Map<String,Object> fromRow, final Collection<String> columnNames) + final Map<String,?> fromRow, final Collection<String> columnNames) { return new Iterable<Map<String, Object>>() { public Iterator<Map<String, Object>> iterator() { @@ -211,7 +210,7 @@ public class Joiner * Fills in the _entryValues with the relevant info from the given "from" * table row. */ - private void toEntryValues(Map<String,Object> fromRow) + private void toEntryValues(Map<String,?> fromRow) { for(int i = 0; i < _entryValues.length; ++i) { _entryValues[i] = fromRow.get(_fromCols.get(i).getName()); diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index ba7b675..ae8b128 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -1339,7 +1339,7 @@ public class Table * appropriate for a call to {@link #addRow(Object...)}. * @usage _general_method_ */ - public Object[] asRow(Map<String,Object> rowMap) { + public Object[] asRow(Map<String,?> rowMap) { return asRow(rowMap, null); } @@ -1348,14 +1348,14 @@ public class Table * appropriate for a call to {@link #updateCurrentRow(Object...)}. * @usage _general_method_ */ - public Object[] asUpdateRow(Map<String,Object> rowMap) { + public Object[] asUpdateRow(Map<String,?> rowMap) { return asRow(rowMap, Column.KEEP_VALUE); } /** * Converts a map of columnName -> columnValue to an array of row values. */ - private Object[] asRow(Map<String,Object> rowMap, Object defaultValue) + private Object[] asRow(Map<String,?> rowMap, Object defaultValue) { Object[] row = new Object[_columns.size()]; if(defaultValue != null) { diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java index f9de56d..0a4b255 100644 --- a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java +++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java @@ -112,7 +112,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue> Table complexColumns = db.getSystemComplexColumns(); IndexCursor cursor = IndexCursor.createCursor( complexColumns, complexColumns.getPrimaryKeyIndex()); - if(!cursor.findRowByEntry(complexTypeId)) { + if(!cursor.findFirstRowByEntry(complexTypeId)) { throw new IOException( "Could not find complex column info for complex column with id " + complexTypeId); @@ -333,7 +333,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue> .toIndexCursor(); } - if(!_pkCursor.findRowByEntry(id)) { + if(!_pkCursor.findFirstRowByEntry(id)) { throw new IllegalArgumentException("Row with id " + id + " does not exist"); } diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java index ba31a3b..c8678a2 100644 --- a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java +++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java @@ -256,8 +256,7 @@ public class ComplexValueForeignKey extends Number return value; } - public UnsupportedValue addUnsupportedValue( - Map<String,? extends Object> values) + public UnsupportedValue addUnsupportedValue(Map<String,?> values) throws IOException { reset(); diff --git a/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java index 27ee434..03bd8b1 100644 --- a/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java +++ b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java @@ -79,13 +79,12 @@ public class UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue> return row; } - public static UnsupportedValue newValue(Map<String,? extends Object> values) { + public static UnsupportedValue newValue(Map<String,?> values) { return newValue(INVALID_COMPLEX_VALUE_ID, values); } public static UnsupportedValue newValue( - ComplexValueForeignKey complexValueFk, - Map<String,? extends Object> values) { + ComplexValueForeignKey complexValueFk, Map<String,?> values) { return new UnsupportedValueImpl(INVALID_ID, complexValueFk, new LinkedHashMap<String,Object>(values)); } diff --git a/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java b/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java index 09ec547..019dd74 100644 --- a/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java @@ -97,7 +97,7 @@ public class ComplexColumnTest extends TestCase "row8-memo", upTime); Cursor cursor = Cursor.createCursor(t1); - assertTrue(cursor.findRow(t1.getColumn("id"), "row3")); + assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row3")); ComplexValueForeignKey row3ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(verCol); cursor.setCurrentRowValue(col, "new-value"); @@ -191,7 +191,7 @@ public class ComplexColumnTest extends TestCase checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt"); Cursor cursor = Cursor.createCursor(t1); - assertTrue(cursor.findRow(t1.getColumn("id"), "row4")); + assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row4")); ComplexValueForeignKey row4ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); Attachment a = row4ValFk.addAttachment(null, "test_data.txt", "txt", @@ -216,7 +216,7 @@ public class ComplexColumnTest extends TestCase row4ValFk.getAttachments().get(0).delete(); checkAttachments(4, row4ValFk); - assertTrue(cursor.findRow(t1.getColumn("id"), "row2")); + assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row2")); ComplexValueForeignKey row2ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); row2ValFk.deleteAllValues(); @@ -267,7 +267,7 @@ public class ComplexColumnTest extends TestCase checkMultiValues(row8ValFk.get(), row8ValFk, "value1", "value2"); Cursor cursor = Cursor.createCursor(t1); - assertTrue(cursor.findRow(t1.getColumn("id"), "row2")); + assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row2")); ComplexValueForeignKey row2ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); SingleValue v = row2ValFk.addMultiValue("value2"); @@ -287,7 +287,7 @@ public class ComplexColumnTest extends TestCase row2ValFk.getMultiValues().get(0).delete(); checkMultiValues(2, row2ValFk); - assertTrue(cursor.findRow(t1.getColumn("id"), "row3")); + assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row3")); ComplexValueForeignKey row3ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); row3ValFk.deleteAllValues(); diff --git a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java index dc79685..897cf53 100644 --- a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java @@ -46,6 +46,10 @@ import junit.framework.TestCase; */ public class CursorTest extends TestCase { + static final List<TestDB> INDEX_CURSOR_DBS = + TestDB.getSupportedForBasename(Basename.INDEX_CURSOR); + + public CursorTest(String name) throws Exception { super(name); } @@ -101,9 +105,6 @@ public class CursorTest extends TestCase { return expectedRows; } - static final List<TestDB> INDEX_CURSOR_DBS = - TestDB.getSupportedForBasename(Basename.INDEX_CURSOR); - static Database createTestIndexTable(final TestDB indexCursorDB) throws Exception { @@ -118,6 +119,52 @@ public class CursorTest extends TestCase { return db; } + private static List<Map<String,Object>> createDupeTestTableData() + throws Exception + { + List<Map<String,Object>> expectedRows = + new ArrayList<Map<String,Object>>(); + int[] ids = new int[]{3, 7, 6, 1, 2, 9, 0, 5, 4, 8}; + for(int i : ids) { + expectedRows.add(createExpectedRow("id", i, "value", "data" + (i % 3))); + } + for(int i : ids) { + expectedRows.add(createExpectedRow("id", i, "value", "data" + (i % 5))); + } + return expectedRows; + } + + private static Database createDupeTestTable(final FileFormat fileFormat) + throws Exception + { + Database db = create(fileFormat); + + Table table = new TableBuilder("test") + .addColumn(new ColumnBuilder("id", DataType.LONG)) + .addColumn(new ColumnBuilder("value", DataType.TEXT)) + .toTable(db); + + for(Map<String,Object> row : createDupeTestTableData()) { + table.addRow(row.get("id"), row.get("value")); + } + + return db; + } + + static Database createDupeTestTable(final TestDB indexCursorDB) + throws Exception + { + Database db = openCopy(indexCursorDB); + + Table table = db.getTable("test"); + + for(Map<String,Object> row : createDupeTestTableData()) { + table.addRow(row.get("id"), row.get("value")); + } + + return db; + } + private static Cursor createIndexSubRangeCursor(Table table, Index idx, int type) @@ -158,8 +205,8 @@ public class CursorTest extends TestCase { } } - private void doTestSimple(Cursor cursor, - List<Map<String, Object>> expectedRows) + private static void doTestSimple(Cursor cursor, + List<Map<String, Object>> expectedRows) throws Exception { if(expectedRows == null) { @@ -186,8 +233,8 @@ public class CursorTest extends TestCase { } } - private void doTestMove(Cursor cursor, - List<Map<String, Object>> expectedRows) + private static void doTestMove(Cursor cursor, + List<Map<String, Object>> expectedRows) throws Exception { if(expectedRows == null) { @@ -245,47 +292,47 @@ public class CursorTest extends TestCase { } } - private void doTestSearch(Table table, Cursor cursor, Index index, - Integer... outOfRangeValues) + private static void doTestSearch(Table table, Cursor cursor, Index index, + Integer... outOfRangeValues) throws Exception { - assertTrue(cursor.findRow(table.getColumn("id"), 3)); + assertTrue(cursor.findFirstRow(table.getColumn("id"), 3)); assertEquals(createExpectedRow("id", 3, "value", "data" + 3), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "id", 6, "value", "data" + 6))); assertEquals(createExpectedRow("id", 6, "value", "data" + 6), cursor.getCurrentRow()); - assertFalse(cursor.findRow(createExpectedRow( + assertFalse(cursor.findFirstRow(createExpectedRow( "id", 8, "value", "data" + 13))); - assertFalse(cursor.findRow(table.getColumn("id"), 13)); + assertFalse(cursor.findFirstRow(table.getColumn("id"), 13)); assertEquals(createExpectedRow("id", 6, "value", "data" + 6), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "value", "data" + 7))); assertEquals(createExpectedRow("id", 7, "value", "data" + 7), cursor.getCurrentRow()); - assertTrue(cursor.findRow(table.getColumn("value"), "data" + 4)); + assertTrue(cursor.findFirstRow(table.getColumn("value"), "data" + 4)); assertEquals(createExpectedRow("id", 4, "value", "data" + 4), cursor.getCurrentRow()); for(Integer outOfRangeValue : outOfRangeValues) { - assertFalse(cursor.findRow(table.getColumn("id"), + assertFalse(cursor.findFirstRow(table.getColumn("id"), outOfRangeValue)); - assertFalse(cursor.findRow(table.getColumn("value"), + assertFalse(cursor.findFirstRow(table.getColumn("value"), "data" + outOfRangeValue)); - assertFalse(cursor.findRow(createExpectedRow( + assertFalse(cursor.findFirstRow(createExpectedRow( "id", outOfRangeValue, "value", "data" + outOfRangeValue))); } @@ -329,8 +376,8 @@ public class CursorTest extends TestCase { } } - private void doTestReverse(Cursor cursor, - List<Map<String, Object>> expectedRows) + private static void doTestReverse(Cursor cursor, + List<Map<String, Object>> expectedRows) throws Exception { if(expectedRows == null) { @@ -360,10 +407,10 @@ public class CursorTest extends TestCase { } } - private void doTestLiveAddition(Table table, - Cursor cursor1, - Cursor cursor2, - Integer newRowNum) throws Exception + private static void doTestLiveAddition(Table table, + Cursor cursor1, + Cursor cursor2, + Integer newRowNum) throws Exception { cursor1.moveNextRows(11); cursor2.moveNextRows(11); @@ -403,7 +450,7 @@ public class CursorTest extends TestCase { } } - private void doTestLiveDeletion( + private static void doTestLiveDeletion( Cursor cursor1, Cursor cursor2, Cursor cursor3, @@ -682,6 +729,128 @@ public class CursorTest extends TestCase { } } + public void testFindAllIndex() throws Exception { + for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) { + Database db = createDupeTestTable(fileFormat); + + Table table = db.getTable("test"); + Cursor cursor = Cursor.createCursor(table); + + doTestFindAll(table, cursor, null); + + db.close(); + } + } + + public void testFindAll() throws Exception { + for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) { + Database db = createDupeTestTable(indexCursorDB); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + Cursor cursor = Cursor.createIndexCursor(table, idx); + + doTestFindAll(table, cursor, idx); + + db.close(); + } + } + + private static void doTestFindAll(Table table, Cursor cursor, Index index) + throws Exception + { + Column valCol = table.getColumn("value"); + List<Map<String,Object>> rows = RowFilterTest.toList( + cursor.columnMatchIterable(valCol, "data2")); + + List<Map<String, Object>> expectedRows = null; + + if(index == null) { + expectedRows = + createExpectedTable( + createExpectedRow( + "id", 2, "value", "data2"), + createExpectedRow( + "id", 5, "value", "data2"), + createExpectedRow( + "id", 8, "value", "data2"), + createExpectedRow( + "id", 7, "value", "data2"), + createExpectedRow( + "id", 2, "value", "data2")); + } else { + expectedRows = + createExpectedTable( + createExpectedRow( + "id", 2, "value", "data2"), + createExpectedRow( + "id", 2, "value", "data2"), + createExpectedRow( + "id", 5, "value", "data2"), + createExpectedRow( + "id", 7, "value", "data2"), + createExpectedRow( + "id", 8, "value", "data2")); + } + assertEquals(expectedRows, rows); + + rows = RowFilterTest.toList( + cursor.columnMatchIterable(valCol, "data4")); + + if(index == null) { + expectedRows = + createExpectedTable( + createExpectedRow( + "id", 9, "value", "data4"), + createExpectedRow( + "id", 4, "value", "data4")); + } else { + expectedRows = + createExpectedTable( + createExpectedRow( + "id", 4, "value", "data4"), + createExpectedRow( + "id", 9, "value", "data4")); + } + assertEquals(expectedRows, rows); + + rows = RowFilterTest.toList( + cursor.columnMatchIterable(valCol, "data9")); + + assertTrue(rows.isEmpty()); + + rows = RowFilterTest.toList( + cursor.rowMatchIterable(Collections.singletonMap("id", 8))); + + expectedRows = + createExpectedTable( + createExpectedRow( + "id", 8, "value", "data2"), + createExpectedRow( + "id", 8, "value", "data3")); + assertEquals(expectedRows, rows); + + for(Map<String,Object> row : table) { + + expectedRows = new ArrayList<Map<String,Object>>(); + for(Map<String,Object> tmpRow : cursor) { + if(row.equals(tmpRow)) { + expectedRows.add(tmpRow); + } + } + assertFalse(expectedRows.isEmpty()); + + rows = RowFilterTest.toList(cursor.rowMatchIterable(row)); + + assertEquals(expectedRows, rows); + } + + rows = RowFilterTest.toList( + cursor.rowMatchIterable(createExpectedRow( + "id", 8, "value", "data13"))); + assertTrue(rows.isEmpty()); + } + public void testId() throws Exception { for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) { @@ -742,8 +911,8 @@ public class CursorTest extends TestCase { } } - private void doTestMatchers(Table table, ColumnMatcher columnMatcher, - boolean caseInsensitive) + private static void doTestMatchers(Table table, ColumnMatcher columnMatcher, + boolean caseInsensitive) throws Exception { assertTrue(columnMatcher.matches(table, "value", null, null)); @@ -758,26 +927,26 @@ public class CursorTest extends TestCase { assertTrue(columnMatcher.matches(table, "value", 13, 13)); } - private void doTestMatcher(Table table, Cursor cursor, - ColumnMatcher columnMatcher, - boolean caseInsensitive) + private static void doTestMatcher(Table table, Cursor cursor, + ColumnMatcher columnMatcher, + boolean caseInsensitive) throws Exception { cursor.setColumnMatcher(columnMatcher); - assertTrue(cursor.findRow(table.getColumn("id"), 3)); + assertTrue(cursor.findFirstRow(table.getColumn("id"), 3)); assertEquals(createExpectedRow("id", 3, "value", "data" + 3), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "id", 6, "value", "data" + 6))); assertEquals(createExpectedRow("id", 6, "value", "data" + 6), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "id", 6, "value", "Data" + 6)) == caseInsensitive); if(caseInsensitive) { @@ -786,21 +955,21 @@ public class CursorTest extends TestCase { cursor.getCurrentRow()); } - assertFalse(cursor.findRow(createExpectedRow( + assertFalse(cursor.findFirstRow(createExpectedRow( "id", 8, "value", "data" + 13))); - assertFalse(cursor.findRow(table.getColumn("id"), 13)); + assertFalse(cursor.findFirstRow(table.getColumn("id"), 13)); assertEquals(createExpectedRow("id", 6, "value", "data" + 6), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "value", "data" + 7))); assertEquals(createExpectedRow("id", 7, "value", "data" + 7), cursor.getCurrentRow()); - assertTrue(cursor.findRow(createExpectedRow( + assertTrue(cursor.findFirstRow(createExpectedRow( "value", "Data" + 7)) == caseInsensitive); if(caseInsensitive) { assertEquals(createExpectedRow("id", 7, @@ -808,12 +977,12 @@ public class CursorTest extends TestCase { cursor.getCurrentRow()); } - assertTrue(cursor.findRow(table.getColumn("value"), "data" + 4)); + assertTrue(cursor.findFirstRow(table.getColumn("value"), "data" + 4)); assertEquals(createExpectedRow("id", 4, "value", "data" + 4), cursor.getCurrentRow()); - assertTrue(cursor.findRow(table.getColumn("value"), "Data" + 4) + assertTrue(cursor.findFirstRow(table.getColumn("value"), "Data" + 4) == caseInsensitive); if(caseInsensitive) { assertEquals(createExpectedRow("id", 4, @@ -831,17 +1000,17 @@ public class CursorTest extends TestCase { Index idx = t1.getIndex(IndexBuilder.PRIMARY_KEY_NAME); IndexCursor cursor = IndexCursor.createCursor(t1, idx); - assertFalse(cursor.findRowByEntry(-1)); + assertFalse(cursor.findFirstRowByEntry(-1)); cursor.findClosestRowByEntry(-1); assertEquals(0, cursor.getCurrentRow().get("id")); - assertTrue(cursor.findRowByEntry(1)); + assertTrue(cursor.findFirstRowByEntry(1)); assertEquals(1, cursor.getCurrentRow().get("id")); cursor.findClosestRowByEntry(2); assertEquals(2, cursor.getCurrentRow().get("id")); - assertFalse(cursor.findRowByEntry(4)); + assertFalse(cursor.findFirstRowByEntry(4)); cursor.findClosestRowByEntry(4); assertTrue(cursor.isAfterLast()); diff --git a/test/src/java/com/healthmarketscience/jackcess/RowFilterTest.java b/test/src/java/com/healthmarketscience/jackcess/RowFilterTest.java index 8d9b510..586ad9a 100644 --- a/test/src/java/com/healthmarketscience/jackcess/RowFilterTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/RowFilterTest.java @@ -100,7 +100,7 @@ public class RowFilterTest extends TestCase rows))); } - private List<Map<String,Object>> toList(Iterable<Map<String,Object>> rows) + static List<Map<String,Object>> toList(Iterable<Map<String,Object>> rows) { List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>(); for(Map<String,Object> row : rows) { |