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