Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
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 */
18 package org.mozilla.gecko.sqlite;
20 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
22 import android.database.AbstractCursor;
23 import android.database.CursorIndexOutOfBoundsException;
25 import java.nio.ByteBuffer;
26 import java.util.ArrayList;
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 {
43 private final String[] columnNames;
44 private Object[] data;
45 private int rowCount = 0;
46 private final int columnCount;
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;
60 if (initialCapacity < 1) {
61 initialCapacity = 1;
62 }
64 this.data = new Object[columnCount * initialCapacity];
65 }
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 }
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 }
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 }
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 }
127 int start = rowCount++ * columnCount;
128 ensureCapacity(start + columnCount);
129 System.arraycopy(columnValues, 0, data, start, columnCount);
130 }
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);
147 if (columnValues instanceof ArrayList<?>) {
148 addRow((ArrayList<?>) columnValues, start);
149 return;
150 }
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 }
163 if (current != end) {
164 // TODO: null out row?
165 throw new IllegalArgumentException(
166 "columnValues.size() < columnNames.length");
167 }
169 // Increase row count here in case we encounter an exception.
170 rowCount++;
171 }
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 }
182 rowCount++;
183 Object[] localData = data;
184 for (int i = 0; i < size; i++) {
185 localData[start + i] = columnValues.get(i);
186 }
187 }
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 }
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 {
209 private int index;
210 private final int endIndex;
212 RowBuilder(int index, int endIndex) {
213 this.index = index;
214 this.endIndex = endIndex;
215 }
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 }
230 data[index++] = columnValue;
231 return this;
232 }
233 }
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 }
249 // AbstractCursor implementation.
250 @Override
251 public int getCount() {
252 return rowCount;
253 }
255 @Override
256 public String[] getColumnNames() {
257 return columnNames;
258 }
260 @Override
261 public String getString(int column) {
262 Object value = get(column);
263 if (value == null) return null;
264 return value.toString();
265 }
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 }
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 }
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 }
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 }
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 }
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 }
323 @Override
324 public boolean isNull(int column) {
325 return get(column) == null;
326 }
327 }