1 /*
2  * Copyright (C) 2015 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.example.android.tvleanback.data;
18 
19 import android.database.AbstractCursor;
20 import android.database.Cursor;
21 
22 /**
23  * A sample paginated cursor which will pre-fetch and cache rows.
24  */
25 public class PaginatedCursor extends AbstractCursor {
26     /**
27      * The number of items that should be loaded each time.
28      */
29     private static final int PAGE_SIZE = 10;
30 
31     /**
32      * The threshold of number of items left that a new page should be loaded.
33      */
34     private static final int PAGE_THRESHOLD = PAGE_SIZE / 2;
35 
36     private final Cursor mCursor;
37     private final int mRowCount;
38     private final boolean[] mCachedRows;
39     private final String[] mColumnNames;
40     private final int mColumnCount;
41     private final int[] mColumnTypes;
42 
43     private final byte[][][] mByteArrayDataCache;
44     private final float[][] mFloatDataCache;
45     private final int[][] mIntDataCache;
46     private final String[][] mStringDataCache;
47     /**
48      * Index mapping from column index into the data type specific cache index;
49      */
50     private final int[] mByteArrayCacheIndexMap;
51     private final int[] mFloatCacheIndexMap;
52     private final int[] mIntCacheIndexMap;
53     private final int[] mStringCacheIndexMap;
54     private int mByteArrayCacheColumnSize;
55     private int mFloatCacheColumnSize;
56     private int mIntCacheColumnSize;
57     private int mStringCacheColumnSize;
58     private int mLastCachePosition;
59 
PaginatedCursor(Cursor cursor)60     public PaginatedCursor(Cursor cursor) {
61         super();
62         mCursor = cursor;
63         mRowCount = mCursor.getCount();
64         mCachedRows = new boolean[mRowCount];
65         mColumnNames = mCursor.getColumnNames();
66         mColumnCount = mCursor.getColumnCount();
67         mColumnTypes = new int[mColumnCount];
68 
69         mByteArrayCacheColumnSize = 0;
70         mFloatCacheColumnSize = 0;
71         mIntCacheColumnSize = 0;
72         mStringCacheColumnSize = 0;
73 
74         mByteArrayCacheIndexMap = new int[mColumnCount];
75         mFloatCacheIndexMap = new int[mColumnCount];
76         mIntCacheIndexMap = new int[mColumnCount];
77         mStringCacheIndexMap = new int[mColumnCount];
78 
79         mCursor.moveToFirst();
80         for (int i = 0; i < mColumnCount; i++) {
81             int type = mCursor.getType(i);
82             mColumnTypes[i] = type;
83             switch (type) {
84                 case Cursor.FIELD_TYPE_BLOB:
85                     mByteArrayCacheIndexMap[i] = mByteArrayCacheColumnSize++;
86                     break;
87                 case Cursor.FIELD_TYPE_FLOAT:
88                     mFloatCacheIndexMap[i] = mFloatCacheColumnSize++;
89                     break;
90                 case Cursor.FIELD_TYPE_INTEGER:
91                     mIntCacheIndexMap[i] = mIntCacheColumnSize++;
92                     break;
93                 case Cursor.FIELD_TYPE_STRING:
94                     mStringCacheIndexMap[i] = mStringCacheColumnSize++;
95                     break;
96             }
97         }
98 
99         mByteArrayDataCache = mByteArrayCacheColumnSize > 0 ? new byte[mRowCount][][] : null;
100         mFloatDataCache = mFloatCacheColumnSize > 0 ? new float[mRowCount][] : null;
101         mIntDataCache = mIntCacheColumnSize > 0 ? new int[mRowCount][] : null;
102         mStringDataCache = mStringCacheColumnSize > 0 ? new String[mRowCount][] : null;
103 
104         for (int i = 0; i < mRowCount; i++) {
105             mCachedRows[i] = false;
106             if (mByteArrayDataCache != null) {
107                 mByteArrayDataCache[i] = new byte[mByteArrayCacheColumnSize][];
108             }
109             if (mFloatDataCache != null) {
110                 mFloatDataCache[i] = new float[mFloatCacheColumnSize];
111             }
112             if (mIntDataCache != null) {
113                 mIntDataCache[i] = new int[mIntCacheColumnSize];
114             }
115             if (mStringDataCache != null) {
116                 mStringDataCache[i] = new String[mStringCacheColumnSize];
117             }
118         }
119 
120         // Cache at the initialization stage.
121         loadCacheStartingFromPosition(0);
122     }
123 
124     /**
125      * Try to load un-cached data with size {@link PAGE_SIZE} starting from given index.
126      */
loadCacheStartingFromPosition(int index)127     private void loadCacheStartingFromPosition(int index) {
128         mCursor.moveToPosition(index);
129         for (int row = index; row < (index + PAGE_SIZE) && row < mRowCount; row++) {
130             if (!mCachedRows[row]) {
131                 for (int col = 0; col < mColumnCount; col++) {
132                     switch (mCursor.getType(col)) {
133                         case Cursor.FIELD_TYPE_BLOB:
134                             mByteArrayDataCache[row][mByteArrayCacheIndexMap[col]] =
135                                     mCursor.getBlob(col);
136                             break;
137                         case Cursor.FIELD_TYPE_FLOAT:
138                             mFloatDataCache[row][mFloatCacheIndexMap[col]] = mCursor.getFloat(col);
139                             break;
140                         case Cursor.FIELD_TYPE_INTEGER:
141                             mIntDataCache[row][mIntCacheIndexMap[col]] = mCursor.getInt(col);
142                             break;
143                         case Cursor.FIELD_TYPE_STRING:
144                             mStringDataCache[row][mStringCacheIndexMap[col]] =
145                                     mCursor.getString(col);
146                             break;
147                     }
148                 }
149                 mCachedRows[row] = true;
150             }
151             mCursor.moveToNext();
152         }
153         mLastCachePosition = Math.min(index + PAGE_SIZE, mRowCount) - 1;
154     }
155 
156     @Override
onMove(int oldPosition, int newPosition)157     public boolean onMove(int oldPosition, int newPosition) {
158         // If it's a consecutive move and haven't exceeds the threshold, do nothing.
159         if ((newPosition - oldPosition) != 1 ||
160                 (newPosition + PAGE_THRESHOLD) <= mLastCachePosition) {
161             loadCacheStartingFromPosition(newPosition);
162         }
163         return true;
164     }
165 
166     @Override
getType(int column)167     public int getType(int column) {
168         return mColumnTypes[column];
169     }
170 
171     @Override
getCount()172     public int getCount() {
173         return mRowCount;
174     }
175 
176     @Override
getColumnNames()177     public String[] getColumnNames() {
178         return mColumnNames;
179     }
180 
181     @Override
getString(int column)182     public String getString(int column) {
183         return mStringDataCache[mPos][mStringCacheIndexMap[column]];
184     }
185 
186     @Override
getShort(int column)187     public short getShort(int column) {
188         return (short) mIntDataCache[mPos][mIntCacheIndexMap[column]];
189     }
190 
191     @Override
getInt(int column)192     public int getInt(int column) {
193         return mIntDataCache[mPos][mIntCacheIndexMap[column]];
194     }
195 
196     @Override
getLong(int column)197     public long getLong(int column) {
198         return mIntDataCache[mPos][mIntCacheIndexMap[column]];
199     }
200 
201     @Override
getFloat(int column)202     public float getFloat(int column) {
203         return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
204     }
205 
206     @Override
getDouble(int column)207     public double getDouble(int column) {
208         return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
209     }
210 
211     @Override
getBlob(int column)212     public byte[] getBlob(int column) {
213         return mByteArrayDataCache[mPos][mByteArrayCacheIndexMap[column]];
214     }
215 
216     @Override
isNull(int column)217     public boolean isNull(int column) {
218         return mColumnTypes[column] == Cursor.FIELD_TYPE_NULL;
219     }
220 
221 }
222