diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2013-07-30 02:17:15 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2013-07-30 02:17:15 +0000 |
commit | d1a79d0064632cca220409abb799ab1757c6caa7 (patch) | |
tree | 5cea8606b34a37ff241f9b24f0d5e6b2178a10b5 /src/java/com/healthmarketscience/jackcess/util/Joiner.java | |
parent | 50a356790e619903269a2aa52db7f4a72d1d802d (diff) | |
download | jackcess-d1a79d0064632cca220409abb799ab1757c6caa7.tar.gz jackcess-d1a79d0064632cca220409abb799ab1757c6caa7.zip |
merge branch jackcess-2 changes through r759
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@760 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/java/com/healthmarketscience/jackcess/util/Joiner.java')
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/util/Joiner.java | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/util/Joiner.java b/src/java/com/healthmarketscience/jackcess/util/Joiner.java new file mode 100644 index 0000000..02aa051 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/util/Joiner.java @@ -0,0 +1,349 @@ +/* +Copyright (c) 2011 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 java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.healthmarketscience.jackcess.CursorBuilder; +import com.healthmarketscience.jackcess.Index; +import com.healthmarketscience.jackcess.IndexCursor; +import com.healthmarketscience.jackcess.Row; +import com.healthmarketscience.jackcess.RuntimeIOException; +import com.healthmarketscience.jackcess.Table; +import com.healthmarketscience.jackcess.impl.IndexImpl; + +/** + * Utility for finding rows based on pre-defined, foreign-key table + * relationships. + * + * @author James Ahlborn + */ +public class Joiner +{ + private final Index _fromIndex; + private final List<? extends Index.Column> _fromCols; + private final IndexCursor _toCursor; + private final Object[] _entryValues; + + private Joiner(Index fromIndex, IndexCursor toCursor) + { + _fromIndex = fromIndex; + _fromCols = _fromIndex.getColumns(); + _entryValues = new Object[_fromCols.size()]; + _toCursor = toCursor; + } + + /** + * Creates a new Joiner based on the foreign-key relationship between the + * given "from"" table and the given "to"" table. + * + * @param fromTable the "from" side of the relationship + * @param toTable the "to" side of the relationship + * @throws IllegalArgumentException if there is no relationship between the + * given tables + */ + public static Joiner create(Table fromTable, Table toTable) + throws IOException + { + return create(fromTable.getForeignKeyIndex(toTable)); + } + + /** + * Creates a new Joiner based on the given index which backs a foreign-key + * relationship. The table of the given index will be the "from" table and + * the table on the other end of the relationship will be the "to" table. + * + * @param fromIndex the index backing one side of a foreign-key relationship + */ + public static Joiner create(Index fromIndex) + throws IOException + { + Index toIndex = fromIndex.getReferencedIndex(); + IndexCursor toCursor = CursorBuilder.createCursor( + toIndex.getTable(), toIndex); + // text lookups are always case-insensitive + toCursor.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE); + return new Joiner(fromIndex, toCursor); + } + + /** + * Creates a new Joiner that is the reverse of this Joiner (the "from" and + * "to" tables are swapped). + */ + public Joiner createReverse() + throws IOException + { + return create(getToTable(), getFromTable()); + } + + public Table getFromTable() { + return getFromIndex().getTable(); + } + + public Index getFromIndex() { + return _fromIndex; + } + + public Table getToTable() { + return getToCursor().getTable(); + } + + public Index getToIndex() { + return getToCursor().getIndex(); + } + + public IndexCursor getToCursor() { + return _toCursor; + } + + public List<? extends Index.Column> getColumns() { + // note, this list is already unmodifiable, no need to re-wrap + return _fromCols; + } + + /** + * Returns {@code true} if the "to" table has any rows based on the given + * columns in the "from" table, {@code false} otherwise. + */ + public boolean hasRows(Map<String,?> fromRow) throws IOException { + toEntryValues(fromRow); + return _toCursor.findFirstRowByEntry(_entryValues); + } + + /** + * Returns {@code true} if the "to" table has any rows based on the given + * columns in the "from" table, {@code false} otherwise. + * @usage _intermediate_method_ + */ + public boolean hasRows(Object[] fromRow) throws IOException { + toEntryValues(fromRow); + return _toCursor.findFirstRowByEntry(_entryValues); + } + + /** + * Returns the first row in the "to" table based on the given columns in the + * "from" table if any, {@code null} if there is no matching row. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + */ + public Row findFirstRow(Map<String,?> fromRow) + throws IOException + { + return findFirstRow(fromRow, null); + } + + /** + * Returns selected columns from the first row in the "to" table based on + * the given columns in the "from" table if any, {@code null} if there is no + * matching row. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @param columnNames desired columns in the from table row + */ + public Row findFirstRow(Map<String,?> fromRow, + Collection<String> columnNames) + throws IOException + { + return (hasRows(fromRow) ? _toCursor.getCurrentRow(columnNames) : null); + } + + /** + * Returns an Iterator over all the rows in the "to" table based on the + * given columns in the "from" table. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + */ + public Iterator<Row> findRows(Map<String,?> fromRow) + { + return findRows(fromRow, null); + } + + /** + * Returns an Iterator with the selected columns over all the rows in the + * "to" table based on the given columns in the "from" table. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @param columnNames desired columns in the from table row + */ + public Iterator<Row> findRows(Map<String,?> fromRow, + Collection<String> columnNames) + { + toEntryValues(fromRow); + return _toCursor.newEntryIterable(_entryValues) + .setColumnNames(columnNames).iterator(); + } + + /** + * Returns an Iterator with the selected columns over all the rows in the + * "to" table based on the given columns in the "from" table. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @param columnNames desired columns in the from table row + * @usage _intermediate_method_ + */ + public Iterator<Row> findRows(Object[] fromRow, + Collection<String> columnNames) + { + toEntryValues(fromRow); + return _toCursor.newEntryIterable(_entryValues) + .setColumnNames(columnNames).iterator(); + } + + /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #findRows(Map)} + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @throws RuntimeIOException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Row> findRowsIterable(Map<String,?> fromRow) + { + return findRowsIterable(fromRow, null); + } + + /** + * Returns an Iterable whose iterator() method returns the result of a call + * to {@link #findRows(Map,Collection)} + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @param columnNames desired columns in the from table row + * @throws RuntimeIOException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterable<Row> findRowsIterable( + final Map<String,?> fromRow, final Collection<String> columnNames) + { + return new Iterable<Row>() { + public Iterator<Row> iterator() { + return findRows(fromRow, columnNames); + } + }; + } + + /** + * Deletes any rows in the "to" table based on the given columns in the + * "from" table. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @return {@code true} if any "to" rows were deleted, {@code false} + * otherwise + */ + public boolean deleteRows(Map<String,?> fromRow) throws IOException { + return deleteRowsImpl(findRows(fromRow, Collections.<String>emptySet())); + } + + /** + * Deletes any rows in the "to" table based on the given columns in the + * "from" table. + * + * @param fromRow row from the "from" table (which must include the relevant + * columns for this join relationship) + * @return {@code true} if any "to" rows were deleted, {@code false} + * otherwise + * @usage _intermediate_method_ + */ + public boolean deleteRows(Object[] fromRow) throws IOException { + return deleteRowsImpl(findRows(fromRow, Collections.<String>emptySet())); + } + + /** + * Deletes all the rows and returns whether or not any "to"" rows were + * deleted. + */ + private static boolean deleteRowsImpl(Iterator<Row> iter) + throws IOException + { + boolean removed = false; + while(iter.hasNext()) { + iter.next(); + iter.remove(); + removed = true; + } + return removed; + } + + /** + * Fills in the _entryValues with the relevant info from the given "from" + * table row. + */ + private void toEntryValues(Map<String,?> fromRow) { + for(int i = 0; i < _entryValues.length; ++i) { + _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow); + } + } + + /** + * Fills in the _entryValues with the relevant info from the given "from" + * table row. + */ + private void toEntryValues(Object[] fromRow) { + for(int i = 0; i < _entryValues.length; ++i) { + _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow); + } + } + + /** + * Returns a pretty string describing the foreign key relationship backing + * this Joiner. + */ + public String toFKString() { + StringBuilder sb = new StringBuilder(); + sb.append("Foreign Key from "); + + String fromType = "] (primary)"; + String toType = "] (secondary)"; + if(!((IndexImpl)_fromIndex).getReference().isPrimaryTable()) { + fromType = "] (secondary)"; + toType = "] (primary)"; + } + + sb.append(getFromTable().getName()).append("["); + + sb.append(_fromCols.get(0).getName()); + for(int i = 1; i < _fromCols.size(); ++i) { + sb.append(",").append(_fromCols.get(i).getName()); + } + sb.append(fromType); + + sb.append(" to ").append(getToTable().getName()).append("["); + List<? extends Index.Column> toCols = _toCursor.getIndex().getColumns(); + sb.append(toCols.get(0).getName()); + for(int i = 1; i < toCols.size(); ++i) { + sb.append(",").append(toCols.get(i).getName()); + } + sb.append(toType); + + return sb.toString(); + } +} |