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