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