1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | |
6 | package org.chromium.chrome.browser.database; |
7 | |
8 | import android.database.AbstractCursor; |
9 | import android.database.CursorWindow; |
10 | import android.util.Log; |
11 | |
12 | import org.chromium.base.CalledByNative; |
13 | |
14 | import java.sql.Types; |
15 | |
16 | /** |
17 | * This class exposes the query result from native side. |
18 | */ |
19 | public class SQLiteCursor extends AbstractCursor { |
20 | private static final String TAG = "SQLiteCursor"; |
21 | // Used by JNI. |
22 | private int mNativeSQLiteCursor; |
23 | |
24 | // The count of result rows. |
25 | private int mCount = -1; |
26 | |
27 | private int[] mColumnTypes; |
28 | |
29 | private final Object mColumnTypeLock = new Object(); |
30 | |
31 | // The belows are the locks for those methods that need wait for |
32 | // the callback result in native side. |
33 | private final Object mMoveLock = new Object(); |
34 | private final Object mGetBlobLock = new Object(); |
35 | |
36 | private SQLiteCursor(int nativeSQLiteCursor) { |
37 | mNativeSQLiteCursor = nativeSQLiteCursor; |
38 | } |
39 | |
40 | @CalledByNative |
41 | private static SQLiteCursor create(int nativeSQLiteCursor) { |
42 | return new SQLiteCursor(nativeSQLiteCursor); |
43 | } |
44 | |
45 | @Override |
46 | public int getCount() { |
47 | synchronized (mMoveLock) { |
48 | if (mCount == -1) |
49 | mCount = nativeGetCount(mNativeSQLiteCursor); |
50 | } |
51 | return mCount; |
52 | } |
53 | |
54 | @Override |
55 | public String[] getColumnNames() { |
56 | return nativeGetColumnNames(mNativeSQLiteCursor); |
57 | } |
58 | |
59 | @Override |
60 | public String getString(int column) { |
61 | return nativeGetString(mNativeSQLiteCursor, column); |
62 | } |
63 | |
64 | @Override |
65 | public short getShort(int column) { |
66 | return (short) nativeGetInt(mNativeSQLiteCursor, column); |
67 | } |
68 | |
69 | @Override |
70 | public int getInt(int column) { |
71 | return nativeGetInt(mNativeSQLiteCursor, column); |
72 | } |
73 | |
74 | @Override |
75 | public long getLong(int column) { |
76 | return nativeGetLong(mNativeSQLiteCursor, column); |
77 | } |
78 | |
79 | @Override |
80 | public float getFloat(int column) { |
81 | return (float)nativeGetDouble(mNativeSQLiteCursor, column); |
82 | } |
83 | |
84 | @Override |
85 | public double getDouble(int column) { |
86 | return nativeGetDouble(mNativeSQLiteCursor, column); |
87 | } |
88 | |
89 | @Override |
90 | public boolean isNull(int column) { |
91 | return nativeIsNull(mNativeSQLiteCursor, column); |
92 | } |
93 | |
94 | @Override |
95 | public void close() { |
96 | super.close(); |
97 | nativeDestroy(mNativeSQLiteCursor); |
98 | mNativeSQLiteCursor = 0; |
99 | } |
100 | |
101 | @Override |
102 | public boolean onMove(int oldPosition, int newPosition) { |
103 | synchronized (mMoveLock) { |
104 | nativeMoveTo(mNativeSQLiteCursor, newPosition); |
105 | } |
106 | return super.onMove(oldPosition, newPosition); |
107 | } |
108 | |
109 | @Override |
110 | public byte[] getBlob(int column) { |
111 | synchronized (mGetBlobLock) { |
112 | return nativeGetBlob(mNativeSQLiteCursor, column); |
113 | } |
114 | } |
115 | |
116 | @Deprecated |
117 | public boolean supportsUpdates() { |
118 | return false; |
119 | } |
120 | |
121 | @Override |
122 | protected void finalize() { |
123 | super.finalize(); |
124 | if (!isClosed()) { |
125 | Log.w(TAG, "Cursor hasn't been closed"); |
126 | close(); |
127 | } |
128 | } |
129 | |
130 | @Override |
131 | public void fillWindow(int position, CursorWindow window) { |
132 | if (position < 0 || position > getCount()) { |
133 | return; |
134 | } |
135 | window.acquireReference(); |
136 | try { |
137 | int oldpos = mPos; |
138 | mPos = position - 1; |
139 | window.clear(); |
140 | window.setStartPosition(position); |
141 | int columnNum = getColumnCount(); |
142 | window.setNumColumns(columnNum); |
143 | while (moveToNext() && window.allocRow()) { |
144 | for (int i = 0; i < columnNum; i++) { |
145 | boolean hasRoom = true; |
146 | switch (getColumnType(i)) { |
147 | case Types.DOUBLE: |
148 | hasRoom = fillRow(window, Double.valueOf(getDouble(i)), mPos, i); |
149 | break; |
150 | case Types.NUMERIC: |
151 | hasRoom = fillRow(window, Long.valueOf(getLong(i)), mPos, i); |
152 | break; |
153 | case Types.BLOB: |
154 | hasRoom = fillRow(window, getBlob(i), mPos, i); |
155 | break; |
156 | case Types.LONGVARCHAR: |
157 | hasRoom = fillRow(window, getString(i), mPos, i); |
158 | break; |
159 | case Types.NULL: |
160 | hasRoom = fillRow(window, null, mPos, i); |
161 | break; |
162 | } |
163 | if (!hasRoom) { |
164 | break; |
165 | } |
166 | } |
167 | } |
168 | mPos = oldpos; |
169 | } catch (IllegalStateException e) { |
170 | // simply ignore it |
171 | } finally { |
172 | window.releaseReference(); |
173 | } |
174 | } |
175 | |
176 | /** |
177 | * Fill row with the given value. If the value type is other than Long, |
178 | * String, byte[] or Double, the NULL will be filled. |
179 | * |
180 | * @return true if succeeded, false if window is full. |
181 | */ |
182 | private boolean fillRow(CursorWindow window, Object value, int pos, int column) { |
183 | if (putValue(window, value, pos, column)) { |
184 | return true; |
185 | } else { |
186 | window.freeLastRow(); |
187 | return false; |
188 | } |
189 | } |
190 | |
191 | /** |
192 | * Put the value in given window. If the value type is other than Long, |
193 | * String, byte[] or Double, the NULL will be filled. |
194 | * |
195 | * @return true if succeeded. |
196 | */ |
197 | private boolean putValue(CursorWindow window, Object value, int pos, int column) { |
198 | if (value == null) { |
199 | return window.putNull(pos, column); |
200 | } else if (value instanceof Long) { |
201 | return window.putLong((Long) value, pos, column); |
202 | } else if (value instanceof String) { |
203 | return window.putString((String) value, pos, column); |
204 | } else if (value instanceof byte[] && ((byte[]) value).length > 0) { |
205 | return window.putBlob((byte[]) value, pos, column); |
206 | } else if (value instanceof Double) { |
207 | return window.putDouble((Double) value, pos, column); |
208 | } else { |
209 | return window.putNull(pos, column); |
210 | } |
211 | } |
212 | |
213 | /** |
214 | * @param index the column index. |
215 | * @return the column type from cache or native side. |
216 | */ |
217 | private int getColumnType(int index) { |
218 | synchronized (mColumnTypeLock) { |
219 | if (mColumnTypes == null) { |
220 | int columnCount = getColumnCount(); |
221 | mColumnTypes = new int[columnCount]; |
222 | for (int i = 0; i < columnCount; i++) { |
223 | mColumnTypes[i] = nativeGetColumnType(mNativeSQLiteCursor, i); |
224 | } |
225 | } |
226 | } |
227 | return mColumnTypes[index]; |
228 | } |
229 | |
230 | private native void nativeDestroy(int nativeSQLiteCursor); |
231 | private native int nativeGetCount(int nativeSQLiteCursor); |
232 | private native String[] nativeGetColumnNames(int nativeSQLiteCursor); |
233 | private native int nativeGetColumnType(int nativeSQLiteCursor, int column); |
234 | private native String nativeGetString(int nativeSQLiteCursor, int column); |
235 | private native byte[] nativeGetBlob(int nativeSQLiteCursor, int column); |
236 | private native boolean nativeIsNull(int nativeSQLiteCursor, int column); |
237 | private native long nativeGetLong(int nativeSQLiteCursor, int column); |
238 | private native int nativeGetInt(int nativeSQLiteCursor, int column); |
239 | private native double nativeGetDouble(int nativeSQLiteCursor, int column); |
240 | private native int nativeMoveTo(int nativeSQLiteCursor, int newPosition); |
241 | } |