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 17 package com.cooliris.media; 18 19 /* 20 * Added changes to support numeric comparisons, and also expose the current 21 * cursor being used 22 */ 23 24 import android.database.AbstractCursor; 25 import android.database.Cursor; 26 import android.database.DataSetObserver; 27 import android.util.Log; 28 29 /** 30 * A variant of MergeCursor that sorts the cursors being merged. If decent 31 * performance is ever obtained, it can be put back under android.database. 32 */ 33 public class SortCursor extends AbstractCursor { 34 private static final String TAG = "SortCursor"; 35 private Cursor mCursor; // updated in onMove 36 private Cursor[] mCursors; 37 private int[] mSortColumns; 38 private final int ROWCACHESIZE = 64; 39 private int mRowNumCache[] = new int[ROWCACHESIZE]; 40 private int mCursorCache[] = new int[ROWCACHESIZE]; 41 private int mCurRowNumCache[][]; 42 private int mLastCacheHit = -1; 43 private int mType; 44 private boolean mAscending; 45 public static final int TYPE_STRING = 0; 46 public static final int TYPE_NUMERIC = 1; 47 48 private DataSetObserver mObserver = new DataSetObserver() { 49 @Override 50 public void onChanged() { 51 // Reset our position so the optimizations in move-related code 52 // don't screw us over 53 mPos = -1; 54 } 55 56 @Override 57 public void onInvalidated() { 58 mPos = -1; 59 } 60 }; 61 private int mCursorIndex; 62 63 public SortCursor(Cursor[] cursors, String sortcolumn, int type, boolean ascending) { 64 mAscending = ascending; 65 mCursors = cursors; 66 mType = type; 67 int length = mCursors.length; 68 mSortColumns = new int[length]; 69 for (int i = 0; i < length; i++) { 70 if (mCursors[i] == null) { 71 continue; 72 } 73 // Register ourself as a data set observer 74 mCursors[i].registerDataSetObserver(mObserver); 75 mCursors[i].moveToFirst(); 76 // We don't catch the exception. 77 mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn); 78 } 79 mCursor = null; 80 if (type == TYPE_STRING) { 81 String smallest = ""; 82 for (int j = 0; j < length; j++) { 83 if (mCursors[j] == null || mCursors[j].isAfterLast()) 84 continue; 85 String current = mCursors[j].getString(mSortColumns[j]); 86 if (mCursor == null || current == null || current.compareToIgnoreCase(smallest) < 0) { 87 smallest = current; 88 mCursor = mCursors[j]; 89 mCursorIndex = j; 90 } 91 } 92 } else { 93 long smallest = (ascending) ? Long.MAX_VALUE : Long.MIN_VALUE; 94 for (int j = 0; j < length; j++) { 95 if (mCursors[j] == null || mCursors[j].isAfterLast()) { 96 continue; 97 } 98 long current = mCursors[j].getLong(mSortColumns[j]); 99 boolean comparison = (ascending) ? (current < smallest) : (current > smallest); 100 if (mCursor == null || comparison) { 101 smallest = current; 102 mCursor = mCursors[j]; 103 mCursorIndex = j; 104 } 105 } 106 } 107 108 for (int i = mRowNumCache.length - 1; i >= 0; i--) { 109 mRowNumCache[i] = -2; 110 } 111 mCurRowNumCache = new int[ROWCACHESIZE][length]; 112 } 113 114 @Override 115 public int getCount() { 116 int count = 0; 117 int length = mCursors.length; 118 for (int i = 0; i < length; i++) { 119 if (mCursors[i] != null) { 120 count += mCursors[i].getCount(); 121 } 122 } 123 return count; 124 } 125 126 @Override 127 public boolean onMove(int oldPosition, int newPosition) { 128 if (oldPosition == newPosition) 129 return true; 130 131 /* 132 * Find the right cursor Because the client of this cursor (the 133 * listadapter/view) tends to jump around in the cursor somewhat, a 134 * simple cache strategy is used to avoid having to search all cursors 135 * from the start. TODO: investigate strategies for optimizing random 136 * access and reverse-order access. 137 */ 138 139 int cache_entry = newPosition % ROWCACHESIZE; 140 141 if (mRowNumCache[cache_entry] == newPosition) { 142 int which = mCursorCache[cache_entry]; 143 mCursor = mCursors[which]; 144 mCursorIndex = which; 145 if (mCursor == null) { 146 Log.w(TAG, "onMove: cache results in a null cursor."); 147 return false; 148 } 149 mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]); 150 mLastCacheHit = cache_entry; 151 return true; 152 } 153 154 mCursor = null; 155 int length = mCursors.length; 156 157 if (mLastCacheHit >= 0) { 158 for (int i = 0; i < length; i++) { 159 if (mCursors[i] == null) 160 continue; 161 mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]); 162 } 163 } 164 165 if (newPosition < oldPosition || oldPosition == -1) { 166 for (int i = 0; i < length; i++) { 167 if (mCursors[i] == null) 168 continue; 169 mCursors[i].moveToFirst(); 170 } 171 oldPosition = 0; 172 } 173 if (oldPosition < 0) { 174 oldPosition = 0; 175 } 176 177 // search forward to the new position 178 int smallestIdx = -1; 179 if (mType == TYPE_STRING) { 180 for (int i = oldPosition; i <= newPosition; i++) { 181 String smallest = ""; 182 smallestIdx = -1; 183 for (int j = 0; j < length; j++) { 184 if (mCursors[j] == null || mCursors[j].isAfterLast()) { 185 continue; 186 } 187 String current = mCursors[j].getString(mSortColumns[j]); 188 if (smallestIdx < 0 || current == null || current.compareToIgnoreCase(smallest) < 0) { 189 smallest = current; 190 smallestIdx = j; 191 } 192 } 193 if (i == newPosition) { 194 break; 195 } 196 if (mCursors[smallestIdx] != null) { 197 mCursors[smallestIdx].moveToNext(); 198 } 199 } 200 } else { 201 for (int i = oldPosition; i <= newPosition; i++) { 202 long smallest = (mAscending) ? Long.MAX_VALUE : Long.MIN_VALUE; 203 smallestIdx = -1; 204 for (int j = 0; j < length; j++) { 205 if (mCursors[j] == null || mCursors[j].isAfterLast()) { 206 continue; 207 } 208 long current = mCursors[j].getLong(mSortColumns[j]); 209 boolean comparison = (mAscending) ? current < smallest : current > smallest; 210 if (smallestIdx < 0 || comparison) { 211 smallest = current; 212 smallestIdx = j; 213 } 214 } 215 if (i == newPosition) { 216 break; 217 } 218 if (mCursors[smallestIdx] != null) { 219 mCursors[smallestIdx].moveToNext(); 220 } 221 } 222 } 223 mCursor = mCursors[smallestIdx]; 224 mCursorIndex = smallestIdx; 225 mRowNumCache[cache_entry] = newPosition; 226 mCursorCache[cache_entry] = smallestIdx; 227 for (int i = 0; i < length; i++) { 228 if (mCursors[i] != null) { 229 mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition(); 230 } 231 } 232 mLastCacheHit = -1; 233 return true; 234 } 235 236 @Override 237 public String getString(int column) { 238 return mCursor.getString(column); 239 } 240 241 @Override 242 public short getShort(int column) { 243 return mCursor.getShort(column); 244 } 245 246 @Override 247 public int getInt(int column) { 248 return mCursor.getInt(column); 249 } 250 251 @Override 252 public long getLong(int column) { 253 return mCursor.getLong(column); 254 } 255 256 @Override 257 public float getFloat(int column) { 258 return mCursor.getFloat(column); 259 } 260 261 @Override 262 public double getDouble(int column) { 263 return mCursor.getDouble(column); 264 } 265 266 @Override 267 public boolean isNull(int column) { 268 return mCursor.isNull(column); 269 } 270 271 @Override 272 public byte[] getBlob(int column) { 273 return mCursor.getBlob(column); 274 } 275 276 @Override 277 public String[] getColumnNames() { 278 if (mCursor != null) { 279 return mCursor.getColumnNames(); 280 } else { 281 // All of the cursors may be empty, but they can still return 282 // this information. 283 int length = mCursors.length; 284 for (int i = 0; i < length; i++) { 285 if (mCursors[i] != null) { 286 return mCursors[i].getColumnNames(); 287 } 288 } 289 throw new IllegalStateException("No cursor that can return names"); 290 } 291 } 292 293 @Override 294 public void deactivate() { 295 int length = mCursors.length; 296 for (int i = 0; i < length; i++) { 297 if (mCursors[i] == null) 298 continue; 299 mCursors[i].deactivate(); 300 } 301 } 302 303 @Override 304 public void close() { 305 int length = mCursors.length; 306 for (int i = 0; i < length; i++) { 307 if (mCursors[i] == null) 308 continue; 309 mCursors[i].close(); 310 } 311 } 312 313 @Override 314 public void registerDataSetObserver(DataSetObserver observer) { 315 int length = mCursors.length; 316 for (int i = 0; i < length; i++) { 317 if (mCursors[i] != null) { 318 mCursors[i].registerDataSetObserver(observer); 319 } 320 } 321 } 322 323 @Override 324 public void unregisterDataSetObserver(DataSetObserver observer) { 325 int length = mCursors.length; 326 for (int i = 0; i < length; i++) { 327 if (mCursors[i] != null) { 328 mCursors[i].unregisterDataSetObserver(observer); 329 } 330 } 331 } 332 333 @Override 334 public boolean requery() { 335 int length = mCursors.length; 336 for (int i = 0; i < length; i++) { 337 if (mCursors[i] == null) 338 continue; 339 340 if (mCursors[i].requery() == false) { 341 return false; 342 } 343 } 344 345 return true; 346 } 347 348 public int getCurrentCursorIndex() { 349 return mCursorIndex; 350 } 351 } 352