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