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