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