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