AbstractCursor.java revision 2589716964f99fd0ee29a9b295584c277e23f34f
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 * @hide 108 * @deprecated 109 */ 110 public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) { 111 return false; 112 } 113 114 /** 115 * @hide 116 * @deprecated 117 */ 118 public boolean deleteRow() { 119 return false; 120 } 121 122 /** 123 * This function is called every time the cursor is successfully scrolled 124 * to a new position, giving the subclass a chance to update any state it 125 * may have. If it returns false the move function will also do so and the 126 * cursor will scroll to the beforeFirst position. 127 * 128 * @param oldPosition the position that we're moving from 129 * @param newPosition the position that we're moving to 130 * @return true if the move is successful, false otherwise 131 */ 132 public boolean onMove(int oldPosition, int newPosition) { 133 return true; 134 } 135 136 137 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 138 // Default implementation, uses getString 139 String result = getString(columnIndex); 140 if (result != null) { 141 char[] data = buffer.data; 142 if (data == null || data.length < result.length()) { 143 buffer.data = result.toCharArray(); 144 } else { 145 result.getChars(0, result.length(), data, 0); 146 } 147 buffer.sizeCopied = result.length(); 148 } 149 } 150 151 /* -------------------------------------------------------- */ 152 /* Implementation */ 153 public AbstractCursor() { 154 mPos = -1; 155 mRowIdColumnIndex = -1; 156 mCurrentRowID = null; 157 mUpdatedRows = new HashMap<Long, Map<String, Object>>(); 158 } 159 160 public final int getPosition() { 161 return mPos; 162 } 163 164 public final boolean moveToPosition(int position) { 165 // Make sure position isn't past the end of the cursor 166 final int count = getCount(); 167 if (position >= count) { 168 mPos = count; 169 return false; 170 } 171 172 // Make sure position isn't before the beginning of the cursor 173 if (position < 0) { 174 mPos = -1; 175 return false; 176 } 177 178 // Check for no-op moves, and skip the rest of the work for them 179 if (position == mPos) { 180 return true; 181 } 182 183 boolean result = onMove(mPos, position); 184 if (result == false) { 185 mPos = -1; 186 } else { 187 mPos = position; 188 if (mRowIdColumnIndex != -1) { 189 mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex)); 190 } 191 } 192 193 return result; 194 } 195 196 /** 197 * Copy data from cursor to CursorWindow 198 * @param position start position of data 199 * @param window 200 */ 201 public void fillWindow(int position, CursorWindow window) { 202 if (position < 0 || position > getCount()) { 203 return; 204 } 205 window.acquireReference(); 206 try { 207 int oldpos = mPos; 208 mPos = position - 1; 209 window.clear(); 210 window.setStartPosition(position); 211 int columnNum = getColumnCount(); 212 window.setNumColumns(columnNum); 213 while (moveToNext() && window.allocRow()) { 214 for (int i = 0; i < columnNum; i++) { 215 String field = getString(i); 216 if (field != null) { 217 if (!window.putString(field, mPos, i)) { 218 window.freeLastRow(); 219 break; 220 } 221 } else { 222 if (!window.putNull(mPos, i)) { 223 window.freeLastRow(); 224 break; 225 } 226 } 227 } 228 } 229 230 mPos = oldpos; 231 } catch (IllegalStateException e){ 232 // simply ignore it 233 } finally { 234 window.releaseReference(); 235 } 236 } 237 238 public final boolean move(int offset) { 239 return moveToPosition(mPos + offset); 240 } 241 242 public final boolean moveToFirst() { 243 return moveToPosition(0); 244 } 245 246 public final boolean moveToLast() { 247 return moveToPosition(getCount() - 1); 248 } 249 250 public final boolean moveToNext() { 251 return moveToPosition(mPos + 1); 252 } 253 254 public final boolean moveToPrevious() { 255 return moveToPosition(mPos - 1); 256 } 257 258 public final boolean isFirst() { 259 return mPos == 0 && getCount() != 0; 260 } 261 262 public final boolean isLast() { 263 int cnt = getCount(); 264 return mPos == (cnt - 1) && cnt != 0; 265 } 266 267 public final boolean isBeforeFirst() { 268 if (getCount() == 0) { 269 return true; 270 } 271 return mPos == -1; 272 } 273 274 public final boolean isAfterLast() { 275 if (getCount() == 0) { 276 return true; 277 } 278 return mPos == getCount(); 279 } 280 281 public int getColumnIndex(String columnName) { 282 // Hack according to bug 903852 283 final int periodIndex = columnName.lastIndexOf('.'); 284 if (periodIndex != -1) { 285 Exception e = new Exception(); 286 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 287 columnName = columnName.substring(periodIndex + 1); 288 } 289 290 String columnNames[] = getColumnNames(); 291 int length = columnNames.length; 292 for (int i = 0; i < length; i++) { 293 if (columnNames[i].equalsIgnoreCase(columnName)) { 294 return i; 295 } 296 } 297 298 if (Config.LOGV) { 299 if (getCount() > 0) { 300 Log.w("AbstractCursor", "Unknown column " + columnName); 301 } 302 } 303 return -1; 304 } 305 306 public int getColumnIndexOrThrow(String columnName) { 307 final int index = getColumnIndex(columnName); 308 if (index < 0) { 309 throw new IllegalArgumentException("column '" + columnName + "' does not exist"); 310 } 311 return index; 312 } 313 314 public String getColumnName(int columnIndex) { 315 return getColumnNames()[columnIndex]; 316 } 317 318 /** 319 * @hide 320 * @deprecated 321 */ 322 public boolean updateBlob(int columnIndex, byte[] value) { 323 return update(columnIndex, value); 324 } 325 326 /** 327 * @hide 328 * @deprecated 329 */ 330 public boolean updateString(int columnIndex, String value) { 331 return update(columnIndex, value); 332 } 333 334 /** 335 * @hide 336 * @deprecated 337 */ 338 public boolean updateShort(int columnIndex, short value) { 339 return update(columnIndex, Short.valueOf(value)); 340 } 341 342 /** 343 * @hide 344 * @deprecated 345 */ 346 public boolean updateInt(int columnIndex, int value) { 347 return update(columnIndex, Integer.valueOf(value)); 348 } 349 350 /** 351 * @hide 352 * @deprecated 353 */ 354 public boolean updateLong(int columnIndex, long value) { 355 return update(columnIndex, Long.valueOf(value)); 356 } 357 358 /** 359 * @hide 360 * @deprecated 361 */ 362 public boolean updateFloat(int columnIndex, float value) { 363 return update(columnIndex, Float.valueOf(value)); 364 } 365 366 /** 367 * @hide 368 * @deprecated 369 */ 370 public boolean updateDouble(int columnIndex, double value) { 371 return update(columnIndex, Double.valueOf(value)); 372 } 373 374 /** 375 * @hide 376 * @deprecated 377 */ 378 public boolean updateToNull(int columnIndex) { 379 return update(columnIndex, null); 380 } 381 382 /** 383 * @hide 384 * @deprecated 385 */ 386 public boolean update(int columnIndex, Object obj) { 387 if (!supportsUpdates()) { 388 return false; 389 } 390 391 // Long.valueOf() returns null sometimes! 392// Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); 393 Long rowid = new Long(getLong(mRowIdColumnIndex)); 394 if (rowid == null) { 395 throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex); 396 } 397 398 synchronized(mUpdatedRows) { 399 Map<String, Object> row = mUpdatedRows.get(rowid); 400 if (row == null) { 401 row = new HashMap<String, Object>(); 402 mUpdatedRows.put(rowid, row); 403 } 404 row.put(getColumnNames()[columnIndex], obj); 405 } 406 407 return true; 408 } 409 410 /** 411 * Returns <code>true</code> if there are pending updates that have not yet been committed. 412 * 413 * @return <code>true</code> if there are pending updates that have not yet been committed. 414 * @hide 415 * @deprecated 416 */ 417 public boolean hasUpdates() { 418 synchronized(mUpdatedRows) { 419 return mUpdatedRows.size() > 0; 420 } 421 } 422 423 /** 424 * @hide 425 * @deprecated 426 */ 427 public void abortUpdates() { 428 synchronized(mUpdatedRows) { 429 mUpdatedRows.clear(); 430 } 431 } 432 433 /** 434 * @hide 435 * @deprecated 436 */ 437 public boolean commitUpdates() { 438 return commitUpdates(null); 439 } 440 441 /** 442 * @hide 443 * @deprecated 444 */ 445 public boolean supportsUpdates() { 446 return mRowIdColumnIndex != -1; 447 } 448 449 public void registerContentObserver(ContentObserver observer) { 450 mContentObservable.registerObserver(observer); 451 } 452 453 public void unregisterContentObserver(ContentObserver observer) { 454 // cursor will unregister all observers when it close 455 if (!mClosed) { 456 mContentObservable.unregisterObserver(observer); 457 } 458 } 459 460 /** 461 * This is hidden until the data set change model has been re-evaluated. 462 * @hide 463 */ 464 protected void notifyDataSetChange() { 465 mDataSetObservable.notifyChanged(); 466 } 467 468 /** 469 * This is hidden until the data set change model has been re-evaluated. 470 * @hide 471 */ 472 protected DataSetObservable getDataSetObservable() { 473 return mDataSetObservable; 474 475 } 476 public void registerDataSetObserver(DataSetObserver observer) { 477 mDataSetObservable.registerObserver(observer); 478 479 } 480 481 public void unregisterDataSetObserver(DataSetObserver observer) { 482 mDataSetObservable.unregisterObserver(observer); 483 } 484 485 /** 486 * Subclasses must call this method when they finish committing updates to notify all 487 * observers. 488 * 489 * @param selfChange 490 */ 491 protected void onChange(boolean selfChange) { 492 synchronized (mSelfObserverLock) { 493 mContentObservable.dispatchChange(selfChange); 494 if (mNotifyUri != null && selfChange) { 495 mContentResolver.notifyChange(mNotifyUri, mSelfObserver); 496 } 497 } 498 } 499 500 /** 501 * Specifies a content URI to watch for changes. 502 * 503 * @param cr The content resolver from the caller's context. 504 * @param notifyUri The URI to watch for changes. This can be a 505 * specific row URI, or a base URI for a whole class of content. 506 */ 507 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 508 synchronized (mSelfObserverLock) { 509 mNotifyUri = notifyUri; 510 mContentResolver = cr; 511 if (mSelfObserver != null) { 512 mContentResolver.unregisterContentObserver(mSelfObserver); 513 } 514 mSelfObserver = new SelfContentObserver(this); 515 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 516 mSelfObserverRegistered = true; 517 } 518 } 519 520 public boolean getWantsAllOnMoveCalls() { 521 return false; 522 } 523 524 public Bundle getExtras() { 525 return Bundle.EMPTY; 526 } 527 528 public Bundle respond(Bundle extras) { 529 return Bundle.EMPTY; 530 } 531 532 /** 533 * This function returns true if the field has been updated and is 534 * used in conjunction with {@link #getUpdatedField} to allow subclasses to 535 * support reading uncommitted updates. NOTE: This function and 536 * {@link #getUpdatedField} should be called together inside of a 537 * block synchronized on mUpdatedRows. 538 * 539 * @param columnIndex the column index of the field to check 540 * @return true if the field has been updated, false otherwise 541 */ 542 protected boolean isFieldUpdated(int columnIndex) { 543 if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) { 544 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 545 if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) { 546 return true; 547 } 548 } 549 return false; 550 } 551 552 /** 553 * This function returns the uncommitted updated value for the field 554 * at columnIndex. NOTE: This function and {@link #isFieldUpdated} should 555 * be called together inside of a block synchronized on mUpdatedRows. 556 * 557 * @param columnIndex the column index of the field to retrieve 558 * @return the updated value 559 */ 560 protected Object getUpdatedField(int columnIndex) { 561 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 562 return updates.get(getColumnNames()[columnIndex]); 563 } 564 565 /** 566 * This function throws CursorIndexOutOfBoundsException if 567 * the cursor position is out of bounds. Subclass implementations of 568 * the get functions should call this before attempting 569 * to retrieve data. 570 * 571 * @throws CursorIndexOutOfBoundsException 572 */ 573 protected void checkPosition() { 574 if (-1 == mPos || getCount() == mPos) { 575 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 576 } 577 } 578 579 @Override 580 protected void finalize() { 581 if (mSelfObserver != null && mSelfObserverRegistered == true) { 582 mContentResolver.unregisterContentObserver(mSelfObserver); 583 } 584 } 585 586 /** 587 * Cursors use this class to track changes others make to their URI. 588 */ 589 protected static class SelfContentObserver extends ContentObserver { 590 WeakReference<AbstractCursor> mCursor; 591 592 public SelfContentObserver(AbstractCursor cursor) { 593 super(null); 594 mCursor = new WeakReference<AbstractCursor>(cursor); 595 } 596 597 @Override 598 public boolean deliverSelfNotifications() { 599 return false; 600 } 601 602 @Override 603 public void onChange(boolean selfChange) { 604 AbstractCursor cursor = mCursor.get(); 605 if (cursor != null) { 606 cursor.onChange(false); 607 } 608 } 609 } 610 611 /** 612 * This HashMap contains a mapping from Long rowIDs to another Map 613 * that maps from String column names to new values. A NULL value means to 614 * remove an existing value, and all numeric values are in their class 615 * forms, i.e. Integer, Long, Float, etc. 616 */ 617 protected HashMap<Long, Map<String, Object>> mUpdatedRows; 618 619 /** 620 * This must be set to the index of the row ID column by any 621 * subclass that wishes to support updates. 622 */ 623 protected int mRowIdColumnIndex; 624 625 protected int mPos; 626 protected Long mCurrentRowID; 627 protected ContentResolver mContentResolver; 628 protected boolean mClosed = false; 629 private Uri mNotifyUri; 630 private ContentObserver mSelfObserver; 631 final private Object mSelfObserverLock = new Object(); 632 private boolean mSelfObserverRegistered; 633} 634