Created
May 12, 2016 06:27
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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