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