/*
* Copyright (C) 2007 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 com.android.music;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.util.Log;
/**
* A variant of MergeCursor that sorts the cursors being merged. If decent
* performance is ever obtained, it can be put back under android.database.
*/
public class SortCursor extends AbstractCursor
{
private static final String TAG = "SortCursor";
private Cursor mCursor; // updated in onMove
private Cursor[] mCursors;
private int [] mSortColumns;
private final int ROWCACHESIZE = 64;
private int mRowNumCache[] = new int[ROWCACHESIZE];
private int mCursorCache[] = new int[ROWCACHESIZE];
private int mCurRowNumCache[][];
private int mLastCacheHit = -1;
private DataSetObserver mObserver = new DataSetObserver() {
@Override
public void onChanged() {
// Reset our position so the optimizations in move-related code
// don't screw us over
mPos = -1;
}
@Override
public void onInvalidated() {
mPos = -1;
}
};
public SortCursor(Cursor[] cursors, String sortcolumn)
{
mCursors = cursors;
int length = mCursors.length;
mSortColumns = new int[length];
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) continue;
// Register ourself as a data set observer
mCursors[i].registerDataSetObserver(mObserver);
mCursors[i].moveToFirst();
// We don't catch the exception
mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
}
mCursor = null;
String smallest = "";
for (int j = 0 ; j < length; j++) {
if (mCursors[j] == null || mCursors[j].isAfterLast())
continue;
String current = mCursors[j].getString(mSortColumns[j]);
if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
smallest = current;
mCursor = mCursors[j];
}
}
for (int i = mRowNumCache.length - 1; i >= 0; i--) {
mRowNumCache[i] = -2;
}
mCurRowNumCache = new int[ROWCACHESIZE][length];
}
@Override
public int getCount()
{
int count = 0;
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
count += mCursors[i].getCount();
}
}
return count;
}
@Override
public boolean onMove(int oldPosition, int newPosition)
{
if (oldPosition == newPosition)
return true;
/* Find the right cursor
* Because the client of this cursor (the listadapter/view) tends
* to jump around in the cursor somewhat, a simple cache strategy
* is used to avoid having to search all cursors from the start.
* TODO: investigate strategies for optimizing random access and
* reverse-order access.
*/
int cache_entry = newPosition % ROWCACHESIZE;
if (mRowNumCache[cache_entry] == newPosition) {
int which = mCursorCache[cache_entry];
mCursor = mCursors[which];
if (mCursor == null) {
Log.w(TAG, "onMove: cache results in a null cursor.");
return false;
}
mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
mLastCacheHit = cache_entry;
return true;
}
mCursor = null;
int length = mCursors.length;
if (mLastCacheHit >= 0) {
for (int i = 0; i < length; i++) {
if (mCursors[i] == null) continue;
mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
}
}
if (newPosition < oldPosition || oldPosition == -1) {
for (int i = 0 ; i < length; i++) {
if (mCursors[i] == null) continue;
mCursors[i].moveToFirst();
}
oldPosition = 0;
}
if (oldPosition < 0) {
oldPosition = 0;
}
// search forward to the new position
int smallestIdx = -1;
for(int i = oldPosition; i <= newPosition; i++) {
String smallest = "";
smallestIdx = -1;
for (int j = 0 ; j < length; j++) {
if (mCursors[j] == null || mCursors[j].isAfterLast()) {
continue;
}
String current = mCursors[j].getString(mSortColumns[j]);
if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
smallest = current;
smallestIdx = j;
}
}
if (i == newPosition) break;
if (mCursors[smallestIdx] != null) {
mCursors[smallestIdx].moveToNext();
}
}
mCursor = mCursors[smallestIdx];
mRowNumCache[cache_entry] = newPosition;
mCursorCache[cache_entry] = smallestIdx;
for (int i = 0; i < length; i++) {
if (mCursors[i] != null) {
mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
}
}
mLastCacheHit = -1;
return true;
}
@Override
public String getString(int column)
{
return mCursor.getString(column);
}
@Override
public short getShort(int column)
{
return mCursor.getShort(column);
}
@Override
public int getInt(int column)
{
return mCursor.getInt(column);
}
@Override
public long getLong(int column)
{
return mCursor.getLong(column);
}
@Override
public float getFloat(int column)
{
return mCursor.getFloat(column);
}
@Override
public double getDouble(int column)
{
return mCursor.getDouble(column);
}
@Override
public boolean isNull(int column)
{
return mCursor.isNull(column);
}
@Override
public byte[] getBlob(int column)
{
return mCursor.getBlob(column);
}
@Override
public String[] getColumnNames()
{
if (mCursor != null) {
return mCursor.getColumnNames();
} else {
// All of the cursors may be empty, but they can still return
// this information.
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
return mCursors[i].getColumnNames();
}
}
throw new IllegalStateException("No cursor that can return names");
}
}
@Override
public void deactivate()
{
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) continue;
mCursors[i].deactivate();
}
}
@Override
public void close() {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) continue;
mCursors[i].close();
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].registerDataSetObserver(observer);
}
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].unregisterDataSetObserver(observer);
}
}
}
@Override
public boolean requery()
{
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) continue;
if (mCursors[i].requery() == false) {
return false;
}
}
return true;
}
}