Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save tomoima525/59298f6aee6bc0ef5e9e00f992101552 to your computer and use it in GitHub Desktop.

Select an option

Save tomoima525/59298f6aee6bc0ef5e9e00f992101552 to your computer and use it in GitHub Desktop.
A custom implementation of CursorJoiner. Allows you to compare int type. Also configures order.
/**
* Custom implementation of {@Link android.database.CursorJoiner.java}
* Does a join on two cursors using specific columns. This class allows join by int type.
* Tomoaki Imai 2016
* reference: http://poly.hatenablog.com/entry/20101006/p1
* MIT License
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
public class CustomCursorJoiner
implements Iterator<CustomCursorJoiner.Result>, Iterable<CustomCursorJoiner.Result> {
private Cursor mCursorLeft;
private Cursor mCursorRight;
private boolean mCompareResultIsValid;
private Result mCompareResult;
private int[] mColumnsLeft;
private int[] mColumnsRight;
private String[] mStringValues;
private int[] mIntValues;
private JoinColumnType mType;
private static OrderType sOrderType;
/**
* The result of a call to next().
*/
public enum Result {
/** The row currently pointed to by the left cursor is unique */
RIGHT,
/** The row currently pointed to by the right cursor is unique */
LEFT,
/** The rows pointed to by both cursors are the same */
BOTH
}
/**
* The type of column to join
*/
public enum JoinColumnType {
STRING,
INT
}
/**
* Order
*/
public enum OrderType {
ASC, DESC;
}
/**
* Initializes the CursorJoiner and resets the cursors to the first row. The left and right
* column name arrays must have the same number of columns.
* @param cursorLeft The left cursor to compare
* @param columnNamesLeft The column names to compare from the left cursor
* @param cursorRight The right cursor to compare
* @param columnNamesRight The column names to compare from the right cursor
* @param type the type of columns to compare
* @param orderType the columns order (ASC or DESC)
*/
public CustomCursorJoiner(
Cursor cursorLeft, String[] columnNamesLeft,
Cursor cursorRight, String[] columnNamesRight, JoinColumnType type, OrderType orderType) {
if (columnNamesLeft.length != columnNamesRight.length) {
throw new IllegalArgumentException(
"you must have the same number of columns on the left and right, "
+ columnNamesLeft.length + " != " + columnNamesRight.length);
}
mCursorLeft = cursorLeft;
mCursorRight = cursorRight;
mCursorLeft.moveToFirst();
mCursorRight.moveToFirst();
mCompareResultIsValid = false;
mColumnsLeft = buildColumnIndiciesArray(cursorLeft, columnNamesLeft);
mColumnsRight = buildColumnIndiciesArray(cursorRight, columnNamesRight);
mType = type;
switch (mType) {
case STRING:
mStringValues = new String[mColumnsLeft.length * 2];
break;
case INT:
mIntValues = new int[mColumnsLeft.length * 2];
}
sOrderType = orderType;
}
public Iterator<Result> iterator() {
return this;
}
/**
* Lookup the indicies of the each column name and return them in an array.
* @param cursor the cursor that contains the columns
* @param columnNames the array of names to lookup
* @return an array of column indices
*/
private int[] buildColumnIndiciesArray(Cursor cursor, String[] columnNames) {
int[] columns = new int[columnNames.length];
for (int i = 0; i < columnNames.length; i++) {
columns[i] = cursor.getColumnIndexOrThrow(columnNames[i]);
}
return columns;
}
/**
* Returns whether or not there are more rows to compare using next().
* @return true if there are more rows to compare
*/
public boolean hasNext() {
if (mCompareResultIsValid) {
switch (mCompareResult) {
case BOTH:
return !mCursorLeft.isLast() || !mCursorRight.isLast();
case LEFT:
return !mCursorLeft.isLast() || !mCursorRight.isAfterLast();
case RIGHT:
return !mCursorLeft.isAfterLast() || !mCursorRight.isLast();
default:
throw new IllegalStateException("bad value for mCompareResult, "
+ mCompareResult);
}
} else {
return !mCursorLeft.isAfterLast() || !mCursorRight.isAfterLast();
}
}
/**
* Returns the comparison result of the next row from each cursor. If one cursor
* has no more rows but the other does then subsequent calls to this will indicate that
* the remaining rows are unique.
* <p>
* The caller must check that hasNext() returns true before calling this.
* <p>
* Once next() has been called the cursors specified in the result of the call to
* next() are guaranteed to point to the row that was indicated. Reading values
* from the cursor that was not indicated in the call to next() will result in
* undefined behavior.
* @return LEFT, if the row pointed to by the left cursor is unique, RIGHT
* if the row pointed to by the right cursor is unique, BOTH if the rows in both
* cursors are the same.
*/
public Result next() {
if (!hasNext()) {
throw new IllegalStateException("you must only call next() when hasNext() is true");
}
incrementCursors();
assert hasNext();
boolean hasLeft = !mCursorLeft.isAfterLast();
boolean hasRight = !mCursorRight.isAfterLast();
if (hasLeft && hasRight) {
int compareResult = 0;
switch (mType){
case STRING:
populateStringValues(mStringValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
populateStringValues(mStringValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
compareResult = compareStrings(mStringValues);
break;
case INT:
populateIntValues(mIntValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
populateIntValues(mIntValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
compareResult = compareInts(mIntValues);
break;
default:
populateStringValues(mStringValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
populateStringValues(mStringValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
compareResult = compareStrings(mStringValues);
break;
}
switch (compareResult) {
case -1:
mCompareResult = Result.LEFT;
break;
case 0:
mCompareResult = Result.BOTH;
break;
case 1:
mCompareResult = Result.RIGHT;
break;
}
} else if (hasLeft) {
mCompareResult = Result.LEFT;
} else {
assert hasRight;
mCompareResult = Result.RIGHT;
}
mCompareResultIsValid = true;
return mCompareResult;
}
public void remove() {
throw new UnsupportedOperationException("not implemented");
}
/**
* Reads the strings from the cursor that are specifed in the columnIndicies
* array and saves them in values beginning at startingIndex, skipping a slot
* for each value. If columnIndicies has length 3 and startingIndex is 1, the
* values will be stored in slots 1, 3, and 5.
* @param values the String[] to populate
* @param cursor the cursor from which to read
* @param columnIndicies the indicies of the values to read from the cursor
* @param startingIndex the slot in which to start storing values, and must be either 0 or 1.
*/
private static void populateStringValues(String[] values, Cursor cursor, int[] columnIndicies,
int startingIndex) {
assert startingIndex == 0 || startingIndex == 1;
for (int i = 0; i < columnIndicies.length; i++) {
values[startingIndex + i*2] = cursor.getString(columnIndicies[i]);
}
}
/**
* Reads the strings from the cursor that are specifed in the columnIndicies
* array and saves them in values beginning at startingIndex, skipping a slot
* for each value. If columnIndicies has length 3 and startingIndex is 1, the
* values will be stored in slots 1, 3, and 5.
* @param values the int[] to populate
* @param cursor the cursor from which to read
* @param columnIndicies the indicies of the values to read from the cursor
* @param startingIndex the slot in which to start storing values, and must be either 0 or 1.
*/
private static void populateIntValues(int[] values, Cursor cursor, int[] columnIndicies,
int startingIndex) {
assert startingIndex == 0 || startingIndex == 1;
for (int i = 0; i < columnIndicies.length; i++) {
values[startingIndex + i*2] = cursor.getInt(columnIndicies[i]);
}
}
/**
* Increment the cursors past the rows indicated in the most recent call to next().
* This will only have an affect once per call to next().
*/
private void incrementCursors() {
if (mCompareResultIsValid) {
switch (mCompareResult) {
case LEFT:
mCursorLeft.moveToNext();
break;
case RIGHT:
mCursorRight.moveToNext();
break;
case BOTH:
mCursorLeft.moveToNext();
mCursorRight.moveToNext();
break;
}
mCompareResultIsValid = false;
}
}
/**
* Compare the values. Values contains n pairs of strings. If all the pairs of strings match
* then returns 0. Otherwise returns the comparison result of the first non-matching pair
* of values, -1 if the first of the pair is less than the second of the pair or 1 if it
* is greater.
* @param values the n pairs of values to compare
* @return -1, 0, or 1 as described above.
*/
private static int compareStrings(String... values) {
if ((values.length % 2) != 0) {
throw new IllegalArgumentException("you must specify an even number of values");
}
for (int index = 0; index < values.length; index+=2) {
if (values[index] == null) {
if (values[index+1] == null) continue;
return -1;
}
if (values[index+1] == null) {
return 1;
}
int comp = values[index].compareTo(values[index+1]);
if (comp != 0) {
return comp < 0 ? -1 : 1;
}
}
return 0;
}
private static int compareInts(int... values) {
if ((values.length % 2) != 0) {
throw new IllegalArgumentException("you must specify an even number of values");
}
for (int index = 0; index < values.length; index += 2) {
int comp = values[index] - values[index + 1];
if (comp != 0) {
if (sOrderType == OrderType.DESC) {
// if LEFT is smaller than RIGHT, move RIGHT cursor since value order is desc
return comp < 0 ? 1 : -1;
} else if (sOrderType == OrderType.ASC) {
return comp > 0 ? 1 : -1;
}
}
}
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment