blob: 91449bc565ef8ee7398d5e3ba651915f242550b7 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#undef LOG_TAG
#define LOG_TAG "CursorWindow"
#include <jni.h>
#include <JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "CursorWindow.h"
#include "sqlite3_exception.h"
#include "android_util_Binder.h"
namespace android {
static jfieldID gWindowField;
static jfieldID gBufferField;
static jfieldID gSizeCopiedField;
#define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))
#define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))
#define SET_BUFFER(env, object, buf) (env->SetObjectField(object, gBufferField, buf))
#define SET_SIZE_COPIED(env, object, size) (env->SetIntField(object, gSizeCopiedField, size))
CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow)
{
return GET_WINDOW(env, javaWindow);
}
static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
{
uint8_t * data;
size_t size;
CursorWindow * window;
window = new CursorWindow(MAX_WINDOW_SIZE);
if (!window) {
jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
return;
}
if (!window->initBuffer(localOnly)) {
jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window");
delete window;
return;
}
LOG_WINDOW("native_init_empty: window = %p", window);
SET_WINDOW(env, object, window);
}
static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
{
sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
if (memory == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
return;
}
CursorWindow * window = new CursorWindow();
if (!window) {
jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
return;
}
if (!window->setMemory(memory)) {
jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
delete window;
return;
}
LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
SET_WINDOW(env, object, window);
}
static jobject native_getBinder(JNIEnv * env, jobject object)
{
CursorWindow * window = GET_WINDOW(env, object);
if (window) {
sp<IMemory> memory = window->getMemory();
if (memory != NULL) {
sp<IBinder> binder = memory->asBinder();
return javaObjectForIBinder(env, binder);
}
}
return NULL;
}
static void native_clear(JNIEnv * env, jobject object)
{
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Clearing window %p", window);
if (window == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "clear() called after close()");
return;
}
window->clear();
}
static void native_close(JNIEnv * env, jobject object)
{
CursorWindow * window = GET_WINDOW(env, object);
if (window) {
LOG_WINDOW("Closing window %p", window);
delete window;
SET_WINDOW(env, object, 0);
}
}
static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column)
{
char buf[100];
snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
static void throwUnknowTypeException(JNIEnv * env, jint type)
{
char buf[80];
snprintf(buf, sizeof(buf), "UNKNOWN type %d", type);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
static jlong getLong_native(JNIEnv * env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Getting long 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 0;
}
uint8_t type = field.type;
if (type == FIELD_TYPE_INTEGER) {
int64_t value;
if (window->getLong(row, column, &value)) {
return value;
}
return 0;
} else if (type == FIELD_TYPE_STRING) {
uint32_t size = field.data.buffer.size;
if (size > 0) {
#if WINDOW_STORAGE_UTF8
return strtoll((char const *)window->offsetToPtr(field.data.buffer.offset), NULL, 0);
#else
String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
char const * str = ascii.string();
return strtoll(str, NULL, 0);
#endif
} else {
return 0;
}
} else if (type == FIELD_TYPE_FLOAT) {
double value;
if (window->getDouble(row, column, &value)) {
return value;
}
return 0;
} else if (type == FIELD_TYPE_NULL) {
return 0;
} else if (type == FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to long");
return 0;
} else {
throwUnknowTypeException(env, type);
return 0;
}
}
static jbyteArray getBlob_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Getting blob 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;
}
uint8_t type = field.type;
if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) {
jbyteArray byteArray = env->NewByteArray(field.data.buffer.size);
LOG_ASSERT(byteArray, "Native could not create new byte[]");
env->SetByteArrayRegion(byteArray, 0, field.data.buffer.size,
(const jbyte*)window->offsetToPtr(field.data.buffer.offset));
return byteArray;
} else if (type == FIELD_TYPE_INTEGER) {
throw_sqlite3_exception(env, "INTEGER data in getBlob_native ");
} else if (type == FIELD_TYPE_FLOAT) {
throw_sqlite3_exception(env, "FLOAT data in getBlob_native ");
} else if (type == FIELD_TYPE_NULL) {
// do nothing
} else {
throwUnknowTypeException(env, type);
}
return NULL;
}
static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a blob or null 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 == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL;
}
static jboolean isString_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a string or null 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 == FIELD_TYPE_STRING || field.type == FIELD_TYPE_NULL;
}
static jboolean isInteger_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is an integer 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 == FIELD_TYPE_INTEGER;
}
static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a float 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 == FIELD_TYPE_FLOAT;
}
static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Getting string 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;
}
uint8_t type = field.type;
if (type == FIELD_TYPE_STRING) {
uint32_t size = field.data.buffer.size;
if (size > 0) {
#if WINDOW_STORAGE_UTF8
// Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
return env->NewString((jchar const *)utf16.string(), utf16.size());
#else
return env->NewString((jchar const *)window->offsetToPtr(field.data.buffer.offset), size / 2);
#endif
} else {
return env->NewStringUTF("");
}
} else if (type == FIELD_TYPE_INTEGER) {
int64_t value;
if (window->getLong(row, column, &value)) {
char buf[32];
snprintf(buf, sizeof(buf), "%lld", value);
return env->NewStringUTF(buf);
}
return NULL;
} else if (type == FIELD_TYPE_FLOAT) {
double value;
if (window->getDouble(row, column, &value)) {
char buf[32];
snprintf(buf, sizeof(buf), "%g", value);
return env->NewStringUTF(buf);
}
return NULL;
} else if (type == FIELD_TYPE_NULL) {
return NULL;
} else if (type == FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
return NULL;
} else {
throwUnknowTypeException(env, type);
return NULL;
}
}
/**
* Use this only to convert characters that are known to be within the
* 0-127 range for direct conversion to UTF-16
*/
static jint charToJchar(const char* src, jchar* dst, jint bufferSize)
{
int32_t len = strlen(src);
if (bufferSize < len) {
len = bufferSize;
}
for (int i = 0; i < len; i++) {
*dst++ = (*src++ & 0x7F);
}
return len;
}
static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row,
jint column, jint bufferSize, jobject buf)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
field_slot_t field;
err = window->read_field_slot(row, column, &field);
if (err != 0) {
jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot");
return NULL;
}
jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField);
if (buffer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null");
return NULL;
}
jchar* dst = env->GetCharArrayElements(buffer, NULL);
uint8_t type = field.type;
uint32_t sizeCopied = 0;
jcharArray newArray = NULL;
if (type == FIELD_TYPE_STRING) {
uint32_t size = field.data.buffer.size;
if (size > 0) {
#if WINDOW_STORAGE_UTF8
// Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
int32_t strSize = utf16.size();
if (strSize > bufferSize || dst == NULL) {
newArray = env->NewCharArray(strSize);
env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string());
} else {
memcpy(dst, (jchar const *)utf16.string(), strSize * 2);
}
sizeCopied = strSize;
#else
sizeCopied = size/2 + size % 2;
if (size > bufferSize * 2 || dst == NULL) {
newArray = env->NewCharArray(sizeCopied);
memcpy(newArray, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
} else {
memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
}
#endif
}
} else if (type == FIELD_TYPE_INTEGER) {
int64_t value;
if (window->getLong(row, column, &value)) {
char buf[32];
int len;
snprintf(buf, sizeof(buf), "%lld", value);
jchar* dst = env->GetCharArrayElements(buffer, NULL);
sizeCopied = charToJchar(buf, dst, bufferSize);
}
} else if (type == FIELD_TYPE_FLOAT) {
double value;
if (window->getDouble(row, column, &value)) {
char tempbuf[32];
snprintf(tempbuf, sizeof(tempbuf), "%g", value);
jchar* dst = env->GetCharArrayElements(buffer, NULL);
sizeCopied = charToJchar(tempbuf, dst, bufferSize);
}
} else if (type == FIELD_TYPE_NULL) {
} else if (type == FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
} else {
LOGE("Unknown field type %d", type);
throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()");
}
SET_SIZE_COPIED(env, buf, sizeCopied);
env->ReleaseCharArrayElements(buffer, dst, JNI_OK);
return newArray;
}
static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Getting double 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 0.0;
}
uint8_t type = field.type;
if (type == FIELD_TYPE_FLOAT) {
double value;
if (window->getDouble(row, column, &value)) {
return value;
}
return 0.0;
} else if (type == FIELD_TYPE_STRING) {
uint32_t size = field.data.buffer.size;
if (size > 0) {
#if WINDOW_STORAGE_UTF8
return strtod((char const *)window->offsetToPtr(field.data.buffer.offset), NULL);
#else
String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
char const * str = ascii.string();
return strtod(str, NULL);
#endif
} else {
return 0.0;
}
} else if (type == FIELD_TYPE_INTEGER) {
int64_t value;
if (window->getLong(row, column, &value)) {
return (double) value;
}
return 0.0;
} else if (type == FIELD_TYPE_NULL) {
return 0.0;
} else if (type == FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to double");
return 0.0;
} else {
throwUnknowTypeException(env, type);
return 0.0;
}
}
static jboolean isNull_native(JNIEnv* env, jobject object, jint row, jint column)
{
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window);
bool isNull;
if (window->getNull(row, column, &isNull)) {
return isNull;
}
//TODO throw execption?
return true;
}
static jint getNumRows(JNIEnv * env, jobject object)
{
CursorWindow * window = GET_WINDOW(env, object);
return window->getNumRows();
}
static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum)
{
CursorWindow * window = GET_WINDOW(env, object);
return window->setNumColumns(columnNum);
}
static jboolean allocRow(JNIEnv * env, jobject object)
{
CursorWindow * window = GET_WINDOW(env, object);
return window->allocRow() != NULL;
}
static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col)
{
CursorWindow * window = GET_WINDOW(env, object);
if (!value) {
LOG_WINDOW("How did a null value send to here");
return false;
}
field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
if (fieldSlot == NULL) {
LOG_WINDOW(" getFieldSlotWithCheck error ");
return false;
}
jint len = env->GetArrayLength(value);
int offset = window->alloc(len);
if (!offset) {
LOG_WINDOW("Failed allocating %u bytes", len);
return false;
}
jbyte * bytes = env->GetByteArrayElements(value, NULL);
window->copyIn(offset, (uint8_t const *)bytes, len);
// This must be updated after the call to alloc(), since that
// may move the field around in the window
fieldSlot->type = FIELD_TYPE_BLOB;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = len;
env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset);
return true;
}
static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col)
{
CursorWindow * window = GET_WINDOW(env, object);
if (!value) {
LOG_WINDOW("How did a null value send to here");
return false;
}
field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
if (fieldSlot == NULL) {
LOG_WINDOW(" getFieldSlotWithCheck error ");
return false;
}
#if WINDOW_STORAGE_UTF8
int len = env->GetStringUTFLength(value) + 1;
char const * valStr = env->GetStringUTFChars(value, NULL);
#else
int len = env->GetStringLength(value);
// GetStringLength return number of chars and one char takes 2 bytes
len *= 2;
const jchar* valStr = env->GetStringChars(value, NULL);
#endif
if (!valStr) {
LOG_WINDOW("value can't be transfer to UTFChars");
return false;
}
int offset = window->alloc(len);
if (!offset) {
LOG_WINDOW("Failed allocating %u bytes", len);
#if WINDOW_STORAGE_UTF8
env->ReleaseStringUTFChars(value, valStr);
#else
env->ReleaseStringChars(value, valStr);
#endif
return false;
}
window->copyIn(offset, (uint8_t const *)valStr, len);
// This must be updated after the call to alloc(), since that
// may move the field around in the window
fieldSlot->type = FIELD_TYPE_STRING;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = len;
LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset);
#if WINDOW_STORAGE_UTF8
env->ReleaseStringUTFChars(value, valStr);
#else
env->ReleaseStringChars(value, valStr);
#endif
return true;
}
static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col)
{
CursorWindow * window = GET_WINDOW(env, object);
if (!window->putLong(row, col, value)) {
LOG_WINDOW(" getFieldSlotWithCheck error ");
return false;
}
LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value);
return true;
}
static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col)
{
CursorWindow * window = GET_WINDOW(env, object);
if (!window->putDouble(row, col, value)) {
LOG_WINDOW(" getFieldSlotWithCheck error ");
return false;
}
LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value);
return true;
}
static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col)
{
CursorWindow * window = GET_WINDOW(env, object);
if (!window->putNull(row, col)) {
LOG_WINDOW(" getFieldSlotWithCheck error ");
return false;
}
LOG_WINDOW("%d,%d is NULL", row, col);
return true;
}
// free the last row
static void freeLastRow(JNIEnv * env, jobject object) {
CursorWindow * window = GET_WINDOW(env, object);
window->freeLastRow();
}
static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
{"native_init", "(Z)V", (void *)native_init_empty},
{"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
{"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder},
{"native_clear", "()V", (void *)native_clear},
{"close_native", "()V", (void *)native_close},
{"getLong_native", "(II)J", (void *)getLong_native},
{"getBlob_native", "(II)[B", (void *)getBlob_native},
{"isBlob_native", "(II)Z", (void *)isBlob_native},
{"getString_native", "(II)Ljava/lang/String;", (void *)getString_native},
{"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native},
{"getDouble_native", "(II)D", (void *)getDouble_native},
{"isNull_native", "(II)Z", (void *)isNull_native},
{"getNumRows_native", "()I", (void *)getNumRows},
{"setNumColumns_native", "(I)Z", (void *)setNumColumns},
{"allocRow_native", "()Z", (void *)allocRow},
{"putBlob_native", "([BII)Z", (void *)putBlob_native},
{"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native},
{"putLong_native", "(JII)Z", (void *)putLong_native},
{"putDouble_native", "(DII)Z", (void *)putDouble_native},
{"freeLastRow_native", "()V", (void *)freeLastRow},
{"putNull_native", "(II)Z", (void *)putNull_native},
{"isString_native", "(II)Z", (void *)isString_native},
{"isFloat_native", "(II)Z", (void *)isFloat_native},
{"isInteger_native", "(II)Z", (void *)isInteger_native},
};
int register_android_database_CursorWindow(JNIEnv * env)
{
jclass clazz;
clazz = env->FindClass("android/database/CursorWindow");
if (clazz == NULL) {
LOGE("Can't find android/database/CursorWindow");
return -1;
}
gWindowField = env->GetFieldID(clazz, "nWindow", "I");
if (gWindowField == NULL) {
LOGE("Error locating fields");
return -1;
}
clazz = env->FindClass("android/database/CharArrayBuffer");
if (clazz == NULL) {
LOGE("Can't find android/database/CharArrayBuffer");
return -1;
}
gBufferField = env->GetFieldID(clazz, "data", "[C");
if (gBufferField == NULL) {
LOGE("Error locating fields data in CharArrayBuffer");
return -1;
}
gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I");
if (gSizeCopiedField == NULL) {
LOGE("Error locating fields sizeCopied in CharArrayBuffer");
return -1;
}
return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
sMethods, NELEM(sMethods));
}
} // namespace android