1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/sqlite/MatrixBlobCursor.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,327 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 +/* 1.6 + * Copyright (C) 2007 The Android Open Source Project 1.7 + * 1.8 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.9 + * you may not use this file except in compliance with the License. 1.10 + * You may obtain a copy of the License at 1.11 + * 1.12 + * http://www.apache.org/licenses/LICENSE-2.0 1.13 + * 1.14 + * Unless required by applicable law or agreed to in writing, software 1.15 + * distributed under the License is distributed on an "AS IS" BASIS, 1.16 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.17 + * See the License for the specific language governing permissions and 1.18 + * limitations under the License. 1.19 + */ 1.20 + 1.21 +package org.mozilla.gecko.sqlite; 1.22 + 1.23 +import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; 1.24 + 1.25 +import android.database.AbstractCursor; 1.26 +import android.database.CursorIndexOutOfBoundsException; 1.27 + 1.28 +import java.nio.ByteBuffer; 1.29 +import java.util.ArrayList; 1.30 + 1.31 +/* 1.32 + * Android's AbstractCursor throws on getBlob() 1.33 + * and MatrixCursor forgot to override it. This was fixed 1.34 + * at some point but old devices are still SOL. 1.35 + * Oh, and everything in MatrixCursor is private instead of 1.36 + * protected, so we need to entirely duplicate it here, 1.37 + * instad of just being able to add the missing method. 1.38 + */ 1.39 +/** 1.40 + * A mutable cursor implementation backed by an array of {@code Object}s. Use 1.41 + * {@link #newRow()} to add rows. Automatically expands internal capacity 1.42 + * as needed. 1.43 + */ 1.44 +public class MatrixBlobCursor extends AbstractCursor { 1.45 + 1.46 + private final String[] columnNames; 1.47 + private Object[] data; 1.48 + private int rowCount = 0; 1.49 + private final int columnCount; 1.50 + 1.51 + /** 1.52 + * Constructs a new cursor with the given initial capacity. 1.53 + * 1.54 + * @param columnNames names of the columns, the ordering of which 1.55 + * determines column ordering elsewhere in this cursor 1.56 + * @param initialCapacity in rows 1.57 + */ 1.58 + @WrapElementForJNI 1.59 + public MatrixBlobCursor(String[] columnNames, int initialCapacity) { 1.60 + this.columnNames = columnNames; 1.61 + this.columnCount = columnNames.length; 1.62 + 1.63 + if (initialCapacity < 1) { 1.64 + initialCapacity = 1; 1.65 + } 1.66 + 1.67 + this.data = new Object[columnCount * initialCapacity]; 1.68 + } 1.69 + 1.70 + /** 1.71 + * Constructs a new cursor. 1.72 + * 1.73 + * @param columnNames names of the columns, the ordering of which 1.74 + * determines column ordering elsewhere in this cursor 1.75 + */ 1.76 + @WrapElementForJNI 1.77 + public MatrixBlobCursor(String[] columnNames) { 1.78 + this(columnNames, 16); 1.79 + } 1.80 + 1.81 + /** 1.82 + * Gets value at the given column for the current row. 1.83 + */ 1.84 + protected Object get(int column) { 1.85 + if (column < 0 || column >= columnCount) { 1.86 + throw new CursorIndexOutOfBoundsException("Requested column: " 1.87 + + column + ", # of columns: " + columnCount); 1.88 + } 1.89 + if (mPos < 0) { 1.90 + throw new CursorIndexOutOfBoundsException("Before first row."); 1.91 + } 1.92 + if (mPos >= rowCount) { 1.93 + throw new CursorIndexOutOfBoundsException("After last row."); 1.94 + } 1.95 + return data[mPos * columnCount + column]; 1.96 + } 1.97 + 1.98 + /** 1.99 + * Adds a new row to the end and returns a builder for that row. Not safe 1.100 + * for concurrent use. 1.101 + * 1.102 + * @return builder which can be used to set the column values for the new 1.103 + * row 1.104 + */ 1.105 + public RowBuilder newRow() { 1.106 + rowCount++; 1.107 + int endIndex = rowCount * columnCount; 1.108 + ensureCapacity(endIndex); 1.109 + int start = endIndex - columnCount; 1.110 + return new RowBuilder(start, endIndex); 1.111 + } 1.112 + 1.113 + /** 1.114 + * Adds a new row to the end with the given column values. Not safe 1.115 + * for concurrent use. 1.116 + * 1.117 + * @throws IllegalArgumentException if {@code columnValues.length != 1.118 + * columnNames.length} 1.119 + * @param columnValues in the same order as the the column names specified 1.120 + * at cursor construction time 1.121 + */ 1.122 + @WrapElementForJNI 1.123 + public void addRow(Object[] columnValues) { 1.124 + if (columnValues.length != columnCount) { 1.125 + throw new IllegalArgumentException("columnNames.length = " 1.126 + + columnCount + ", columnValues.length = " 1.127 + + columnValues.length); 1.128 + } 1.129 + 1.130 + int start = rowCount++ * columnCount; 1.131 + ensureCapacity(start + columnCount); 1.132 + System.arraycopy(columnValues, 0, data, start, columnCount); 1.133 + } 1.134 + 1.135 + /** 1.136 + * Adds a new row to the end with the given column values. Not safe 1.137 + * for concurrent use. 1.138 + * 1.139 + * @throws IllegalArgumentException if {@code columnValues.size() != 1.140 + * columnNames.length} 1.141 + * @param columnValues in the same order as the the column names specified 1.142 + * at cursor construction time 1.143 + */ 1.144 + @WrapElementForJNI 1.145 + public void addRow(Iterable<?> columnValues) { 1.146 + int start = rowCount * columnCount; 1.147 + int end = start + columnCount; 1.148 + ensureCapacity(end); 1.149 + 1.150 + if (columnValues instanceof ArrayList<?>) { 1.151 + addRow((ArrayList<?>) columnValues, start); 1.152 + return; 1.153 + } 1.154 + 1.155 + int current = start; 1.156 + Object[] localData = data; 1.157 + for (Object columnValue : columnValues) { 1.158 + if (current == end) { 1.159 + // TODO: null out row? 1.160 + throw new IllegalArgumentException( 1.161 + "columnValues.size() > columnNames.length"); 1.162 + } 1.163 + localData[current++] = columnValue; 1.164 + } 1.165 + 1.166 + if (current != end) { 1.167 + // TODO: null out row? 1.168 + throw new IllegalArgumentException( 1.169 + "columnValues.size() < columnNames.length"); 1.170 + } 1.171 + 1.172 + // Increase row count here in case we encounter an exception. 1.173 + rowCount++; 1.174 + } 1.175 + 1.176 + /** Optimization for {@link ArrayList}. */ 1.177 + @WrapElementForJNI 1.178 + private void addRow(ArrayList<?> columnValues, int start) { 1.179 + int size = columnValues.size(); 1.180 + if (size != columnCount) { 1.181 + throw new IllegalArgumentException("columnNames.length = " 1.182 + + columnCount + ", columnValues.size() = " + size); 1.183 + } 1.184 + 1.185 + rowCount++; 1.186 + Object[] localData = data; 1.187 + for (int i = 0; i < size; i++) { 1.188 + localData[start + i] = columnValues.get(i); 1.189 + } 1.190 + } 1.191 + 1.192 + /** Ensures that this cursor has enough capacity. */ 1.193 + private void ensureCapacity(int size) { 1.194 + if (size > data.length) { 1.195 + Object[] oldData = this.data; 1.196 + int newSize = data.length * 2; 1.197 + if (newSize < size) { 1.198 + newSize = size; 1.199 + } 1.200 + this.data = new Object[newSize]; 1.201 + System.arraycopy(oldData, 0, this.data, 0, oldData.length); 1.202 + } 1.203 + } 1.204 + 1.205 + /** 1.206 + * Builds a row, starting from the left-most column and adding one column 1.207 + * value at a time. Follows the same ordering as the column names specified 1.208 + * at cursor construction time. 1.209 + */ 1.210 + public class RowBuilder { 1.211 + 1.212 + private int index; 1.213 + private final int endIndex; 1.214 + 1.215 + RowBuilder(int index, int endIndex) { 1.216 + this.index = index; 1.217 + this.endIndex = endIndex; 1.218 + } 1.219 + 1.220 + /** 1.221 + * Sets the next column value in this row. 1.222 + * 1.223 + * @throws CursorIndexOutOfBoundsException if you try to add too many 1.224 + * values 1.225 + * @return this builder to support chaining 1.226 + */ 1.227 + public RowBuilder add(Object columnValue) { 1.228 + if (index == endIndex) { 1.229 + throw new CursorIndexOutOfBoundsException( 1.230 + "No more columns left."); 1.231 + } 1.232 + 1.233 + data[index++] = columnValue; 1.234 + return this; 1.235 + } 1.236 + } 1.237 + 1.238 + public void set(int column, Object value) { 1.239 + if (column < 0 || column >= columnCount) { 1.240 + throw new CursorIndexOutOfBoundsException("Requested column: " 1.241 + + column + ", # of columns: " + columnCount); 1.242 + } 1.243 + if (mPos < 0) { 1.244 + throw new CursorIndexOutOfBoundsException("Before first row."); 1.245 + } 1.246 + if (mPos >= rowCount) { 1.247 + throw new CursorIndexOutOfBoundsException("After last row."); 1.248 + } 1.249 + data[mPos * columnCount + column] = value; 1.250 + } 1.251 + 1.252 + // AbstractCursor implementation. 1.253 + @Override 1.254 + public int getCount() { 1.255 + return rowCount; 1.256 + } 1.257 + 1.258 + @Override 1.259 + public String[] getColumnNames() { 1.260 + return columnNames; 1.261 + } 1.262 + 1.263 + @Override 1.264 + public String getString(int column) { 1.265 + Object value = get(column); 1.266 + if (value == null) return null; 1.267 + return value.toString(); 1.268 + } 1.269 + 1.270 + @Override 1.271 + public short getShort(int column) { 1.272 + Object value = get(column); 1.273 + if (value == null) return 0; 1.274 + if (value instanceof Number) return ((Number) value).shortValue(); 1.275 + return Short.parseShort(value.toString()); 1.276 + } 1.277 + 1.278 + @Override 1.279 + public int getInt(int column) { 1.280 + Object value = get(column); 1.281 + if (value == null) return 0; 1.282 + if (value instanceof Number) return ((Number) value).intValue(); 1.283 + return Integer.parseInt(value.toString()); 1.284 + } 1.285 + 1.286 + @Override 1.287 + public long getLong(int column) { 1.288 + Object value = get(column); 1.289 + if (value == null) return 0; 1.290 + if (value instanceof Number) return ((Number) value).longValue(); 1.291 + return Long.parseLong(value.toString()); 1.292 + } 1.293 + 1.294 + @Override 1.295 + public float getFloat(int column) { 1.296 + Object value = get(column); 1.297 + if (value == null) return 0.0f; 1.298 + if (value instanceof Number) return ((Number) value).floatValue(); 1.299 + return Float.parseFloat(value.toString()); 1.300 + } 1.301 + 1.302 + @Override 1.303 + public double getDouble(int column) { 1.304 + Object value = get(column); 1.305 + if (value == null) return 0.0d; 1.306 + if (value instanceof Number) return ((Number) value).doubleValue(); 1.307 + return Double.parseDouble(value.toString()); 1.308 + } 1.309 + 1.310 + @Override 1.311 + public byte[] getBlob(int column) { 1.312 + Object value = get(column); 1.313 + if (value == null) return null; 1.314 + if (value instanceof byte[]) { 1.315 + return (byte[]) value; 1.316 + } 1.317 + if (value instanceof ByteBuffer) { 1.318 + ByteBuffer data = (ByteBuffer)value; 1.319 + byte[] byteArray = new byte[data.remaining()]; 1.320 + data.get(byteArray); 1.321 + return byteArray; 1.322 + } 1.323 + throw new UnsupportedOperationException("BLOB Object not of known type"); 1.324 + } 1.325 + 1.326 + @Override 1.327 + public boolean isNull(int column) { 1.328 + return get(column) == null; 1.329 + } 1.330 +}