blob: 9481b8e418d60f2fcad2ece740dc1a9e2d306258 [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.
*/
// Derived from
// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/base/core/jni/android_database_CursorWindow.cpp
#undef LOG_TAG
#define LOG_TAG "CursorWindow"
#define LOG_NDEBUG 0
#include <dirent.h>
#include <inttypes.h>
#include <jni.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Unicode.h>
#undef LOG_NDEBUG
#define LOG_NDEBUG 1
#include <androidfw/CursorWindow.h>
// #include "android_os_Parcel.h"
// #include "android_util_Binder.h"
#include <nativehelper/scoped_local_ref.h>
#include "robo_android_database_SQLiteCommon.h"
namespace android {
// static struct {
// jfieldID data;
// jfieldID sizeCopied;
// } gCharArrayBufferClassInfo;
static jfieldID getCharArrayBufferDataFieldId(JNIEnv* env, jobject obj) {
jclass clsObj = env->GetObjectClass(obj);
if (clsObj == nullptr) {
printf("cls obj is null");
}
return env->GetFieldID(clsObj, "data", "[C");
}
static jfieldID getCharArrayBufferSizeCopiedFieldId(JNIEnv* env, jobject obj) {
jclass clsObj = env->GetObjectClass(obj);
if (clsObj == nullptr) {
printf("cls obj is null");
}
return env->GetFieldID(clsObj, "sizeCopied", "I");
}
static jstring gEmptyString;
static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
String8 msg;
msg.appendFormat(
"Couldn't read row %d, col %d from CursorWindow. "
"Make sure the Cursor is initialized correctly before accessing data "
"from it.",
row, column);
jniThrowException(env, "java/lang/IllegalStateException", msg.string());
}
static void throwUnknownTypeException(JNIEnv* env, jint type) {
String8 msg;
msg.appendFormat("UNKNOWN type %d", type);
jniThrowException(env, "java/lang/IllegalStateException", msg.string());
}
// static int getFdCount() {
// char fdpath[PATH_MAX];
// int count = 0;
// snprintf(fdpath, PATH_MAX, "/proc/%d/fd", getpid());
// DIR* dir = opendir(fdpath);
// if (dir != NULL) {
// struct dirent* dirent;
// while ((dirent = readdir(dir))) {
// count++;
// }
// count -= 2; // discount "." and ".."
// closedir(dir);
// }
// return count;
// }
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj,
jint cursorWindowSize) {
String8 name;
const char* nameStr = env->GetStringUTFChars(nameObj, nullptr);
name.setTo(nameStr);
env->ReleaseStringUTFChars(nameObj, nameStr);
CursorWindow* window;
status_t status = CursorWindow::create(name, cursorWindowSize, &window);
if (status || !window) {
jniThrowExceptionFmt(
env, "android/database/CursorWindowAllocationException",
"Could not allocate CursorWindow '%s' of size %d due to error %d.",
name.string(), cursorWindowSize, status);
return 0;
}
LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
return reinterpret_cast<jlong>(window);
}
// static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject
// parcelObj) {
// Parcel* parcel = parcelForJavaObject(env, parcelObj);
//
// CursorWindow* window;
// status_t status = CursorWindow::createFromParcel(parcel, &window);
// if (status || !window) {
// jniThrowExceptionFmt(env,
// "android/database/CursorWindowAllocationException",
// "Could not create CursorWindow from Parcel due to
// error %d, process fd count=%d", status,
// getFdCount());
// return 0;
// }
//
// LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d,
// window = %p",
// window->getNumRows(), window->getNumColumns(), window);
// return reinterpret_cast<jlong>(window);
// }
static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
if (window) {
LOG_WINDOW("Closing window %p", window);
delete window;
}
}
static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
return env->NewStringUTF(window->name().string());
}
//
// static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
// jobject parcelObj) {
// CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
// Parcel* parcel = parcelForJavaObject(env, parcelObj);
//
// status_t status = window->writeToParcel(parcel);
// if (status) {
// String8 msg;
// msg.appendFormat("Could not write CursorWindow to Parcel due to error
// %d.", status); jniThrowRuntimeException(env, msg.string());
// }
//}
static void nativeClear(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Clearing window %p", window);
status_t status = window->clear();
if (status) {
LOG_WINDOW("Could not clear window. error=%d", status);
}
}
static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
return window->getNumRows();
}
static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
jint columnNum) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->setNumColumns(columnNum);
return status == OK;
}
static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->allocRow();
return status == OK;
}
static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
window->freeLastRow();
}
static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr, jint row,
jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column,
window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
// FIXME: This is really broken but we have CTS tests that depend
// on this legacy behavior.
// throwExceptionWithRowCol(env, row, column);
return CursorWindow::FIELD_TYPE_NULL;
}
return window->getFieldSlotType(fieldSlot);
}
static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return nullptr;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_BLOB ||
type == CursorWindow::FIELD_TYPE_STRING) {
size_t size;
const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
if (!value) {
throw_sqlite3_exception(env, "Native could not read blob slot");
return nullptr;
}
jbyteArray byteArray = env->NewByteArray(size);
if (!byteArray) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Native could not create new byte[]");
return nullptr;
}
env->SetByteArrayRegion(byteArray, 0, size,
static_cast<const jbyte*>(value));
return byteArray;
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
// do nothing
} else {
throwUnknownTypeException(env, type);
}
return nullptr;
}
static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return nullptr;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value =
window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
if (!value) {
throw_sqlite3_exception(env, "Native could not read string slot");
return nullptr;
}
if (sizeIncludingNull <= 1) {
return gEmptyString;
}
// Convert to UTF-16 here instead of calling NewStringUTF. NewStringUTF
// doesn't like UTF-8 strings with high codepoints. It actually expects
// Modified UTF-8 with encoded surrogate pairs.
String16 utf16(value, sizeIncludingNull - 1);
return env->NewString(reinterpret_cast<const jchar*>(utf16.string()),
utf16.size());
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
int64_t value = window->getFieldSlotValueLong(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%" PRId64, value);
return env->NewStringUTF(buf);
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
double value = window->getFieldSlotValueDouble(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%g", value);
return env->NewStringUTF(buf);
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return nullptr;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
return nullptr;
} else {
throwUnknownTypeException(env, type);
return nullptr;
}
}
static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj,
size_t size) {
jcharArray dataObj = jcharArray(env->GetObjectField(
bufferObj, getCharArrayBufferDataFieldId(env, bufferObj)));
if (dataObj && size) {
jsize capacity = env->GetArrayLength(dataObj);
if (size_t(capacity) < size) {
env->DeleteLocalRef(dataObj);
dataObj = nullptr;
}
}
if (!dataObj) {
jsize capacity = size;
if (capacity < 64) {
capacity = 64;
}
dataObj = env->NewCharArray(capacity); // might throw OOM
if (dataObj) {
env->SetObjectField(
bufferObj, getCharArrayBufferDataFieldId(env, bufferObj), dataObj);
}
}
return dataObj;
}
static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
const char* str, size_t len) {
ssize_t size =
utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
if (size < 0) {
size = 0; // invalid UTF8 string
}
jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
if (dataObj) {
if (size) {
jchar* data =
static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, nullptr));
utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str),
len, reinterpret_cast<char16_t*>(data),
static_cast<size_t>(size));
env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
}
env->SetIntField(bufferObj,
getCharArrayBufferSizeCopiedFieldId(env, bufferObj), size);
}
}
static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
if (dataObj) {
env->SetIntField(bufferObj,
getCharArrayBufferSizeCopiedFieldId(env, bufferObj), 0);
}
}
static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column, jobject bufferObj) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value =
window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
if (sizeIncludingNull > 1) {
fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
} else {
clearCharArrayBuffer(env, bufferObj);
}
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
int64_t value = window->getFieldSlotValueLong(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%" PRId64, value);
fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
double value = window->getFieldSlotValueDouble(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%g", value);
fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
clearCharArrayBuffer(env, bufferObj);
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
} else {
throwUnknownTypeException(env, type);
}
}
static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr, jint row,
jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return 0;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_INTEGER) {
return window->getFieldSlotValueLong(fieldSlot);
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value =
window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
return sizeIncludingNull > 1 ? strtoll(value, nullptr, 0) : 0L;
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
return jlong(window->getFieldSlotValueDouble(fieldSlot));
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return 0;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to long");
return 0;
} else {
throwUnknownTypeException(env, type);
return 0;
}
}
static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return 0.0;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_FLOAT) {
return window->getFieldSlotValueDouble(fieldSlot);
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value =
window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
return sizeIncludingNull > 1 ? strtod(value, nullptr) : 0.0;
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
return jdouble(window->getFieldSlotValueLong(fieldSlot));
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return 0.0;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to double");
return 0.0;
} else {
throwUnknownTypeException(env, type);
return 0.0;
}
}
static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jbyteArray valueObj, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
jsize len = env->GetArrayLength(valueObj);
void* value = env->GetPrimitiveArrayCritical(valueObj, nullptr);
status_t status = window->putBlob(row, column, value, len);
env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
if (status) {
LOG_WINDOW("Failed to put blob. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
return true;
}
static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
jstring valueObj, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
const char* valueStr = env->GetStringUTFChars(valueObj, nullptr);
if (!valueStr) {
LOG_WINDOW("value can't be transferred to UTFChars");
return false;
}
status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
env->ReleaseStringUTFChars(valueObj, valueStr);
if (status) {
LOG_WINDOW("Failed to put string. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is TEXT with %zu bytes", row, column, sizeIncludingNull);
return true;
}
static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
jlong value, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putLong(row, column, value);
if (status) {
LOG_WINDOW("Failed to put long. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is INTEGER %" PRId64, row, column, value);
return true;
}
static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
jdouble value, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putDouble(row, column, value);
if (status) {
LOG_WINDOW("Failed to put double. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
return true;
}
static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putNull(row, column);
if (status) {
LOG_WINDOW("Failed to put null. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is NULL", row, column);
return true;
}
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{(char*)"nativeCreate", (char*)"(Ljava/lang/String;I)J",
reinterpret_cast<void*>(nativeCreate)},
// { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
// (void*)nativeCreateFromParcel },
{(char*)"nativeDispose", (char*)"(J)V",
reinterpret_cast<void*>(nativeDispose)},
// { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
// (void*)nativeWriteToParcel },
{const_cast<char*>("nativeGetName"),
const_cast<char*>("(J)Ljava/lang/String;"),
reinterpret_cast<void*>(nativeGetName)},
{const_cast<char*>("nativeGetBlob"), const_cast<char*>("(JII)[B"),
reinterpret_cast<void*>(nativeGetBlob)},
{const_cast<char*>("nativeGetString"),
const_cast<char*>("(JII)Ljava/lang/String;"),
reinterpret_cast<void*>(nativeGetString)},
{const_cast<char*>("nativeCopyStringToBuffer"),
const_cast<char*>("(JIILandroid/database/CharArrayBuffer;)V"),
reinterpret_cast<void*>(nativeCopyStringToBuffer)},
{const_cast<char*>("nativePutBlob"), const_cast<char*>("(J[BII)Z"),
reinterpret_cast<void*>(nativePutBlob)},
{const_cast<char*>("nativePutString"),
const_cast<char*>("(JLjava/lang/String;II)Z"),
reinterpret_cast<void*>(nativePutString)},
// ------- @FastNative below here ----------------------
{const_cast<char*>("nativeClear"), const_cast<char*>("(J)V"),
reinterpret_cast<void*>(nativeClear)},
{const_cast<char*>("nativeGetNumRows"), const_cast<char*>("(J)I"),
reinterpret_cast<void*>(nativeGetNumRows)},
{const_cast<char*>("nativeSetNumColumns"), const_cast<char*>("(JI)Z"),
reinterpret_cast<void*>(nativeSetNumColumns)},
{const_cast<char*>("nativeAllocRow"), const_cast<char*>("(J)Z"),
reinterpret_cast<void*>(nativeAllocRow)},
{const_cast<char*>("nativeFreeLastRow"), const_cast<char*>("(J)V"),
reinterpret_cast<void*>(nativeFreeLastRow)},
{const_cast<char*>("nativeGetType"), const_cast<char*>("(JII)I"),
reinterpret_cast<void*>(nativeGetType)},
{const_cast<char*>("nativeGetLong"), const_cast<char*>("(JII)J"),
reinterpret_cast<void*>(nativeGetLong)},
{const_cast<char*>("nativeGetDouble"), const_cast<char*>("(JII)D"),
reinterpret_cast<void*>(nativeGetDouble)},
{const_cast<char*>("nativePutLong"), const_cast<char*>("(JJII)Z"),
reinterpret_cast<void*>(nativePutLong)},
{const_cast<char*>("nativePutDouble"), const_cast<char*>("(JDII)Z"),
reinterpret_cast<void*>(nativePutDouble)},
{const_cast<char*>("nativePutNull"), const_cast<char*>("(JII)Z"),
reinterpret_cast<void*>(nativePutNull)},
};
int register_android_database_CursorWindow(JNIEnv* env) {
gEmptyString = (jstring)env->NewGlobalRef(env->NewStringUTF(""));
static const char* kCursorWindowClass =
"org/robolectric/nativeruntime/CursorWindowNatives";
ScopedLocalRef<jclass> cls(env, env->FindClass(kCursorWindowClass));
if (cls.get() == nullptr) {
ALOGE("jni CursorWindow registration failure, class not found '%s'",
kCursorWindowClass);
return JNI_ERR;
}
const jint count = sizeof(sMethods) / sizeof(sMethods[0]);
int status = env->RegisterNatives(cls.get(), sMethods, count);
if (status < 0) {
ALOGE("jni CursorWindow registration failure, status: %d", status);
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
} // namespace android