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