AbstractCursor.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.util.Config;
22import android.util.Log;
23import android.os.Bundle;
24
25import java.lang.ref.WeakReference;
26import java.lang.UnsupportedOperationException;
27import java.util.HashMap;
28import java.util.Map;
29
30
31/**
32 * This is an abstract cursor class that handles a lot of the common code
33 * that all cursors need to deal with and is provided for convenience reasons.
34 */
35public abstract class AbstractCursor implements CrossProcessCursor {
36    private static final String TAG = "Cursor";
37
38    DataSetObservable mDataSetObservable = new DataSetObservable();
39    ContentObservable mContentObservable = new ContentObservable();
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    // TODO implement getBlob in all cursor types
56    public byte[] getBlob(int column) {
57        throw new UnsupportedOperationException("getBlob is not supported");
58    }
59    /* -------------------------------------------------------- */
60    /* Methods that may optionally be implemented by subclasses */
61
62    /**
63     * returns a pre-filled window, return NULL if no such window
64     */
65    public CursorWindow getWindow() {
66        return null;
67    }
68
69    public int getColumnCount() {
70        return getColumnNames().length;
71    }
72
73    public void deactivate() {
74        deactivateInternal();
75    }
76
77    /**
78     * @hide
79     */
80    public void deactivateInternal() {
81        if (mSelfObserver != null) {
82            mContentResolver.unregisterContentObserver(mSelfObserver);
83            mSelfObserverRegistered = false;
84        }
85        mDataSetObservable.notifyInvalidated();
86    }
87
88    public boolean requery() {
89        if (mSelfObserver != null && mSelfObserverRegistered == false) {
90            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
91            mSelfObserverRegistered = true;
92        }
93        mDataSetObservable.notifyChanged();
94        return true;
95    }
96
97    public boolean isClosed() {
98        return mClosed;
99    }
100
101    public void close() {
102        mClosed = true;
103        mContentObservable.unregisterAll();
104        deactivateInternal();
105    }
106
107    /**
108     * @hide
109     * @deprecated
110     */
111    public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) {
112        return false;
113    }
114
115    /**
116     * @hide
117     * @deprecated
118     */
119    public boolean deleteRow() {
120        return false;
121    }
122
123    /**
124     * This function is called every time the cursor is successfully scrolled
125     * to a new position, giving the subclass a chance to update any state it
126     * may have. If it returns false the move function will also do so and the
127     * cursor will scroll to the beforeFirst position.
128     *
129     * @param oldPosition the position that we're moving from
130     * @param newPosition the position that we're moving to
131     * @return true if the move is successful, false otherwise
132     */
133    public boolean onMove(int oldPosition, int newPosition) {
134        return true;
135    }
136
137
138    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
139        // Default implementation, uses getString
140        String result = getString(columnIndex);
141        if (result != null) {
142            char[] data = buffer.data;
143            if (data == null || data.length < result.length()) {
144                buffer.data = result.toCharArray();
145            } else {
146                result.getChars(0, result.length(), data, 0);
147            }
148            buffer.sizeCopied = result.length();
149        }
150    }
151
152    /* -------------------------------------------------------- */
153    /* Implementation */
154    public AbstractCursor() {
155        mPos = -1;
156        mRowIdColumnIndex = -1;
157        mCurrentRowID = null;
158        mUpdatedRows = new HashMap<Long, Map<String, Object>>();
159    }
160
161    public final int getPosition() {
162        return mPos;
163    }
164
165    public final boolean moveToPosition(int position) {
166        // Make sure position isn't past the end of the cursor
167        final int count = getCount();
168        if (position >= count) {
169            mPos = count;
170            return false;
171        }
172
173        // Make sure position isn't before the beginning of the cursor
174        if (position < 0) {
175            mPos = -1;
176            return false;
177        }
178
179        // Check for no-op moves, and skip the rest of the work for them
180        if (position == mPos) {
181            return true;
182        }
183
184        boolean result = onMove(mPos, position);
185        if (result == false) {
186            mPos = -1;
187        } else {
188            mPos = position;
189            if (mRowIdColumnIndex != -1) {
190                mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
191            }
192        }
193
194        return result;
195    }
196
197    /**
198     * Copy data from cursor to CursorWindow
199     * @param position start position of data
200     * @param window
201     */
202    public void fillWindow(int position, CursorWindow window) {
203        if (position < 0 || position > getCount()) {
204            return;
205        }
206        window.acquireReference();
207        try {
208            int oldpos = mPos;
209            mPos = position - 1;
210            window.clear();
211            window.setStartPosition(position);
212            int columnNum = getColumnCount();
213            window.setNumColumns(columnNum);
214            while (moveToNext() && window.allocRow()) {
215                for (int i = 0; i < columnNum; i++) {
216                    String field = getString(i);
217                    if (field != null) {
218                        if (!window.putString(field, mPos, i)) {
219                            window.freeLastRow();
220                            break;
221                        }
222                    } else {
223                        if (!window.putNull(mPos, i)) {
224                            window.freeLastRow();
225                            break;
226                        }
227                    }
228                }
229            }
230
231            mPos = oldpos;
232        } catch (IllegalStateException e){
233            // simply ignore it
234        } finally {
235            window.releaseReference();
236        }
237    }
238
239    public final boolean move(int offset) {
240        return moveToPosition(mPos + offset);
241    }
242
243    public final boolean moveToFirst() {
244        return moveToPosition(0);
245    }
246
247    public final boolean moveToLast() {
248        return moveToPosition(getCount() - 1);
249    }
250
251    public final boolean moveToNext() {
252        return moveToPosition(mPos + 1);
253    }
254
255    public final boolean moveToPrevious() {
256        return moveToPosition(mPos - 1);
257    }
258
259    public final boolean isFirst() {
260        return mPos == 0 && getCount() != 0;
261    }
262
263    public final boolean isLast() {
264        int cnt = getCount();
265        return mPos == (cnt - 1) && cnt != 0;
266    }
267
268    public final boolean isBeforeFirst() {
269        if (getCount() == 0) {
270            return true;
271        }
272        return mPos == -1;
273    }
274
275    public final boolean isAfterLast() {
276        if (getCount() == 0) {
277            return true;
278        }
279        return mPos == getCount();
280    }
281
282    public int getColumnIndex(String columnName) {
283        // Hack according to bug 903852
284        final int periodIndex = columnName.lastIndexOf('.');
285        if (periodIndex != -1) {
286            Exception e = new Exception();
287            Log.e(TAG, "requesting column name with table name -- " + columnName, e);
288            columnName = columnName.substring(periodIndex + 1);
289        }
290
291        String columnNames[] = getColumnNames();
292        int length = columnNames.length;
293        for (int i = 0; i < length; i++) {
294            if (columnNames[i].equalsIgnoreCase(columnName)) {
295                return i;
296            }
297        }
298
299        if (Config.LOGV) {
300            if (getCount() > 0) {
301                Log.w("AbstractCursor", "Unknown column " + columnName);
302            }
303        }
304        return -1;
305    }
306
307    public int getColumnIndexOrThrow(String columnName) {
308        final int index = getColumnIndex(columnName);
309        if (index < 0) {
310            throw new IllegalArgumentException("column '" + columnName + "' does not exist");
311        }
312        return index;
313    }
314
315    public String getColumnName(int columnIndex) {
316        return getColumnNames()[columnIndex];
317    }
318
319    /**
320     * @hide
321     * @deprecated
322     */
323    public boolean updateBlob(int columnIndex, byte[] value) {
324        return update(columnIndex, value);
325    }
326
327    /**
328     * @hide
329     * @deprecated
330     */
331    public boolean updateString(int columnIndex, String value) {
332        return update(columnIndex, value);
333    }
334
335    /**
336     * @hide
337     * @deprecated
338     */
339    public boolean updateShort(int columnIndex, short value) {
340        return update(columnIndex, Short.valueOf(value));
341    }
342
343    /**
344     * @hide
345     * @deprecated
346     */
347    public boolean updateInt(int columnIndex, int value) {
348        return update(columnIndex, Integer.valueOf(value));
349    }
350
351    /**
352     * @hide
353     * @deprecated
354     */
355    public boolean updateLong(int columnIndex, long value) {
356        return update(columnIndex, Long.valueOf(value));
357    }
358
359    /**
360     * @hide
361     * @deprecated
362     */
363    public boolean updateFloat(int columnIndex, float value) {
364        return update(columnIndex, Float.valueOf(value));
365    }
366
367    /**
368     * @hide
369     * @deprecated
370     */
371    public boolean updateDouble(int columnIndex, double value) {
372        return update(columnIndex, Double.valueOf(value));
373    }
374
375    /**
376     * @hide
377     * @deprecated
378     */
379    public boolean updateToNull(int columnIndex) {
380        return update(columnIndex, null);
381    }
382
383    /**
384     * @hide
385     * @deprecated
386     */
387    public boolean update(int columnIndex, Object obj) {
388        if (!supportsUpdates()) {
389            return false;
390        }
391
392        // Long.valueOf() returns null sometimes!
393//        Long rowid = Long.valueOf(getLong(mRowIdColumnIndex));
394        Long rowid = new Long(getLong(mRowIdColumnIndex));
395        if (rowid == null) {
396            throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex);
397        }
398
399        synchronized(mUpdatedRows) {
400            Map<String, Object> row = mUpdatedRows.get(rowid);
401            if (row == null) {
402                row = new HashMap<String, Object>();
403                mUpdatedRows.put(rowid, row);
404            }
405            row.put(getColumnNames()[columnIndex], obj);
406        }
407
408        return true;
409    }
410
411    /**
412     * Returns <code>true</code> if there are pending updates that have not yet been committed.
413     *
414     * @return <code>true</code> if there are pending updates that have not yet been committed.
415     * @hide
416     * @deprecated
417     */
418    public boolean hasUpdates() {
419        synchronized(mUpdatedRows) {
420            return mUpdatedRows.size() > 0;
421        }
422    }
423
424    /**
425     * @hide
426     * @deprecated
427     */
428    public void abortUpdates() {
429        synchronized(mUpdatedRows) {
430            mUpdatedRows.clear();
431        }
432    }
433
434    /**
435     * @hide
436     * @deprecated
437     */
438    public boolean commitUpdates() {
439        return commitUpdates(null);
440    }
441
442    /**
443     * @hide
444     * @deprecated
445     */
446    public boolean supportsUpdates() {
447        return mRowIdColumnIndex != -1;
448    }
449
450    public void registerContentObserver(ContentObserver observer) {
451        mContentObservable.registerObserver(observer);
452    }
453
454    public void unregisterContentObserver(ContentObserver observer) {
455        // cursor will unregister all observers when it close
456        if (!mClosed) {
457            mContentObservable.unregisterObserver(observer);
458        }
459    }
460
461    public void registerDataSetObserver(DataSetObserver observer) {
462        mDataSetObservable.registerObserver(observer);
463    }
464
465    public void unregisterDataSetObserver(DataSetObserver observer) {
466        mDataSetObservable.unregisterObserver(observer);
467    }
468
469    /**
470     * Subclasses must call this method when they finish committing updates to notify all
471     * observers.
472     *
473     * @param selfChange
474     */
475    protected void onChange(boolean selfChange) {
476        synchronized (mSelfObserverLock) {
477            mContentObservable.dispatchChange(selfChange);
478            if (mNotifyUri != null && selfChange) {
479                mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
480            }
481        }
482    }
483
484    /**
485     * Specifies a content URI to watch for changes.
486     *
487     * @param cr The content resolver from the caller's context.
488     * @param notifyUri The URI to watch for changes. This can be a
489     * specific row URI, or a base URI for a whole class of content.
490     */
491    public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
492        synchronized (mSelfObserverLock) {
493            mNotifyUri = notifyUri;
494            mContentResolver = cr;
495            if (mSelfObserver != null) {
496                mContentResolver.unregisterContentObserver(mSelfObserver);
497            }
498            mSelfObserver = new SelfContentObserver(this);
499            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
500            mSelfObserverRegistered = true;
501        }
502    }
503
504    public boolean getWantsAllOnMoveCalls() {
505        return false;
506    }
507
508    public Bundle getExtras() {
509        return Bundle.EMPTY;
510    }
511
512    public Bundle respond(Bundle extras) {
513        return Bundle.EMPTY;
514    }
515
516    /**
517     * This function returns true if the field has been updated and is
518     * used in conjunction with {@link #getUpdatedField} to allow subclasses to
519     * support reading uncommitted updates. NOTE: This function and
520     * {@link #getUpdatedField} should be called together inside of a
521     * block synchronized on mUpdatedRows.
522     *
523     * @param columnIndex the column index of the field to check
524     * @return true if the field has been updated, false otherwise
525     */
526    protected boolean isFieldUpdated(int columnIndex) {
527        if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) {
528            Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
529            if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) {
530                return true;
531            }
532        }
533        return false;
534    }
535
536    /**
537     * This function returns the uncommitted updated value for the field
538     * at columnIndex.  NOTE: This function and {@link #isFieldUpdated} should
539     * be called together inside of a block synchronized on mUpdatedRows.
540     *
541     * @param columnIndex the column index of the field to retrieve
542     * @return the updated value
543     */
544    protected Object getUpdatedField(int columnIndex) {
545        Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
546        return updates.get(getColumnNames()[columnIndex]);
547    }
548
549    /**
550     * This function throws CursorIndexOutOfBoundsException if
551     * the cursor position is out of bounds. Subclass implementations of
552     * the get functions should call this before attempting
553     * to retrieve data.
554     *
555     * @throws CursorIndexOutOfBoundsException
556     */
557    protected void checkPosition() {
558        if (-1 == mPos || getCount() == mPos) {
559            throw new CursorIndexOutOfBoundsException(mPos, getCount());
560        }
561    }
562
563    @Override
564    protected void finalize() {
565        if (mSelfObserver != null && mSelfObserverRegistered == true) {
566            mContentResolver.unregisterContentObserver(mSelfObserver);
567        }
568    }
569
570    /**
571     * Cursors use this class to track changes others make to their URI.
572     */
573    protected static class SelfContentObserver extends ContentObserver {
574        WeakReference<AbstractCursor> mCursor;
575
576        public SelfContentObserver(AbstractCursor cursor) {
577            super(null);
578            mCursor = new WeakReference<AbstractCursor>(cursor);
579        }
580
581        @Override
582        public boolean deliverSelfNotifications() {
583            return false;
584        }
585
586        @Override
587        public void onChange(boolean selfChange) {
588            AbstractCursor cursor = mCursor.get();
589            if (cursor != null) {
590                cursor.onChange(false);
591            }
592        }
593    }
594
595    /**
596     * This HashMap contains a mapping from Long rowIDs to another Map
597     * that maps from String column names to new values. A NULL value means to
598     * remove an existing value, and all numeric values are in their class
599     * forms, i.e. Integer, Long, Float, etc.
600     */
601    protected HashMap<Long, Map<String, Object>> mUpdatedRows;
602
603    /**
604     * This must be set to the index of the row ID column by any
605     * subclass that wishes to support updates.
606     */
607    protected int mRowIdColumnIndex;
608
609    protected int mPos;
610    protected Long mCurrentRowID;
611    protected ContentResolver mContentResolver;
612    protected boolean mClosed = false;
613    private Uri mNotifyUri;
614    private ContentObserver mSelfObserver;
615    final private Object mSelfObserverLock = new Object();
616    private boolean mSelfObserverRegistered;
617}
618