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