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