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