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