diff --git a/jni/net_sqlcipher_CursorWindow.cpp b/jni/net_sqlcipher_CursorWindow.cpp index c1e53af0..98eb9cc9 100644 --- a/jni/net_sqlcipher_CursorWindow.cpp +++ b/jni/net_sqlcipher_CursorWindow.cpp @@ -271,6 +271,22 @@ LOG_WINDOW("Checking if column is an integer for %d,%d from %p", row, column, wi return field.type == FIELD_TYPE_INTEGER; } +static jint getType_native(JNIEnv* env, jobject object, jint row, jint column) +{ + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); +LOG_WINDOW("Getting type for %d,%d from %p", row, column, window); + + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return NULL; + } + + return field.type; +} + static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column) { int32_t err; @@ -677,6 +693,7 @@ static JNINativeMethod sMethods[] = {"isString_native", "(II)Z", (void *)isString_native}, {"isFloat_native", "(II)Z", (void *)isFloat_native}, {"isInteger_native", "(II)Z", (void *)isInteger_native}, + {"getType_native", "(II)I", (void *)getType_native}, }; int register_android_database_CursorWindow(JNIEnv * env) diff --git a/src/net/sqlcipher/AbstractCursor.java b/src/net/sqlcipher/AbstractCursor.java index 651a1fea..bfb4e3c6 100644 --- a/src/net/sqlcipher/AbstractCursor.java +++ b/src/net/sqlcipher/AbstractCursor.java @@ -56,6 +56,10 @@ public abstract class AbstractCursor implements android.database.CrossProcessCur abstract public double getDouble(int column); abstract public boolean isNull(int column); + public int getType(int column) { + throw new UnsupportedOperationException(); + } + // TODO implement getBlob in all cursor types public byte[] getBlob(int column) { throw new UnsupportedOperationException("getBlob is not supported"); @@ -151,6 +155,8 @@ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { result.getChars(0, result.length(), data, 0); } buffer.sizeCopied = result.length(); + } else { + buffer.sizeCopied = 0; } } @@ -205,7 +211,7 @@ public final boolean moveToPosition(int position) { * @param window */ public void fillWindow(int position, android.database.CursorWindow window) { - if (position < 0 || position > getCount()) { + if (position < 0 || position >= getCount()) { return; } window.acquireReference(); @@ -523,6 +529,10 @@ public void setNotificationUri(ContentResolver cr, Uri notifyUri) { } } + public Uri getNotificationUri() { + return mNotifyUri; + } + public boolean getWantsAllOnMoveCalls() { return false; } @@ -630,6 +640,12 @@ public void onChange(boolean selfChange) { protected int mRowIdColumnIndex; protected int mPos; + + /** + * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of + * the column at {@link #mRowIdColumnIndex} for the current row this cursor is + * pointing at. + */ protected Long mCurrentRowID; protected ContentResolver mContentResolver; protected boolean mClosed = false; diff --git a/src/net/sqlcipher/AbstractWindowedCursor.java b/src/net/sqlcipher/AbstractWindowedCursor.java index c1f707d2..ec48e709 100644 --- a/src/net/sqlcipher/AbstractWindowedCursor.java +++ b/src/net/sqlcipher/AbstractWindowedCursor.java @@ -210,6 +210,12 @@ public boolean isFloat(int columnIndex) return mWindow.isFloat(mPos, columnIndex); } + @Override + public int getType(int columnIndex) { + checkPosition(); + return mWindow.getType(mPos, columnIndex); + } + @Override protected void checkPosition() { diff --git a/src/net/sqlcipher/CursorWindow.java b/src/net/sqlcipher/CursorWindow.java index 78d08882..ec4d25b9 100644 --- a/src/net/sqlcipher/CursorWindow.java +++ b/src/net/sqlcipher/CursorWindow.java @@ -17,9 +17,17 @@ package net.sqlcipher; import android.database.CharArrayBuffer; +import android.database.Cursor; + +import android.content.res.Resources; +import android.database.sqlite.SQLiteClosable; +import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; +import android.util.Log; +import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. @@ -27,6 +35,10 @@ public class CursorWindow extends android.database.CursorWindow implements Parcelable { /** The pointer to the native window class */ @SuppressWarnings("unused") + + /** The pointer to the native window class. set by the native methods in + * android_database_CursorWindow.cpp + */ private int nWindow; private int mStartPos; @@ -227,9 +239,9 @@ public boolean isNull(int row, int col) { releaseReference(); } } - + private native boolean isNull_native(int row, int col); - + /** * Returns a byte array for the given field. * @@ -246,14 +258,50 @@ public byte[] getBlob(int row, int col) { } } + /** + * Returns the value at (row, col) as a byte array. + * + *

