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