
org.robolectric.shadows.ShadowCursorWindow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of framework Show documentation
Show all versions of framework Show documentation
An alternative Android testing framework.
The newest version!
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.robolectric.RuntimeEnvironment.castNativePtr;
import android.database.Cursor;
import android.database.CursorWindow;
import com.almworks.sqlite4java.SQLiteConstants;
import com.almworks.sqlite4java.SQLiteException;
import com.almworks.sqlite4java.SQLiteStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(value = CursorWindow.class)
public class ShadowCursorWindow {
private static final WindowData WINDOW_DATA = new WindowData();
@Implementation
public static Number nativeCreate(String name, int cursorWindowSize) {
return castNativePtr(WINDOW_DATA.create(name, cursorWindowSize));
}
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeDispose(int windowPtr) {
nativeDispose((long) windowPtr);
}
@Implementation(minSdk = LOLLIPOP)
public static void nativeDispose(long windowPtr) {
WINDOW_DATA.close(windowPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static byte[] nativeGetBlob(int windowPtr, int row, int column) {
return nativeGetBlob((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static byte[] nativeGetBlob(long windowPtr, int row, int column) {
Value value = WINDOW_DATA.get(windowPtr).value(row, column);
switch (value.type) {
case Cursor.FIELD_TYPE_NULL:
return null;
case Cursor.FIELD_TYPE_BLOB:
// This matches Android's behavior, which does not match the SQLite spec
byte[] blob = (byte[])value.value;
return blob == null ? new byte[]{} : blob;
case Cursor.FIELD_TYPE_STRING:
return ((String)value.value).getBytes(UTF_8);
default:
throw new android.database.sqlite.SQLiteException("Getting blob when column is non-blob. Row " + row + ", col " + column);
}
}
@Implementation(maxSdk = KITKAT_WATCH)
public static String nativeGetString(int windowPtr, int row, int column) {
return nativeGetString((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static String nativeGetString(long windowPtr, int row, int column) {
Value val = WINDOW_DATA.get(windowPtr).value(row, column);
if (val.type == Cursor.FIELD_TYPE_BLOB) {
throw new android.database.sqlite.SQLiteException("Getting string when column is blob. Row " + row + ", col " + column);
}
Object value = val.value;
return value == null ? null : String.valueOf(value);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static long nativeGetLong(int windowPtr, int row, int column) {
return nativeGetLong((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static long nativeGetLong(long windowPtr, int row, int column) {
return nativeGetNumber(windowPtr, row, column).longValue();
}
@Implementation(maxSdk = KITKAT_WATCH)
public static double nativeGetDouble(int windowPtr, int row, int column) {
return nativeGetDouble((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static double nativeGetDouble(long windowPtr, int row, int column) {
return nativeGetNumber(windowPtr, row, column).doubleValue();
}
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeGetType(int windowPtr, int row, int column) {
return nativeGetType((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static int nativeGetType(long windowPtr, int row, int column) {
return WINDOW_DATA.get(windowPtr).value(row, column).type;
}
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeClear(int windowPtr) {
nativeClear((long) windowPtr);
}
@Implementation(minSdk = LOLLIPOP)
public static void nativeClear(long windowPtr) {
WINDOW_DATA.clear(windowPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeGetNumRows(int windowPtr) {
return nativeGetNumRows((long) windowPtr);
}
@Implementation(minSdk = LOLLIPOP)
public static int nativeGetNumRows(long windowPtr) {
return WINDOW_DATA.get(windowPtr).numRows();
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativePutBlob(int windowPtr, byte[] value, int row, int column) {
return nativePutBlob((long) windowPtr, value, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativePutBlob(long windowPtr, byte[] value, int row, int column) {
return WINDOW_DATA.get(windowPtr).putValue(new Value(value, Cursor.FIELD_TYPE_BLOB), row, column);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativePutString(int windowPtr, String value, int row, int column) {
return nativePutString((long) windowPtr, value, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativePutString(long windowPtr, String value, int row, int column) {
return WINDOW_DATA.get(windowPtr).putValue(new Value(value, Cursor.FIELD_TYPE_STRING), row, column);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativePutLong(int windowPtr, long value, int row, int column) {
return nativePutLong((long) windowPtr, value, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativePutLong(long windowPtr, long value, int row, int column) {
return WINDOW_DATA.get(windowPtr).putValue(new Value(value, Cursor.FIELD_TYPE_INTEGER), row, column);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativePutDouble(int windowPtr, double value, int row, int column) {
return nativePutDouble((long) windowPtr, value, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativePutDouble(long windowPtr, double value, int row, int column) {
return WINDOW_DATA.get(windowPtr).putValue(new Value(value, Cursor.FIELD_TYPE_FLOAT), row, column);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativePutNull(int windowPtr, int row, int column) {
return nativePutNull((long) windowPtr, row, column);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativePutNull(long windowPtr, int row, int column) {
return WINDOW_DATA.get(windowPtr).putValue(new Value(null, Cursor.FIELD_TYPE_NULL), row, column);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativeAllocRow(int windowPtr) {
return nativeAllocRow((long) windowPtr);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativeAllocRow(long windowPtr) {
return WINDOW_DATA.get(windowPtr).allocRow();
}
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativeSetNumColumns(int windowPtr, int columnNum) {
return nativeSetNumColumns((long) windowPtr, columnNum);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativeSetNumColumns(long windowPtr, int columnNum) {
return WINDOW_DATA.get(windowPtr).setNumColumns(columnNum);
}
@Implementation(maxSdk = KITKAT_WATCH)
public static String nativeGetName(int windowPtr) {
return nativeGetName((long) windowPtr);
}
@Implementation(minSdk = LOLLIPOP)
public static String nativeGetName(long windowPtr) {
return WINDOW_DATA.get(windowPtr).getName();
}
protected static int setData(long windowPtr, SQLiteStatement stmt) throws SQLiteException {
return WINDOW_DATA.setData(windowPtr, stmt);
}
private static Number nativeGetNumber(long windowPtr, int row, int column) {
Value value = WINDOW_DATA.get(windowPtr).value(row, column);
switch (value.type) {
case Cursor.FIELD_TYPE_NULL:
case SQLiteConstants.SQLITE_NULL:
return 0;
case Cursor.FIELD_TYPE_INTEGER:
case Cursor.FIELD_TYPE_FLOAT:
return (Number) value.value;
case Cursor.FIELD_TYPE_STRING: {
try {
return Double.parseDouble((String) value.value);
} catch (NumberFormatException e) {
return 0;
}
}
case Cursor.FIELD_TYPE_BLOB:
throw new android.database.sqlite.SQLiteException("could not convert "+value);
default:
throw new android.database.sqlite.SQLiteException("unknown type: "+value.type);
}
}
private static class Data {
private final List rows;
private final String name;
private int numColumns;
public Data(String name, int cursorWindowSize) {
this.name = name;
this.rows = new ArrayList(cursorWindowSize);
}
public Value value(int rowN, int colN) {
Row row = rows.get(rowN);
if (row == null) {
throw new IllegalArgumentException("Bad row number: " + rowN + ", count: " + rows.size());
}
return row.get(colN);
}
public int numRows() {
return rows.size();
}
public boolean putValue(Value value, int rowN, int colN) {
return rows.get(rowN).set(colN, value);
}
public void fillWith(SQLiteStatement stmt) throws SQLiteException {
//Android caches results in the WindowedCursor to allow moveToPrevious() to function.
//Robolectric will have to cache the results too. In the rows list.
while (stmt.step()) {
rows.add(fillRowValues(stmt));
}
}
private static int cursorValueType(final int sqliteType) {
switch (sqliteType) {
case SQLiteConstants.SQLITE_NULL: return Cursor.FIELD_TYPE_NULL;
case SQLiteConstants.SQLITE_INTEGER: return Cursor.FIELD_TYPE_INTEGER;
case SQLiteConstants.SQLITE_FLOAT: return Cursor.FIELD_TYPE_FLOAT;
case SQLiteConstants.SQLITE_TEXT: return Cursor.FIELD_TYPE_STRING;
case SQLiteConstants.SQLITE_BLOB: return Cursor.FIELD_TYPE_BLOB;
default:
throw new IllegalArgumentException("Bad SQLite type " + sqliteType + ". See possible values in SQLiteConstants.");
}
}
private static Row fillRowValues(SQLiteStatement stmt) throws SQLiteException {
final int columnCount = stmt.columnCount();
Row row = new Row(columnCount);
for (int index = 0; index < columnCount; index++) {
row.set(index, new Value(stmt.columnValue(index), cursorValueType(stmt.columnType(index))));
}
return row;
}
public void clear() {
rows.clear();
}
public boolean allocRow() {
rows.add(new Row(numColumns));
return true;
}
public boolean setNumColumns(int numColumns) {
this.numColumns = numColumns;
return true;
}
public String getName() {
return name;
}
}
private static class Row {
private final List values;
public Row(int length) {
values = new ArrayList(length);
for (int i=0; i dataMap = new ConcurrentHashMap<>();
public Data get(long ptr) {
Data data = dataMap.get(ptr);
if (data == null) {
throw new IllegalArgumentException("Invalid window pointer: " + ptr + "; current pointers: " + dataMap.keySet());
}
return data;
}
public int setData(final long ptr, final SQLiteStatement stmt) throws SQLiteException {
Data data = get(ptr);
data.fillWith(stmt);
return data.numRows();
}
public void close(final long ptr) {
Data removed = dataMap.remove(ptr);
if (removed == null) {
throw new IllegalArgumentException("Bad cursor window pointer " + ptr + ". Valid pointers: " + dataMap.keySet());
}
}
public void clear(final long ptr) {
get(ptr).clear();
}
public long create(String name, int cursorWindowSize) {
long ptr = windowPtrCounter.incrementAndGet();
dataMap.put(ptr, new Data(name, cursorWindowSize));
return ptr;
}
}
// TODO: Implement these methods
// private static native int nativeCreateFromParcel(Parcel parcel);
// private static native void nativeWriteToParcel($ptrClass windowPtr, Parcel parcel);
// private static native void nativeFreeLastRow($ptrClass windowPtr);
// private static native void nativeCopyStringToBuffer($ptrClass windowPtr, int row, int column, CharArrayBuffer buffer);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy