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