Home | History | Annotate | Download | only in database
      1 /*
      2  * Copyright (C) 2007 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 
     17 package com.android.internal.database;
     18 
     19 import android.database.AbstractCursor;
     20 import android.database.Cursor;
     21 import android.database.DataSetObserver;
     22 import android.util.Log;
     23 
     24 /**
     25  * A variant of MergeCursor that sorts the cursors being merged. If decent
     26  * performance is ever obtained, it can be put back under android.database.
     27  */
     28 public class SortCursor extends AbstractCursor
     29 {
     30     private static final String TAG = "SortCursor";
     31     private Cursor mCursor; // updated in onMove
     32     private Cursor[] mCursors;
     33     private int [] mSortColumns;
     34     private final int ROWCACHESIZE = 64;
     35     private int mRowNumCache[] = new int[ROWCACHESIZE];
     36     private int mCursorCache[] = new int[ROWCACHESIZE];
     37     private int mCurRowNumCache[][];
     38     private int mLastCacheHit = -1;
     39 
     40     private DataSetObserver mObserver = new DataSetObserver() {
     41 
     42         @Override
     43         public void onChanged() {
     44             // Reset our position so the optimizations in move-related code
     45             // don't screw us over
     46             mPos = -1;
     47         }
     48 
     49         @Override
     50         public void onInvalidated() {
     51             mPos = -1;
     52         }
     53     };
     54 
     55     public SortCursor(Cursor[] cursors, String sortcolumn)
     56     {
     57         mCursors = cursors;
     58 
     59         int length = mCursors.length;
     60         mSortColumns = new int[length];
     61         for (int i = 0 ; i < length ; i++) {
     62             if (mCursors[i] == null) continue;
     63 
     64             // Register ourself as a data set observer
     65             mCursors[i].registerDataSetObserver(mObserver);
     66 
     67             mCursors[i].moveToFirst();
     68 
     69             // We don't catch the exception
     70             mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
     71         }
     72         mCursor = null;
     73         String smallest = "";
     74         for (int j = 0 ; j < length; j++) {
     75             if (mCursors[j] == null || mCursors[j].isAfterLast())
     76                 continue;
     77             String current = mCursors[j].getString(mSortColumns[j]);
     78             if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
     79                 smallest = current;
     80                 mCursor = mCursors[j];
     81             }
     82         }
     83 
     84         for (int i = mRowNumCache.length - 1; i >= 0; i--) {
     85             mRowNumCache[i] = -2;
     86         }
     87         mCurRowNumCache = new int[ROWCACHESIZE][length];
     88     }
     89 
     90     @Override
     91     public int getCount()
     92     {
     93         int count = 0;
     94         int length = mCursors.length;
     95         for (int i = 0 ; i < length ; i++) {
     96             if (mCursors[i] != null) {
     97                 count += mCursors[i].getCount();
     98             }
     99         }
    100         return count;
    101     }
    102 
    103     @Override
    104     public boolean onMove(int oldPosition, int newPosition)
    105     {
    106         if (oldPosition == newPosition)
    107             return true;
    108 
    109         /* Find the right cursor
    110          * Because the client of this cursor (the listadapter/view) tends
    111          * to jump around in the cursor somewhat, a simple cache strategy
    112          * is used to avoid having to search all cursors from the start.
    113          * TODO: investigate strategies for optimizing random access and
    114          * reverse-order access.
    115          */
    116 
    117         int cache_entry = newPosition % ROWCACHESIZE;
    118 
    119         if (mRowNumCache[cache_entry] == newPosition) {
    120             int which = mCursorCache[cache_entry];
    121             mCursor = mCursors[which];
    122             if (mCursor == null) {
    123                 Log.w(TAG, "onMove: cache results in a null cursor.");
    124                 return false;
    125             }
    126             mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
    127             mLastCacheHit = cache_entry;
    128             return true;
    129         }
    130 
    131         mCursor = null;
    132         int length = mCursors.length;
    133 
    134         if (mLastCacheHit >= 0) {
    135             for (int i = 0; i < length; i++) {
    136                 if (mCursors[i] == null) continue;
    137                 mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
    138             }
    139         }
    140 
    141         if (newPosition < oldPosition || oldPosition == -1) {
    142             for (int i = 0 ; i < length; i++) {
    143                 if (mCursors[i] == null) continue;
    144                 mCursors[i].moveToFirst();
    145             }
    146             oldPosition = 0;
    147         }
    148         if (oldPosition < 0) {
    149             oldPosition = 0;
    150         }
    151 
    152         // search forward to the new position
    153         int smallestIdx = -1;
    154         for(int i = oldPosition; i <= newPosition; i++) {
    155             String smallest = "";
    156             smallestIdx = -1;
    157             for (int j = 0 ; j < length; j++) {
    158                 if (mCursors[j] == null || mCursors[j].isAfterLast()) {
    159                     continue;
    160                 }
    161                 String current = mCursors[j].getString(mSortColumns[j]);
    162                 if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
    163                     smallest = current;
    164                     smallestIdx = j;
    165                 }
    166             }
    167             if (i == newPosition) break;
    168             if (mCursors[smallestIdx] != null) {
    169                 mCursors[smallestIdx].moveToNext();
    170             }
    171         }
    172         mCursor = mCursors[smallestIdx];
    173         mRowNumCache[cache_entry] = newPosition;
    174         mCursorCache[cache_entry] = smallestIdx;
    175         for (int i = 0; i < length; i++) {
    176             if (mCursors[i] != null) {
    177                 mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
    178             }
    179         }
    180         mLastCacheHit = -1;
    181         return true;
    182     }
    183 
    184     @Override
    185     public String getString(int column)
    186     {
    187         return mCursor.getString(column);
    188     }
    189 
    190     @Override
    191     public short getShort(int column)
    192     {
    193         return mCursor.getShort(column);
    194     }
    195 
    196     @Override
    197     public int getInt(int column)
    198     {
    199         return mCursor.getInt(column);
    200     }
    201 
    202     @Override
    203     public long getLong(int column)
    204     {
    205         return mCursor.getLong(column);
    206     }
    207 
    208     @Override
    209     public float getFloat(int column)
    210     {
    211         return mCursor.getFloat(column);
    212     }
    213 
    214     @Override
    215     public double getDouble(int column)
    216     {
    217         return mCursor.getDouble(column);
    218     }
    219 
    220     @Override
    221     public int getType(int column) {
    222         return mCursor.getType(column);
    223     }
    224 
    225     @Override
    226     public boolean isNull(int column)
    227     {
    228         return mCursor.isNull(column);
    229     }
    230 
    231     @Override
    232     public byte[] getBlob(int column)
    233     {
    234         return mCursor.getBlob(column);
    235     }
    236 
    237     @Override
    238     public String[] getColumnNames()
    239     {
    240         if (mCursor != null) {
    241             return mCursor.getColumnNames();
    242         } else {
    243             // All of the cursors may be empty, but they can still return
    244             // this information.
    245             int length = mCursors.length;
    246             for (int i = 0 ; i < length ; i++) {
    247                 if (mCursors[i] != null) {
    248                     return mCursors[i].getColumnNames();
    249                 }
    250             }
    251             throw new IllegalStateException("No cursor that can return names");
    252         }
    253     }
    254 
    255     @Override
    256     public void deactivate()
    257     {
    258         int length = mCursors.length;
    259         for (int i = 0 ; i < length ; i++) {
    260             if (mCursors[i] == null) continue;
    261             mCursors[i].deactivate();
    262         }
    263     }
    264 
    265     @Override
    266     public void close() {
    267         int length = mCursors.length;
    268         for (int i = 0 ; i < length ; i++) {
    269             if (mCursors[i] == null) continue;
    270             mCursors[i].close();
    271         }
    272     }
    273 
    274     @Override
    275     public void registerDataSetObserver(DataSetObserver observer) {
    276         int length = mCursors.length;
    277         for (int i = 0 ; i < length ; i++) {
    278             if (mCursors[i] != null) {
    279                 mCursors[i].registerDataSetObserver(observer);
    280             }
    281         }
    282     }
    283 
    284     @Override
    285     public void unregisterDataSetObserver(DataSetObserver observer) {
    286         int length = mCursors.length;
    287         for (int i = 0 ; i < length ; i++) {
    288             if (mCursors[i] != null) {
    289                 mCursors[i].unregisterDataSetObserver(observer);
    290             }
    291         }
    292     }
    293 
    294     @Override
    295     public boolean requery()
    296     {
    297         int length = mCursors.length;
    298         for (int i = 0 ; i < length ; i++) {
    299             if (mCursors[i] == null) continue;
    300 
    301             if (mCursors[i].requery() == false) {
    302                 return false;
    303             }
    304         }
    305 
    306         return true;
    307     }
    308 }
    309