michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: /* michael@0: * Copyright (C) 2007 The Android Open Source Project michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: package org.mozilla.gecko.sqlite; michael@0: michael@0: import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; michael@0: michael@0: import android.database.AbstractCursor; michael@0: import android.database.CursorIndexOutOfBoundsException; michael@0: michael@0: import java.nio.ByteBuffer; michael@0: import java.util.ArrayList; michael@0: michael@0: /* michael@0: * Android's AbstractCursor throws on getBlob() michael@0: * and MatrixCursor forgot to override it. This was fixed michael@0: * at some point but old devices are still SOL. michael@0: * Oh, and everything in MatrixCursor is private instead of michael@0: * protected, so we need to entirely duplicate it here, michael@0: * instad of just being able to add the missing method. michael@0: */ michael@0: /** michael@0: * A mutable cursor implementation backed by an array of {@code Object}s. Use michael@0: * {@link #newRow()} to add rows. Automatically expands internal capacity michael@0: * as needed. michael@0: */ michael@0: public class MatrixBlobCursor extends AbstractCursor { michael@0: michael@0: private final String[] columnNames; michael@0: private Object[] data; michael@0: private int rowCount = 0; michael@0: private final int columnCount; michael@0: michael@0: /** michael@0: * Constructs a new cursor with the given initial capacity. michael@0: * michael@0: * @param columnNames names of the columns, the ordering of which michael@0: * determines column ordering elsewhere in this cursor michael@0: * @param initialCapacity in rows michael@0: */ michael@0: @WrapElementForJNI michael@0: public MatrixBlobCursor(String[] columnNames, int initialCapacity) { michael@0: this.columnNames = columnNames; michael@0: this.columnCount = columnNames.length; michael@0: michael@0: if (initialCapacity < 1) { michael@0: initialCapacity = 1; michael@0: } michael@0: michael@0: this.data = new Object[columnCount * initialCapacity]; michael@0: } michael@0: michael@0: /** michael@0: * Constructs a new cursor. michael@0: * michael@0: * @param columnNames names of the columns, the ordering of which michael@0: * determines column ordering elsewhere in this cursor michael@0: */ michael@0: @WrapElementForJNI michael@0: public MatrixBlobCursor(String[] columnNames) { michael@0: this(columnNames, 16); michael@0: } michael@0: michael@0: /** michael@0: * Gets value at the given column for the current row. michael@0: */ michael@0: protected Object get(int column) { michael@0: if (column < 0 || column >= columnCount) { michael@0: throw new CursorIndexOutOfBoundsException("Requested column: " michael@0: + column + ", # of columns: " + columnCount); michael@0: } michael@0: if (mPos < 0) { michael@0: throw new CursorIndexOutOfBoundsException("Before first row."); michael@0: } michael@0: if (mPos >= rowCount) { michael@0: throw new CursorIndexOutOfBoundsException("After last row."); michael@0: } michael@0: return data[mPos * columnCount + column]; michael@0: } michael@0: michael@0: /** michael@0: * Adds a new row to the end and returns a builder for that row. Not safe michael@0: * for concurrent use. michael@0: * michael@0: * @return builder which can be used to set the column values for the new michael@0: * row michael@0: */ michael@0: public RowBuilder newRow() { michael@0: rowCount++; michael@0: int endIndex = rowCount * columnCount; michael@0: ensureCapacity(endIndex); michael@0: int start = endIndex - columnCount; michael@0: return new RowBuilder(start, endIndex); michael@0: } michael@0: michael@0: /** michael@0: * Adds a new row to the end with the given column values. Not safe michael@0: * for concurrent use. michael@0: * michael@0: * @throws IllegalArgumentException if {@code columnValues.length != michael@0: * columnNames.length} michael@0: * @param columnValues in the same order as the the column names specified michael@0: * at cursor construction time michael@0: */ michael@0: @WrapElementForJNI michael@0: public void addRow(Object[] columnValues) { michael@0: if (columnValues.length != columnCount) { michael@0: throw new IllegalArgumentException("columnNames.length = " michael@0: + columnCount + ", columnValues.length = " michael@0: + columnValues.length); michael@0: } michael@0: michael@0: int start = rowCount++ * columnCount; michael@0: ensureCapacity(start + columnCount); michael@0: System.arraycopy(columnValues, 0, data, start, columnCount); michael@0: } michael@0: michael@0: /** michael@0: * Adds a new row to the end with the given column values. Not safe michael@0: * for concurrent use. michael@0: * michael@0: * @throws IllegalArgumentException if {@code columnValues.size() != michael@0: * columnNames.length} michael@0: * @param columnValues in the same order as the the column names specified michael@0: * at cursor construction time michael@0: */ michael@0: @WrapElementForJNI michael@0: public void addRow(Iterable columnValues) { michael@0: int start = rowCount * columnCount; michael@0: int end = start + columnCount; michael@0: ensureCapacity(end); michael@0: michael@0: if (columnValues instanceof ArrayList) { michael@0: addRow((ArrayList) columnValues, start); michael@0: return; michael@0: } michael@0: michael@0: int current = start; michael@0: Object[] localData = data; michael@0: for (Object columnValue : columnValues) { michael@0: if (current == end) { michael@0: // TODO: null out row? michael@0: throw new IllegalArgumentException( michael@0: "columnValues.size() > columnNames.length"); michael@0: } michael@0: localData[current++] = columnValue; michael@0: } michael@0: michael@0: if (current != end) { michael@0: // TODO: null out row? michael@0: throw new IllegalArgumentException( michael@0: "columnValues.size() < columnNames.length"); michael@0: } michael@0: michael@0: // Increase row count here in case we encounter an exception. michael@0: rowCount++; michael@0: } michael@0: michael@0: /** Optimization for {@link ArrayList}. */ michael@0: @WrapElementForJNI michael@0: private void addRow(ArrayList columnValues, int start) { michael@0: int size = columnValues.size(); michael@0: if (size != columnCount) { michael@0: throw new IllegalArgumentException("columnNames.length = " michael@0: + columnCount + ", columnValues.size() = " + size); michael@0: } michael@0: michael@0: rowCount++; michael@0: Object[] localData = data; michael@0: for (int i = 0; i < size; i++) { michael@0: localData[start + i] = columnValues.get(i); michael@0: } michael@0: } michael@0: michael@0: /** Ensures that this cursor has enough capacity. */ michael@0: private void ensureCapacity(int size) { michael@0: if (size > data.length) { michael@0: Object[] oldData = this.data; michael@0: int newSize = data.length * 2; michael@0: if (newSize < size) { michael@0: newSize = size; michael@0: } michael@0: this.data = new Object[newSize]; michael@0: System.arraycopy(oldData, 0, this.data, 0, oldData.length); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Builds a row, starting from the left-most column and adding one column michael@0: * value at a time. Follows the same ordering as the column names specified michael@0: * at cursor construction time. michael@0: */ michael@0: public class RowBuilder { michael@0: michael@0: private int index; michael@0: private final int endIndex; michael@0: michael@0: RowBuilder(int index, int endIndex) { michael@0: this.index = index; michael@0: this.endIndex = endIndex; michael@0: } michael@0: michael@0: /** michael@0: * Sets the next column value in this row. michael@0: * michael@0: * @throws CursorIndexOutOfBoundsException if you try to add too many michael@0: * values michael@0: * @return this builder to support chaining michael@0: */ michael@0: public RowBuilder add(Object columnValue) { michael@0: if (index == endIndex) { michael@0: throw new CursorIndexOutOfBoundsException( michael@0: "No more columns left."); michael@0: } michael@0: michael@0: data[index++] = columnValue; michael@0: return this; michael@0: } michael@0: } michael@0: michael@0: public void set(int column, Object value) { michael@0: if (column < 0 || column >= columnCount) { michael@0: throw new CursorIndexOutOfBoundsException("Requested column: " michael@0: + column + ", # of columns: " + columnCount); michael@0: } michael@0: if (mPos < 0) { michael@0: throw new CursorIndexOutOfBoundsException("Before first row."); michael@0: } michael@0: if (mPos >= rowCount) { michael@0: throw new CursorIndexOutOfBoundsException("After last row."); michael@0: } michael@0: data[mPos * columnCount + column] = value; michael@0: } michael@0: michael@0: // AbstractCursor implementation. michael@0: @Override michael@0: public int getCount() { michael@0: return rowCount; michael@0: } michael@0: michael@0: @Override michael@0: public String[] getColumnNames() { michael@0: return columnNames; michael@0: } michael@0: michael@0: @Override michael@0: public String getString(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return null; michael@0: return value.toString(); michael@0: } michael@0: michael@0: @Override michael@0: public short getShort(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return 0; michael@0: if (value instanceof Number) return ((Number) value).shortValue(); michael@0: return Short.parseShort(value.toString()); michael@0: } michael@0: michael@0: @Override michael@0: public int getInt(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return 0; michael@0: if (value instanceof Number) return ((Number) value).intValue(); michael@0: return Integer.parseInt(value.toString()); michael@0: } michael@0: michael@0: @Override michael@0: public long getLong(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return 0; michael@0: if (value instanceof Number) return ((Number) value).longValue(); michael@0: return Long.parseLong(value.toString()); michael@0: } michael@0: michael@0: @Override michael@0: public float getFloat(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return 0.0f; michael@0: if (value instanceof Number) return ((Number) value).floatValue(); michael@0: return Float.parseFloat(value.toString()); michael@0: } michael@0: michael@0: @Override michael@0: public double getDouble(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return 0.0d; michael@0: if (value instanceof Number) return ((Number) value).doubleValue(); michael@0: return Double.parseDouble(value.toString()); michael@0: } michael@0: michael@0: @Override michael@0: public byte[] getBlob(int column) { michael@0: Object value = get(column); michael@0: if (value == null) return null; michael@0: if (value instanceof byte[]) { michael@0: return (byte[]) value; michael@0: } michael@0: if (value instanceof ByteBuffer) { michael@0: ByteBuffer data = (ByteBuffer)value; michael@0: byte[] byteArray = new byte[data.remaining()]; michael@0: data.get(byteArray); michael@0: return byteArray; michael@0: } michael@0: throw new UnsupportedOperationException("BLOB Object not of known type"); michael@0: } michael@0: michael@0: @Override michael@0: public boolean isNull(int column) { michael@0: return get(column) == null; michael@0: } michael@0: }