AbstractCursor.java revision 7cd51efcbd2d083bf577696591ef1769034f7e2f
1/* 2 * Copyright (C) 2006 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 android.database; 18 19import android.content.ContentResolver; 20import android.net.Uri; 21import android.os.Bundle; 22import android.util.Config; 23import android.util.Log; 24 25import java.lang.ref.WeakReference; 26import java.util.HashMap; 27import java.util.Map; 28 29 30/** 31 * This is an abstract cursor class that handles a lot of the common code 32 * that all cursors need to deal with and is provided for convenience reasons. 33 */ 34public abstract class AbstractCursor implements CrossProcessCursor { 35 private static final String TAG = "Cursor"; 36 37 DataSetObservable mDataSetObservable = new DataSetObservable(); 38 ContentObservable mContentObservable = new ContentObservable(); 39 40 /* -------------------------------------------------------- */ 41 /* These need to be implemented by subclasses */ 42 abstract public int getCount(); 43 44 abstract public String[] getColumnNames(); 45 46 abstract public String getString(int column); 47 abstract public short getShort(int column); 48 abstract public int getInt(int column); 49 abstract public long getLong(int column); 50 abstract public float getFloat(int column); 51 abstract public double getDouble(int column); 52 abstract public boolean isNull(int column); 53 54 // TODO implement getBlob in all cursor types 55 public byte[] getBlob(int column) { 56 throw new UnsupportedOperationException("getBlob is not supported"); 57 } 58 /* -------------------------------------------------------- */ 59 /* Methods that may optionally be implemented by subclasses */ 60 61 /** 62 * returns a pre-filled window, return NULL if no such window 63 */ 64 public CursorWindow getWindow() { 65 return null; 66 } 67 68 public int getColumnCount() { 69 return getColumnNames().length; 70 } 71 72 public void deactivate() { 73 deactivateInternal(); 74 } 75 76 /** 77 * @hide 78 */ 79 public void deactivateInternal() { 80 if (mSelfObserver != null) { 81 mContentResolver.unregisterContentObserver(mSelfObserver); 82 mSelfObserverRegistered = false; 83 } 84 mDataSetObservable.notifyInvalidated(); 85 } 86 87 public boolean requery() { 88 if (mSelfObserver != null && mSelfObserverRegistered == false) { 89 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 90 mSelfObserverRegistered = true; 91 } 92 mDataSetObservable.notifyChanged(); 93 return true; 94 } 95 96 public boolean isClosed() { 97 return mClosed; 98 } 99 100 public void close() { 101 mClosed = true; 102 mContentObservable.unregisterAll(); 103 deactivateInternal(); 104 } 105 106 /** 107 * This function is called every time the cursor is successfully scrolled 108 * to a new position, giving the subclass a chance to update any state it 109 * may have. If it returns false the move function will also do so and the 110 * cursor will scroll to the beforeFirst position. 111 * 112 * @param oldPosition the position that we're moving from 113 * @param newPosition the position that we're moving to 114 * @return true if the move is successful, false otherwise 115 */ 116 public boolean onMove(int oldPosition, int newPosition) { 117 return true; 118 } 119 120 121 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 122 // Default implementation, uses getString 123 String result = getString(columnIndex); 124 if (result != null) { 125 char[] data = buffer.data; 126 if (data == null || data.length < result.length()) { 127 buffer.data = result.toCharArray(); 128 } else { 129 result.getChars(0, result.length(), data, 0); 130 } 131 buffer.sizeCopied = result.length(); 132 } 133 } 134 135 /* -------------------------------------------------------- */ 136 /* Implementation */ 137 public AbstractCursor() { 138 mPos = -1; 139 mRowIdColumnIndex = -1; 140 mCurrentRowID = null; 141 mUpdatedRows = new HashMap<Long, Map<String, Object>>(); 142 } 143 144 public final int getPosition() { 145 return mPos; 146 } 147 148 public final boolean moveToPosition(int position) { 149 // Make sure position isn't past the end of the cursor 150 final int count = getCount(); 151 if (position >= count) { 152 mPos = count; 153 return false; 154 } 155 156 // Make sure position isn't before the beginning of the cursor 157 if (position < 0) { 158 mPos = -1; 159 return false; 160 } 161 162 // Check for no-op moves, and skip the rest of the work for them 163 if (position == mPos) { 164 return true; 165 } 166 167 boolean result = onMove(mPos, position); 168 if (result == false) { 169 mPos = -1; 170 } else { 171 mPos = position; 172 if (mRowIdColumnIndex != -1) { 173 mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex)); 174 } 175 } 176 177 return result; 178 } 179 180 /** 181 * Copy data from cursor to CursorWindow 182 * @param position start position of data 183 * @param window 184 */ 185 public void fillWindow(int position, CursorWindow window) { 186 if (position < 0 || position > getCount()) { 187 return; 188 } 189 window.acquireReference(); 190 try { 191 int oldpos = mPos; 192 mPos = position - 1; 193 window.clear(); 194 window.setStartPosition(position); 195 int columnNum = getColumnCount(); 196 window.setNumColumns(columnNum); 197 while (moveToNext() && window.allocRow()) { 198 for (int i = 0; i < columnNum; i++) { 199 String field = getString(i); 200 if (field != null) { 201 if (!window.putString(field, mPos, i)) { 202 window.freeLastRow(); 203 break; 204 } 205 } else { 206 if (!window.putNull(mPos, i)) { 207 window.freeLastRow(); 208 break; 209 } 210 } 211 } 212 } 213 214 mPos = oldpos; 215 } catch (IllegalStateException e){ 216 // simply ignore it 217 } finally { 218 window.releaseReference(); 219 } 220 } 221 222 public final boolean move(int offset) { 223 return moveToPosition(mPos + offset); 224 } 225 226 public final boolean moveToFirst() { 227 return moveToPosition(0); 228 } 229 230 public final boolean moveToLast() { 231 return moveToPosition(getCount() - 1); 232 } 233 234 public final boolean moveToNext() { 235 return moveToPosition(mPos + 1); 236 } 237 238 public final boolean moveToPrevious() { 239 return moveToPosition(mPos - 1); 240 } 241 242 public final boolean isFirst() { 243 return mPos == 0 && getCount() != 0; 244 } 245 246 public final boolean isLast() { 247 int cnt = getCount(); 248 return mPos == (cnt - 1) && cnt != 0; 249 } 250 251 public final boolean isBeforeFirst() { 252 if (getCount() == 0) { 253 return true; 254 } 255 return mPos == -1; 256 } 257 258 public final boolean isAfterLast() { 259 if (getCount() == 0) { 260 return true; 261 } 262 return mPos == getCount(); 263 } 264 265 public int getColumnIndex(String columnName) { 266 // Hack according to bug 903852 267 final int periodIndex = columnName.lastIndexOf('.'); 268 if (periodIndex != -1) { 269 Exception e = new Exception(); 270 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 271 columnName = columnName.substring(periodIndex + 1); 272 } 273 274 String columnNames[] = getColumnNames(); 275 int length = columnNames.length; 276 for (int i = 0; i < length; i++) { 277 if (columnNames[i].equalsIgnoreCase(columnName)) { 278 return i; 279 } 280 } 281 282 if (Config.LOGV) { 283 if (getCount() > 0) { 284 Log.w("AbstractCursor", "Unknown column " + columnName); 285 } 286 } 287 return -1; 288 } 289 290 public int getColumnIndexOrThrow(String columnName) { 291 final int index = getColumnIndex(columnName); 292 if (index < 0) { 293 throw new IllegalArgumentException("column '" + columnName + "' does not exist"); 294 } 295 return index; 296 } 297 298 public String getColumnName(int columnIndex) { 299 return getColumnNames()[columnIndex]; 300 } 301 302 public void registerContentObserver(ContentObserver observer) { 303 mContentObservable.registerObserver(observer); 304 } 305 306 public void unregisterContentObserver(ContentObserver observer) { 307 // cursor will unregister all observers when it close 308 if (!mClosed) { 309 mContentObservable.unregisterObserver(observer); 310 } 311 } 312 313 /** 314 * This is hidden until the data set change model has been re-evaluated. 315 * @hide 316 */ 317 protected void notifyDataSetChange() { 318 mDataSetObservable.notifyChanged(); 319 } 320 321 /** 322 * This is hidden until the data set change model has been re-evaluated. 323 * @hide 324 */ 325 protected DataSetObservable getDataSetObservable() { 326 return mDataSetObservable; 327 328 } 329 public void registerDataSetObserver(DataSetObserver observer) { 330 mDataSetObservable.registerObserver(observer); 331 332 } 333 334 public void unregisterDataSetObserver(DataSetObserver observer) { 335 mDataSetObservable.unregisterObserver(observer); 336 } 337 338 /** 339 * Subclasses must call this method when they finish committing updates to notify all 340 * observers. 341 * 342 * @param selfChange 343 */ 344 protected void onChange(boolean selfChange) { 345 synchronized (mSelfObserverLock) { 346 mContentObservable.dispatchChange(selfChange); 347 if (mNotifyUri != null && selfChange) { 348 mContentResolver.notifyChange(mNotifyUri, mSelfObserver); 349 } 350 } 351 } 352 353 /** 354 * Specifies a content URI to watch for changes. 355 * 356 * @param cr The content resolver from the caller's context. 357 * @param notifyUri The URI to watch for changes. This can be a 358 * specific row URI, or a base URI for a whole class of content. 359 */ 360 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 361 synchronized (mSelfObserverLock) { 362 mNotifyUri = notifyUri; 363 mContentResolver = cr; 364 if (mSelfObserver != null) { 365 mContentResolver.unregisterContentObserver(mSelfObserver); 366 } 367 mSelfObserver = new SelfContentObserver(this); 368 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 369 mSelfObserverRegistered = true; 370 } 371 } 372 373 public boolean getWantsAllOnMoveCalls() { 374 return false; 375 } 376 377 public Bundle getExtras() { 378 return Bundle.EMPTY; 379 } 380 381 public Bundle respond(Bundle extras) { 382 return Bundle.EMPTY; 383 } 384 385 /** 386 * This function returns true if the field has been updated and is 387 * used in conjunction with {@link #getUpdatedField} to allow subclasses to 388 * support reading uncommitted updates. NOTE: This function and 389 * {@link #getUpdatedField} should be called together inside of a 390 * block synchronized on mUpdatedRows. 391 * 392 * @param columnIndex the column index of the field to check 393 * @return true if the field has been updated, false otherwise 394 */ 395 protected boolean isFieldUpdated(int columnIndex) { 396 if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) { 397 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 398 if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) { 399 return true; 400 } 401 } 402 return false; 403 } 404 405 /** 406 * This function returns the uncommitted updated value for the field 407 * at columnIndex. NOTE: This function and {@link #isFieldUpdated} should 408 * be called together inside of a block synchronized on mUpdatedRows. 409 * 410 * @param columnIndex the column index of the field to retrieve 411 * @return the updated value 412 */ 413 protected Object getUpdatedField(int columnIndex) { 414 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 415 return updates.get(getColumnNames()[columnIndex]); 416 } 417 418 /** 419 * This function throws CursorIndexOutOfBoundsException if 420 * the cursor position is out of bounds. Subclass implementations of 421 * the get functions should call this before attempting 422 * to retrieve data. 423 * 424 * @throws CursorIndexOutOfBoundsException 425 */ 426 protected void checkPosition() { 427 if (-1 == mPos || getCount() == mPos) { 428 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 429 } 430 } 431 432 @Override 433 protected void finalize() { 434 if (mSelfObserver != null && mSelfObserverRegistered == true) { 435 mContentResolver.unregisterContentObserver(mSelfObserver); 436 } 437 } 438 439 /** 440 * Cursors use this class to track changes others make to their URI. 441 */ 442 protected static class SelfContentObserver extends ContentObserver { 443 WeakReference<AbstractCursor> mCursor; 444 445 public SelfContentObserver(AbstractCursor cursor) { 446 super(null); 447 mCursor = new WeakReference<AbstractCursor>(cursor); 448 } 449 450 @Override 451 public boolean deliverSelfNotifications() { 452 return false; 453 } 454 455 @Override 456 public void onChange(boolean selfChange) { 457 AbstractCursor cursor = mCursor.get(); 458 if (cursor != null) { 459 cursor.onChange(false); 460 } 461 } 462 } 463 464 /** 465 * This HashMap contains a mapping from Long rowIDs to another Map 466 * that maps from String column names to new values. A NULL value means to 467 * remove an existing value, and all numeric values are in their class 468 * forms, i.e. Integer, Long, Float, etc. 469 */ 470 protected HashMap<Long, Map<String, Object>> mUpdatedRows; 471 472 /** 473 * This must be set to the index of the row ID column by any 474 * subclass that wishes to support updates. 475 */ 476 protected int mRowIdColumnIndex; 477 478 protected int mPos; 479 protected Long mCurrentRowID; 480 protected ContentResolver mContentResolver; 481 protected boolean mClosed = false; 482 private Uri mNotifyUri; 483 private ContentObserver mSelfObserver; 484 final private Object mSelfObserverLock = new Object(); 485 private boolean mSelfObserverRegistered; 486} 487