public class

SortCursor

extends AbstractCursor
/*
 * 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.cooliris.media;

/*
 *  Added changes to support numeric comparisons, and also expose the current
 *  cursor being used
 */

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 int mType;
    private boolean mAscending;
    public static final int TYPE_STRING = 0;
    public static final int TYPE_NUMERIC = 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;
        }
    };
    private int mCursorIndex;

    public SortCursor(Cursor[] cursors, String sortcolumn, int type, boolean ascending) {
        mAscending = ascending;
        mCursors = cursors;
        mType = type;
        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;
        if (type == TYPE_STRING) {
            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 == null || current.compareToIgnoreCase(smallest) < 0) {
                    smallest = current;
                    mCursor = mCursors[j];
                    mCursorIndex = j;
                }
            }
        } else {
            long smallest = (ascending) ? Long.MAX_VALUE : Long.MIN_VALUE;
            for (int j = 0; j < length; j++) {
                if (mCursors[j] == null || mCursors[j].isAfterLast()) {
                    continue;
                }
                long current = mCursors[j].getLong(mSortColumns[j]);
                boolean comparison = (ascending) ? (current < smallest) : (current > smallest);
                if (mCursor == null || comparison) {
                    smallest = current;
                    mCursor = mCursors[j];
                    mCursorIndex = 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];
            mCursorIndex = 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;
        if (mType == TYPE_STRING) {
            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 == null || current.compareToIgnoreCase(smallest) < 0) {
                        smallest = current;
                        smallestIdx = j;
                    }
                }
                if (i == newPosition) {
                    break;
                }
                if (mCursors[smallestIdx] != null) {
                    mCursors[smallestIdx].moveToNext();
                }
            }
        } else {
            for (int i = oldPosition; i <= newPosition; i++) {
                long smallest = (mAscending) ? Long.MAX_VALUE : Long.MIN_VALUE;
                smallestIdx = -1;
                for (int j = 0; j < length; j++) {
                    if (mCursors[j] == null || mCursors[j].isAfterLast()) {
                        continue;
                    }
                    long current = mCursors[j].getLong(mSortColumns[j]);
                    boolean comparison = (mAscending) ? current < smallest : current > smallest;
                    if (smallestIdx < 0 || comparison) {
                        smallest = current;
                        smallestIdx = j;
                    }
                }
                if (i == newPosition) {
                    break;
                }
                if (mCursors[smallestIdx] != null) {
                    mCursors[smallestIdx].moveToNext();
                }
            }
        }
        mCursor = mCursors[smallestIdx];
        mCursorIndex = 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;
    }

    public int getCurrentCursorIndex() {
        return mCursorIndex;
    }
}