267 lines
9.6 KiB
Java
267 lines
9.6 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.database;
|
|
|
|
import java.util.Iterator;
|
|
|
|
/**
|
|
* Does a join on two cursors using the specified columns. The cursors must already
|
|
* be sorted on each of the specified columns in ascending order. This joiner only
|
|
* supports the case where the tuple of key column values is unique.
|
|
* <p>
|
|
* Typical usage:
|
|
*
|
|
* <pre>
|
|
* CursorJoiner joiner = new CursorJoiner(cursorA, keyColumnsofA, cursorB, keyColumnsofB);
|
|
* for (CursorJoiner.Result joinerResult : joiner) {
|
|
* switch (joinerResult) {
|
|
* case LEFT:
|
|
* // handle case where a row in cursorA is unique
|
|
* break;
|
|
* case RIGHT:
|
|
* // handle case where a row in cursorB is unique
|
|
* break;
|
|
* case BOTH:
|
|
* // handle case where a row with the same key is in both cursors
|
|
* break;
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
|
public final class CursorJoiner
|
|
implements Iterator<CursorJoiner.Result>, Iterable<CursorJoiner.Result> {
|
|
private Cursor mCursorLeft;
|
|
private Cursor mCursorRight;
|
|
private boolean mCompareResultIsValid;
|
|
private Result mCompareResult;
|
|
private int[] mColumnsLeft;
|
|
private int[] mColumnsRight;
|
|
private String[] mValues;
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public CursorJoiner(
|
|
Cursor cursorLeft, String[] columnNamesLeft,
|
|
Cursor cursorRight, String[] columnNamesRight) {
|
|
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);
|
|
|
|
mValues = new String[mColumnsLeft.length * 2];
|
|
}
|
|
|
|
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) {
|
|
populateValues(mValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
|
|
populateValues(mValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
|
|
switch (compareStrings(mValues)) {
|
|
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 populateValues(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]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|