If the value is null, then null is returned. If the + * type of column col is a string type, then the result + * is the array of bytes that make up the internal representation of the + * string value. If the type of column col is integral or floating-point, + * then an {@link SQLiteException} is thrown. + */ private native byte[] getBlob_native(int row, int col); + /** + * Returns data type of the given column's value. + *

+ * Returned column types are + *

+ *

+ * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return the value type + */ + public int getType(int row, int col) { + acquireReference(); + try { + return getType_native(row - mStartPos, col); + } finally { + releaseReference(); + } + } + /** * Checks if a field contains either a blob or is null. * * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is {@code NULL} or a blob + * @deprecated use {@link #getType(int, int)} instead */ public boolean isBlob(int row, int col) { acquireReference(); @@ -270,6 +318,7 @@ public boolean isBlob(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is a long + * @deprecated use {@link #getType(int, int)} instead */ public boolean isLong(int row, int col) { acquireReference(); @@ -286,6 +335,7 @@ public boolean isLong(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is a float + * @deprecated use {@link #getType(int, int)} instead */ public boolean isFloat(int row, int col) { acquireReference(); @@ -302,6 +352,7 @@ public boolean isFloat(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is {@code NULL} or a String + * @deprecated use {@link #getType(int, int)} instead */ public boolean isString(int row, int col) { acquireReference(); @@ -317,6 +368,8 @@ public boolean isString(int row, int col) { private native boolean isInteger_native(int row, int col); private native boolean isFloat_native(int row, int col); + private native int getType_native(int row, int col); + /** * Returns a String for the given field. * @@ -333,6 +386,19 @@ public String getString(int row, int col) { } } + /** + * Returns the value at (row, col) as a String. + * + *

If the value is null, then null is returned. If the + * type of column col is integral, then the result is the string + * that is obtained by formatting the integer value with the printf + * family of functions using format specifier %lld. If the + * type of column col is floating-point, then the result is the string + * that is obtained by formatting the floating-point value with the + * printf family of functions using format specifier %g. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native String getString_native(int row, int col); /** @@ -384,6 +450,17 @@ public long getLong(int row, int col) { } } + /** + * Returns the value at (row, col) as a long. + * + *

If the value is null, then 0L is returned. If the + * type of column col is a string type, then the result + * is the long that is obtained by parsing the string value with + * strtoll. If the type of column col is + * floating-point, then the result is the floating-point value casted to a long. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native long getLong_native(int row, int col); /** @@ -403,6 +480,17 @@ public double getDouble(int row, int col) { } } + /** + * Returns the value at (row, col) as a double. + * + *

If the value is null, then 0.0 is returned. If the + * type of column col is a string type, then the result + * is the double that is obtained by parsing the string value with + * strtod. If the type of column col is + * integral, then the result is the integer value casted to a double. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native double getDouble_native(int row, int col); /** @@ -485,6 +573,9 @@ public void close() { @Override protected void finalize() { // Just in case someone forgot to call close... + if (nWindow == 0) { + return; + } close_native(); } @@ -534,6 +625,7 @@ public CursorWindow(Parcel source,int foo) { @Override protected void onAllReferencesReleased() { close_native(); - super.onAllReferencesReleased(); + + super.onAllReferencesReleased(); } } diff --git a/src/net/sqlcipher/DatabaseUtils.java b/src/net/sqlcipher/DatabaseUtils.java index 34b9a220..ceaf5e13 100644 --- a/src/net/sqlcipher/DatabaseUtils.java +++ b/src/net/sqlcipher/DatabaseUtils.java @@ -194,6 +194,37 @@ public static void bindObjectToProgram(SQLiteProgram prog, int index, } } + /** + * Returns data type of the given object's value. + *

+ * Returned values are + *

+ *

+ * + * @param obj the object whose value type is to be returned + * @return object value type + * @hide + */ + public static int getTypeOfObject(Object obj) { + if (obj == null) { + return 0; /* Cursor.FIELD_TYPE_NULL */ + } else if (obj instanceof byte[]) { + return 4; /* Cursor.FIELD_TYPE_BLOB */ + } else if (obj instanceof Float || obj instanceof Double) { + return 2; /* Cursor.FIELD_TYPE_FLOAT */ + } else if (obj instanceof Long || obj instanceof Integer) { + return 1; /* Cursor.FIELD_TYPE_INTEGER */ + } else { + return 3; /* Cursor.FIELD_TYPE_STRING */ + } + } + /** * Appends an SQL string to the given StringBuilder, including the opening * and closing single quotes. Any single quotes internal to sqlString will diff --git a/src/net/sqlcipher/MatrixCursor.java b/src/net/sqlcipher/MatrixCursor.java index 140a291e..6ca0798c 100644 --- a/src/net/sqlcipher/MatrixCursor.java +++ b/src/net/sqlcipher/MatrixCursor.java @@ -274,6 +274,11 @@ public double getDouble(int column) { return Double.parseDouble(value.toString()); } + @Override + public int getType(int column) { + return DatabaseUtils.getTypeOfObject(get(column)); + } + @Override public boolean isNull(int column) { return get(column) == null;