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