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