|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 /* |
|
3 * Copyright (C) 2007 The Android Open Source Project |
|
4 * |
|
5 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 * you may not use this file except in compliance with the License. |
|
7 * You may obtain a copy of the License at |
|
8 * |
|
9 * http://www.apache.org/licenses/LICENSE-2.0 |
|
10 * |
|
11 * Unless required by applicable law or agreed to in writing, software |
|
12 * distributed under the License is distributed on an "AS IS" BASIS, |
|
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 * See the License for the specific language governing permissions and |
|
15 * limitations under the License. |
|
16 */ |
|
17 |
|
18 package org.mozilla.gecko.sqlite; |
|
19 |
|
20 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; |
|
21 |
|
22 import android.database.AbstractCursor; |
|
23 import android.database.CursorIndexOutOfBoundsException; |
|
24 |
|
25 import java.nio.ByteBuffer; |
|
26 import java.util.ArrayList; |
|
27 |
|
28 /* |
|
29 * Android's AbstractCursor throws on getBlob() |
|
30 * and MatrixCursor forgot to override it. This was fixed |
|
31 * at some point but old devices are still SOL. |
|
32 * Oh, and everything in MatrixCursor is private instead of |
|
33 * protected, so we need to entirely duplicate it here, |
|
34 * instad of just being able to add the missing method. |
|
35 */ |
|
36 /** |
|
37 * A mutable cursor implementation backed by an array of {@code Object}s. Use |
|
38 * {@link #newRow()} to add rows. Automatically expands internal capacity |
|
39 * as needed. |
|
40 */ |
|
41 public class MatrixBlobCursor extends AbstractCursor { |
|
42 |
|
43 private final String[] columnNames; |
|
44 private Object[] data; |
|
45 private int rowCount = 0; |
|
46 private final int columnCount; |
|
47 |
|
48 /** |
|
49 * Constructs a new cursor with the given initial capacity. |
|
50 * |
|
51 * @param columnNames names of the columns, the ordering of which |
|
52 * determines column ordering elsewhere in this cursor |
|
53 * @param initialCapacity in rows |
|
54 */ |
|
55 @WrapElementForJNI |
|
56 public MatrixBlobCursor(String[] columnNames, int initialCapacity) { |
|
57 this.columnNames = columnNames; |
|
58 this.columnCount = columnNames.length; |
|
59 |
|
60 if (initialCapacity < 1) { |
|
61 initialCapacity = 1; |
|
62 } |
|
63 |
|
64 this.data = new Object[columnCount * initialCapacity]; |
|
65 } |
|
66 |
|
67 /** |
|
68 * Constructs a new cursor. |
|
69 * |
|
70 * @param columnNames names of the columns, the ordering of which |
|
71 * determines column ordering elsewhere in this cursor |
|
72 */ |
|
73 @WrapElementForJNI |
|
74 public MatrixBlobCursor(String[] columnNames) { |
|
75 this(columnNames, 16); |
|
76 } |
|
77 |
|
78 /** |
|
79 * Gets value at the given column for the current row. |
|
80 */ |
|
81 protected Object get(int column) { |
|
82 if (column < 0 || column >= columnCount) { |
|
83 throw new CursorIndexOutOfBoundsException("Requested column: " |
|
84 + column + ", # of columns: " + columnCount); |
|
85 } |
|
86 if (mPos < 0) { |
|
87 throw new CursorIndexOutOfBoundsException("Before first row."); |
|
88 } |
|
89 if (mPos >= rowCount) { |
|
90 throw new CursorIndexOutOfBoundsException("After last row."); |
|
91 } |
|
92 return data[mPos * columnCount + column]; |
|
93 } |
|
94 |
|
95 /** |
|
96 * Adds a new row to the end and returns a builder for that row. Not safe |
|
97 * for concurrent use. |
|
98 * |
|
99 * @return builder which can be used to set the column values for the new |
|
100 * row |
|
101 */ |
|
102 public RowBuilder newRow() { |
|
103 rowCount++; |
|
104 int endIndex = rowCount * columnCount; |
|
105 ensureCapacity(endIndex); |
|
106 int start = endIndex - columnCount; |
|
107 return new RowBuilder(start, endIndex); |
|
108 } |
|
109 |
|
110 /** |
|
111 * Adds a new row to the end with the given column values. Not safe |
|
112 * for concurrent use. |
|
113 * |
|
114 * @throws IllegalArgumentException if {@code columnValues.length != |
|
115 * columnNames.length} |
|
116 * @param columnValues in the same order as the the column names specified |
|
117 * at cursor construction time |
|
118 */ |
|
119 @WrapElementForJNI |
|
120 public void addRow(Object[] columnValues) { |
|
121 if (columnValues.length != columnCount) { |
|
122 throw new IllegalArgumentException("columnNames.length = " |
|
123 + columnCount + ", columnValues.length = " |
|
124 + columnValues.length); |
|
125 } |
|
126 |
|
127 int start = rowCount++ * columnCount; |
|
128 ensureCapacity(start + columnCount); |
|
129 System.arraycopy(columnValues, 0, data, start, columnCount); |
|
130 } |
|
131 |
|
132 /** |
|
133 * Adds a new row to the end with the given column values. Not safe |
|
134 * for concurrent use. |
|
135 * |
|
136 * @throws IllegalArgumentException if {@code columnValues.size() != |
|
137 * columnNames.length} |
|
138 * @param columnValues in the same order as the the column names specified |
|
139 * at cursor construction time |
|
140 */ |
|
141 @WrapElementForJNI |
|
142 public void addRow(Iterable<?> columnValues) { |
|
143 int start = rowCount * columnCount; |
|
144 int end = start + columnCount; |
|
145 ensureCapacity(end); |
|
146 |
|
147 if (columnValues instanceof ArrayList<?>) { |
|
148 addRow((ArrayList<?>) columnValues, start); |
|
149 return; |
|
150 } |
|
151 |
|
152 int current = start; |
|
153 Object[] localData = data; |
|
154 for (Object columnValue : columnValues) { |
|
155 if (current == end) { |
|
156 // TODO: null out row? |
|
157 throw new IllegalArgumentException( |
|
158 "columnValues.size() > columnNames.length"); |
|
159 } |
|
160 localData[current++] = columnValue; |
|
161 } |
|
162 |
|
163 if (current != end) { |
|
164 // TODO: null out row? |
|
165 throw new IllegalArgumentException( |
|
166 "columnValues.size() < columnNames.length"); |
|
167 } |
|
168 |
|
169 // Increase row count here in case we encounter an exception. |
|
170 rowCount++; |
|
171 } |
|
172 |
|
173 /** Optimization for {@link ArrayList}. */ |
|
174 @WrapElementForJNI |
|
175 private void addRow(ArrayList<?> columnValues, int start) { |
|
176 int size = columnValues.size(); |
|
177 if (size != columnCount) { |
|
178 throw new IllegalArgumentException("columnNames.length = " |
|
179 + columnCount + ", columnValues.size() = " + size); |
|
180 } |
|
181 |
|
182 rowCount++; |
|
183 Object[] localData = data; |
|
184 for (int i = 0; i < size; i++) { |
|
185 localData[start + i] = columnValues.get(i); |
|
186 } |
|
187 } |
|
188 |
|
189 /** Ensures that this cursor has enough capacity. */ |
|
190 private void ensureCapacity(int size) { |
|
191 if (size > data.length) { |
|
192 Object[] oldData = this.data; |
|
193 int newSize = data.length * 2; |
|
194 if (newSize < size) { |
|
195 newSize = size; |
|
196 } |
|
197 this.data = new Object[newSize]; |
|
198 System.arraycopy(oldData, 0, this.data, 0, oldData.length); |
|
199 } |
|
200 } |
|
201 |
|
202 /** |
|
203 * Builds a row, starting from the left-most column and adding one column |
|
204 * value at a time. Follows the same ordering as the column names specified |
|
205 * at cursor construction time. |
|
206 */ |
|
207 public class RowBuilder { |
|
208 |
|
209 private int index; |
|
210 private final int endIndex; |
|
211 |
|
212 RowBuilder(int index, int endIndex) { |
|
213 this.index = index; |
|
214 this.endIndex = endIndex; |
|
215 } |
|
216 |
|
217 /** |
|
218 * Sets the next column value in this row. |
|
219 * |
|
220 * @throws CursorIndexOutOfBoundsException if you try to add too many |
|
221 * values |
|
222 * @return this builder to support chaining |
|
223 */ |
|
224 public RowBuilder add(Object columnValue) { |
|
225 if (index == endIndex) { |
|
226 throw new CursorIndexOutOfBoundsException( |
|
227 "No more columns left."); |
|
228 } |
|
229 |
|
230 data[index++] = columnValue; |
|
231 return this; |
|
232 } |
|
233 } |
|
234 |
|
235 public void set(int column, Object value) { |
|
236 if (column < 0 || column >= columnCount) { |
|
237 throw new CursorIndexOutOfBoundsException("Requested column: " |
|
238 + column + ", # of columns: " + columnCount); |
|
239 } |
|
240 if (mPos < 0) { |
|
241 throw new CursorIndexOutOfBoundsException("Before first row."); |
|
242 } |
|
243 if (mPos >= rowCount) { |
|
244 throw new CursorIndexOutOfBoundsException("After last row."); |
|
245 } |
|
246 data[mPos * columnCount + column] = value; |
|
247 } |
|
248 |
|
249 // AbstractCursor implementation. |
|
250 @Override |
|
251 public int getCount() { |
|
252 return rowCount; |
|
253 } |
|
254 |
|
255 @Override |
|
256 public String[] getColumnNames() { |
|
257 return columnNames; |
|
258 } |
|
259 |
|
260 @Override |
|
261 public String getString(int column) { |
|
262 Object value = get(column); |
|
263 if (value == null) return null; |
|
264 return value.toString(); |
|
265 } |
|
266 |
|
267 @Override |
|
268 public short getShort(int column) { |
|
269 Object value = get(column); |
|
270 if (value == null) return 0; |
|
271 if (value instanceof Number) return ((Number) value).shortValue(); |
|
272 return Short.parseShort(value.toString()); |
|
273 } |
|
274 |
|
275 @Override |
|
276 public int getInt(int column) { |
|
277 Object value = get(column); |
|
278 if (value == null) return 0; |
|
279 if (value instanceof Number) return ((Number) value).intValue(); |
|
280 return Integer.parseInt(value.toString()); |
|
281 } |
|
282 |
|
283 @Override |
|
284 public long getLong(int column) { |
|
285 Object value = get(column); |
|
286 if (value == null) return 0; |
|
287 if (value instanceof Number) return ((Number) value).longValue(); |
|
288 return Long.parseLong(value.toString()); |
|
289 } |
|
290 |
|
291 @Override |
|
292 public float getFloat(int column) { |
|
293 Object value = get(column); |
|
294 if (value == null) return 0.0f; |
|
295 if (value instanceof Number) return ((Number) value).floatValue(); |
|
296 return Float.parseFloat(value.toString()); |
|
297 } |
|
298 |
|
299 @Override |
|
300 public double getDouble(int column) { |
|
301 Object value = get(column); |
|
302 if (value == null) return 0.0d; |
|
303 if (value instanceof Number) return ((Number) value).doubleValue(); |
|
304 return Double.parseDouble(value.toString()); |
|
305 } |
|
306 |
|
307 @Override |
|
308 public byte[] getBlob(int column) { |
|
309 Object value = get(column); |
|
310 if (value == null) return null; |
|
311 if (value instanceof byte[]) { |
|
312 return (byte[]) value; |
|
313 } |
|
314 if (value instanceof ByteBuffer) { |
|
315 ByteBuffer data = (ByteBuffer)value; |
|
316 byte[] byteArray = new byte[data.remaining()]; |
|
317 data.get(byteArray); |
|
318 return byteArray; |
|
319 } |
|
320 throw new UnsupportedOperationException("BLOB Object not of known type"); |
|
321 } |
|
322 |
|
323 @Override |
|
324 public boolean isNull(int column) { |
|
325 return get(column) == null; |
|
326 } |
|
327 } |