AbstractCursor.java revision bd4c9f13022e875c8b420248214482a5f5b46618
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 public void registerDataSetObserver(DataSetObserver observer) { 288 mDataSetObservable.registerObserver(observer); 289 } 290 291 public void unregisterDataSetObserver(DataSetObserver observer) { 292 mDataSetObservable.unregisterObserver(observer); 293 } 294 295 /** 296 * Subclasses must call this method when they finish committing updates to notify all 297 * observers. 298 * 299 * @param selfChange 300 */ 301 protected void onChange(boolean selfChange) { 302 synchronized (mSelfObserverLock) { 303 mContentObservable.dispatchChange(selfChange); 304 if (mNotifyUri != null && selfChange) { 305 mContentResolver.notifyChange(mNotifyUri, mSelfObserver); 306 } 307 } 308 } 309 310 /** 311 * Specifies a content URI to watch for changes. 312 * 313 * @param cr The content resolver from the caller's context. 314 * @param notifyUri The URI to watch for changes. This can be a 315 * specific row URI, or a base URI for a whole class of content. 316 */ 317 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 318 synchronized (mSelfObserverLock) { 319 mNotifyUri = notifyUri; 320 mContentResolver = cr; 321 if (mSelfObserver != null) { 322 mContentResolver.unregisterContentObserver(mSelfObserver); 323 } 324 mSelfObserver = new SelfContentObserver(this); 325 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 326 mSelfObserverRegistered = true; 327 } 328 } 329 330 public Uri getNotificationUri() { 331 return mNotifyUri; 332 } 333 334 public boolean getWantsAllOnMoveCalls() { 335 return false; 336 } 337 338 /** 339 * Sets a {@link Bundle} that will be returned by {@link #getExtras()}. <code>null</code> will 340 * be converted into {@link Bundle#EMPTY}. 341 * 342 * @param extras {@link Bundle} to set. 343 * @hide 344 */ 345 public void setExtras(Bundle extras) { 346 mExtras = (extras == null) ? Bundle.EMPTY : extras; 347 } 348 349 public Bundle getExtras() { 350 return mExtras; 351 } 352 353 public Bundle respond(Bundle extras) { 354 return Bundle.EMPTY; 355 } 356 357 /** 358 * @deprecated Always returns false since Cursors do not support updating rows 359 */ 360 @Deprecated 361 protected boolean isFieldUpdated(int columnIndex) { 362 return false; 363 } 364 365 /** 366 * @deprecated Always returns null since Cursors do not support updating rows 367 */ 368 @Deprecated 369 protected Object getUpdatedField(int columnIndex) { 370 return null; 371 } 372 373 /** 374 * This function throws CursorIndexOutOfBoundsException if 375 * the cursor position is out of bounds. Subclass implementations of 376 * the get functions should call this before attempting 377 * to retrieve data. 378 * 379 * @throws CursorIndexOutOfBoundsException 380 */ 381 protected void checkPosition() { 382 if (-1 == mPos || getCount() == mPos) { 383 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 384 } 385 } 386 387 @Override 388 protected void finalize() { 389 if (mSelfObserver != null && mSelfObserverRegistered == true) { 390 mContentResolver.unregisterContentObserver(mSelfObserver); 391 } 392 } 393 394 /** 395 * Cursors use this class to track changes others make to their URI. 396 */ 397 protected static class SelfContentObserver extends ContentObserver { 398 WeakReference<AbstractCursor> mCursor; 399 400 public SelfContentObserver(AbstractCursor cursor) { 401 super(null); 402 mCursor = new WeakReference<AbstractCursor>(cursor); 403 } 404 405 @Override 406 public boolean deliverSelfNotifications() { 407 return false; 408 } 409 410 @Override 411 public void onChange(boolean selfChange) { 412 AbstractCursor cursor = mCursor.get(); 413 if (cursor != null) { 414 cursor.onChange(false); 415 } 416 } 417 } 418 419 /** 420 * @deprecated This is never updated by this class and should not be used 421 */ 422 @Deprecated 423 protected HashMap<Long, Map<String, Object>> mUpdatedRows; 424 425 /** 426 * This must be set to the index of the row ID column by any 427 * subclass that wishes to support updates. 428 */ 429 protected int mRowIdColumnIndex; 430 431 protected int mPos; 432 /** 433 * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of 434 * the column at {@link #mRowIdColumnIndex} for the current row this cursor is 435 * pointing at. 436 */ 437 protected Long mCurrentRowID; 438 protected ContentResolver mContentResolver; 439 protected boolean mClosed = false; 440 private Uri mNotifyUri; 441 private ContentObserver mSelfObserver; 442 final private Object mSelfObserverLock = new Object(); 443 private boolean mSelfObserverRegistered; 444} 445