SortCursor.java revision b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.music;
18
19import android.database.AbstractCursor;
20import android.database.Cursor;
21import android.database.DataSetObserver;
22import android.util.Log;
23
24/**
25 * A variant of MergeCursor that sorts the cursors being merged. If decent
26 * performance is ever obtained, it can be put back under android.database.
27 */
28public class SortCursor extends AbstractCursor {
29    private static final String TAG = "SortCursor";
30    private Cursor mCursor; // updated in onMove
31    private Cursor[] mCursors;
32    private int[] mSortColumns;
33    private final int ROWCACHESIZE = 64;
34    private int mRowNumCache[] = new int[ROWCACHESIZE];
35    private int mCursorCache[] = new int[ROWCACHESIZE];
36    private int mCurRowNumCache[][];
37    private int mLastCacheHit = -1;
38
39    private DataSetObserver mObserver = new DataSetObserver() {
40
41        @Override
42        public void onChanged() {
43            // Reset our position so the optimizations in move-related code
44            // don't screw us over
45            mPos = -1;
46        }
47
48        @Override
49        public void onInvalidated() {
50            mPos = -1;
51        }
52    };
53
54    public SortCursor(Cursor[] cursors, String sortcolumn) {
55        mCursors = cursors;
56
57        int length = mCursors.length;
58        mSortColumns = new int[length];
59        for (int i = 0; i < length; i++) {
60            if (mCursors[i] == null) continue;
61
62            // Register ourself as a data set observer
63            mCursors[i].registerDataSetObserver(mObserver);
64
65            mCursors[i].moveToFirst();
66
67            // We don't catch the exception
68            mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
69        }
70        mCursor = null;
71        String smallest = "";
72        for (int j = 0; j < length; j++) {
73            if (mCursors[j] == null || mCursors[j].isAfterLast()) continue;
74            String current = mCursors[j].getString(mSortColumns[j]);
75            if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
76                smallest = current;
77                mCursor = mCursors[j];
78            }
79        }
80
81        for (int i = mRowNumCache.length - 1; i >= 0; i--) {
82            mRowNumCache[i] = -2;
83        }
84        mCurRowNumCache = new int[ROWCACHESIZE][length];
85    }
86
87    @Override
88    public int getCount() {
89        int count = 0;
90        int length = mCursors.length;
91        for (int i = 0; i < length; i++) {
92            if (mCursors[i] != null) {
93                count += mCursors[i].getCount();
94            }
95        }
96        return count;
97    }
98
99    @Override
100    public boolean onMove(int oldPosition, int newPosition) {
101        if (oldPosition == newPosition) return true;
102
103        /* Find the right cursor
104         * Because the client of this cursor (the listadapter/view) tends
105         * to jump around in the cursor somewhat, a simple cache strategy
106         * is used to avoid having to search all cursors from the start.
107         * TODO: investigate strategies for optimizing random access and
108         * reverse-order access.
109         */
110
111        int cache_entry = newPosition % ROWCACHESIZE;
112
113        if (mRowNumCache[cache_entry] == newPosition) {
114            int which = mCursorCache[cache_entry];
115            mCursor = mCursors[which];
116            if (mCursor == null) {
117                Log.w(TAG, "onMove: cache results in a null cursor.");
118                return false;
119            }
120            mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
121            mLastCacheHit = cache_entry;
122            return true;
123        }
124
125        mCursor = null;
126        int length = mCursors.length;
127
128        if (mLastCacheHit >= 0) {
129            for (int i = 0; i < length; i++) {
130                if (mCursors[i] == null) continue;
131                mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
132            }
133        }
134
135        if (newPosition < oldPosition || oldPosition == -1) {
136            for (int i = 0; i < length; i++) {
137                if (mCursors[i] == null) continue;
138                mCursors[i].moveToFirst();
139            }
140            oldPosition = 0;
141        }
142        if (oldPosition < 0) {
143            oldPosition = 0;
144        }
145
146        // search forward to the new position
147        int smallestIdx = -1;
148        for (int i = oldPosition; i <= newPosition; i++) {
149            String smallest = "";
150            smallestIdx = -1;
151            for (int j = 0; j < length; j++) {
152                if (mCursors[j] == null || mCursors[j].isAfterLast()) {
153                    continue;
154                }
155                String current = mCursors[j].getString(mSortColumns[j]);
156                if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
157                    smallest = current;
158                    smallestIdx = j;
159                }
160            }
161            if (i == newPosition) break;
162            if (mCursors[smallestIdx] != null) {
163                mCursors[smallestIdx].moveToNext();
164            }
165        }
166        mCursor = mCursors[smallestIdx];
167        mRowNumCache[cache_entry] = newPosition;
168        mCursorCache[cache_entry] = smallestIdx;
169        for (int i = 0; i < length; i++) {
170            if (mCursors[i] != null) {
171                mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
172            }
173        }
174        mLastCacheHit = -1;
175        return true;
176    }
177
178    @Override
179    public String getString(int column) {
180        return mCursor.getString(column);
181    }
182
183    @Override
184    public short getShort(int column) {
185        return mCursor.getShort(column);
186    }
187
188    @Override
189    public int getInt(int column) {
190        return mCursor.getInt(column);
191    }
192
193    @Override
194    public long getLong(int column) {
195        return mCursor.getLong(column);
196    }
197
198    @Override
199    public float getFloat(int column) {
200        return mCursor.getFloat(column);
201    }
202
203    @Override
204    public double getDouble(int column) {
205        return mCursor.getDouble(column);
206    }
207
208    @Override
209    public int getType(int column) {
210        return mCursor.getType(column);
211    }
212
213    @Override
214    public boolean isNull(int column) {
215        return mCursor.isNull(column);
216    }
217
218    @Override
219    public byte[] getBlob(int column) {
220        return mCursor.getBlob(column);
221    }
222
223    @Override
224    public String[] getColumnNames() {
225        if (mCursor != null) {
226            return mCursor.getColumnNames();
227        } else {
228            // All of the cursors may be empty, but they can still return
229            // this information.
230            int length = mCursors.length;
231            for (int i = 0; i < length; i++) {
232                if (mCursors[i] != null) {
233                    return mCursors[i].getColumnNames();
234                }
235            }
236            throw new IllegalStateException("No cursor that can return names");
237        }
238    }
239
240    @Override
241    public void deactivate() {
242        int length = mCursors.length;
243        for (int i = 0; i < length; i++) {
244            if (mCursors[i] == null) continue;
245            mCursors[i].deactivate();
246        }
247    }
248
249    @Override
250    public void close() {
251        int length = mCursors.length;
252        for (int i = 0; i < length; i++) {
253            if (mCursors[i] == null) continue;
254            mCursors[i].close();
255        }
256    }
257
258    @Override
259    public void registerDataSetObserver(DataSetObserver observer) {
260        int length = mCursors.length;
261        for (int i = 0; i < length; i++) {
262            if (mCursors[i] != null) {
263                mCursors[i].registerDataSetObserver(observer);
264            }
265        }
266    }
267
268    @Override
269    public void unregisterDataSetObserver(DataSetObserver observer) {
270        int length = mCursors.length;
271        for (int i = 0; i < length; i++) {
272            if (mCursors[i] != null) {
273                mCursors[i].unregisterDataSetObserver(observer);
274            }
275        }
276    }
277
278    @Override
279    public boolean requery() {
280        int length = mCursors.length;
281        for (int i = 0; i < length; i++) {
282            if (mCursors[i] == null) continue;
283
284            if (mCursors[i].requery() == false) {
285                return false;
286            }
287        }
288
289        return true;
290    }
291}
292