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