blob: 230a9b87727a33baa8e3d2f3745865643bd8a01d [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
17package com.android.internal.database;
18
19import android.compat.annotation.UnsupportedAppUsage;
20import android.database.AbstractCursor;
21import android.database.Cursor;
22import android.database.DataSetObserver;
23import 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 */
29public 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
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
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
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
189 public String getString(int column)
190 {
191 return mCursor.getString(column);
192 }
193
194 @Override
195 public short getShort(int column)
196 {
197 return mCursor.getShort(column);
198 }
199
200 @Override
201 public int getInt(int column)
202 {
203 return mCursor.getInt(column);
204 }
205
206 @Override
207 public long getLong(int column)
208 {
209 return mCursor.getLong(column);
210 }
211
212 @Override
213 public float getFloat(int column)
214 {
215 return mCursor.getFloat(column);
216 }
217
218 @Override
219 public double getDouble(int column)
220 {
221 return mCursor.getDouble(column);
222 }
223
224 @Override
225 public int getType(int column) {
226 return mCursor.getType(column);
227 }
228
229 @Override
230 public boolean isNull(int column)
231 {
232 return mCursor.isNull(column);
233 }
234
235 @Override
236 public byte[] getBlob(int column)
237 {
238 return mCursor.getBlob(column);
239 }
240
241 @Override
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
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
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
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
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
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}