Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #undef LOG_TAG
     18 #define LOG_TAG "CursorWindow"
     19 
     20 #include <jni.h>
     21 #include <JNIHelp.h>
     22 #include <android_runtime/AndroidRuntime.h>
     23 
     24 #include <utils/Log.h>
     25 #include <utils/String8.h>
     26 #include <utils/String16.h>
     27 #include <utils/Unicode.h>
     28 
     29 #include <stdio.h>
     30 #include <string.h>
     31 #include <unistd.h>
     32 
     33 #include <androidfw/CursorWindow.h>
     34 #include "android_os_Parcel.h"
     35 #include "android_util_Binder.h"
     36 #include "android_database_SQLiteCommon.h"
     37 
     38 namespace android {
     39 
     40 static struct {
     41     jfieldID data;
     42     jfieldID sizeCopied;
     43 } gCharArrayBufferClassInfo;
     44 
     45 static jstring gEmptyString;
     46 
     47 static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
     48     String8 msg;
     49     msg.appendFormat("Couldn't read row %d, col %d from CursorWindow.  "
     50             "Make sure the Cursor is initialized correctly before accessing data from it.",
     51             row, column);
     52     jniThrowException(env, "java/lang/IllegalStateException", msg.string());
     53 }
     54 
     55 static void throwUnknownTypeException(JNIEnv * env, jint type) {
     56     String8 msg;
     57     msg.appendFormat("UNKNOWN type %d", type);
     58     jniThrowException(env, "java/lang/IllegalStateException", msg.string());
     59 }
     60 
     61 static jint nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
     62     String8 name;
     63     const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
     64     name.setTo(nameStr);
     65     env->ReleaseStringUTFChars(nameObj, nameStr);
     66 
     67     CursorWindow* window;
     68     status_t status = CursorWindow::create(name, cursorWindowSize, &window);
     69     if (status || !window) {
     70         ALOGE("Could not allocate CursorWindow '%s' of size %d due to error %d.",
     71                 name.string(), cursorWindowSize, status);
     72         return 0;
     73     }
     74 
     75     LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
     76     return reinterpret_cast<jint>(window);
     77 }
     78 
     79 static jint nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
     80     Parcel* parcel = parcelForJavaObject(env, parcelObj);
     81 
     82     CursorWindow* window;
     83     status_t status = CursorWindow::createFromParcel(parcel, &window);
     84     if (status || !window) {
     85         ALOGE("Could not create CursorWindow from Parcel due to error %d.", status);
     86         return 0;
     87     }
     88 
     89     LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
     90             window->getNumRows(), window->getNumColumns(), window);
     91     return reinterpret_cast<jint>(window);
     92 }
     93 
     94 static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) {
     95     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
     96     if (window) {
     97         LOG_WINDOW("Closing window %p", window);
     98         delete window;
     99     }
    100 }
    101 
    102 static jstring nativeGetName(JNIEnv* env, jclass clazz, jint windowPtr) {
    103     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    104     return env->NewStringUTF(window->name().string());
    105 }
    106 
    107 static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jint windowPtr,
    108         jobject parcelObj) {
    109     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    110     Parcel* parcel = parcelForJavaObject(env, parcelObj);
    111 
    112     status_t status = window->writeToParcel(parcel);
    113     if (status) {
    114         String8 msg;
    115         msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
    116         jniThrowRuntimeException(env, msg.string());
    117     }
    118 }
    119 
    120 static void nativeClear(JNIEnv * env, jclass clazz, jint windowPtr) {
    121     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    122     LOG_WINDOW("Clearing window %p", window);
    123     status_t status = window->clear();
    124     if (status) {
    125         LOG_WINDOW("Could not clear window. error=%d", status);
    126     }
    127 }
    128 
    129 static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jint windowPtr) {
    130     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    131     return window->getNumRows();
    132 }
    133 
    134 static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jint windowPtr,
    135         jint columnNum) {
    136     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    137     status_t status = window->setNumColumns(columnNum);
    138     return status == OK;
    139 }
    140 
    141 static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jint windowPtr) {
    142     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    143     status_t status = window->allocRow();
    144     return status == OK;
    145 }
    146 
    147 static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jint windowPtr) {
    148     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    149     window->freeLastRow();
    150 }
    151 
    152 static jint nativeGetType(JNIEnv* env, jclass clazz, jint windowPtr,
    153         jint row, jint column) {
    154     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    155     LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
    156 
    157     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    158     if (!fieldSlot) {
    159         // FIXME: This is really broken but we have CTS tests that depend
    160         // on this legacy behavior.
    161         //throwExceptionWithRowCol(env, row, column);
    162         return CursorWindow::FIELD_TYPE_NULL;
    163     }
    164     return window->getFieldSlotType(fieldSlot);
    165 }
    166 
    167 static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr,
    168         jint row, jint column) {
    169     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    170     LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
    171 
    172     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    173     if (!fieldSlot) {
    174         throwExceptionWithRowCol(env, row, column);
    175         return NULL;
    176     }
    177 
    178     int32_t type = window->getFieldSlotType(fieldSlot);
    179     if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
    180         size_t size;
    181         const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
    182         jbyteArray byteArray = env->NewByteArray(size);
    183         if (!byteArray) {
    184             env->ExceptionClear();
    185             throw_sqlite3_exception(env, "Native could not create new byte[]");
    186             return NULL;
    187         }
    188         env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(value));
    189         return byteArray;
    190     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    191         throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
    192     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    193         throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
    194     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    195         // do nothing
    196     } else {
    197         throwUnknownTypeException(env, type);
    198     }
    199     return NULL;
    200 }
    201 
    202 static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr,
    203         jint row, jint column) {
    204     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    205     LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
    206 
    207     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    208     if (!fieldSlot) {
    209         throwExceptionWithRowCol(env, row, column);
    210         return NULL;
    211     }
    212 
    213     int32_t type = window->getFieldSlotType(fieldSlot);
    214     if (type == CursorWindow::FIELD_TYPE_STRING) {
    215         size_t sizeIncludingNull;
    216         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    217         if (sizeIncludingNull <= 1) {
    218             return gEmptyString;
    219         }
    220         // Convert to UTF-16 here instead of calling NewStringUTF.  NewStringUTF
    221         // doesn't like UTF-8 strings with high codepoints.  It actually expects
    222         // Modified UTF-8 with encoded surrogate pairs.
    223         String16 utf16(value, sizeIncludingNull - 1);
    224         return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
    225     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    226         int64_t value = window->getFieldSlotValueLong(fieldSlot);
    227         char buf[32];
    228         snprintf(buf, sizeof(buf), "%lld", value);
    229         return env->NewStringUTF(buf);
    230     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    231         double value = window->getFieldSlotValueDouble(fieldSlot);
    232         char buf[32];
    233         snprintf(buf, sizeof(buf), "%g", value);
    234         return env->NewStringUTF(buf);
    235     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    236         return NULL;
    237     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    238         throw_sqlite3_exception(env, "Unable to convert BLOB to string");
    239         return NULL;
    240     } else {
    241         throwUnknownTypeException(env, type);
    242         return NULL;
    243     }
    244 }
    245 
    246 static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
    247     jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
    248             gCharArrayBufferClassInfo.data));
    249     if (dataObj && size) {
    250         jsize capacity = env->GetArrayLength(dataObj);
    251         if (size_t(capacity) < size) {
    252             env->DeleteLocalRef(dataObj);
    253             dataObj = NULL;
    254         }
    255     }
    256     if (!dataObj) {
    257         jsize capacity = size;
    258         if (capacity < 64) {
    259             capacity = 64;
    260         }
    261         dataObj = env->NewCharArray(capacity); // might throw OOM
    262         if (dataObj) {
    263             env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
    264         }
    265     }
    266     return dataObj;
    267 }
    268 
    269 static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
    270         const char* str, size_t len) {
    271     ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
    272     if (size < 0) {
    273         size = 0; // invalid UTF8 string
    274     }
    275     jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
    276     if (dataObj) {
    277         if (size) {
    278             jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
    279             utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
    280                     reinterpret_cast<char16_t*>(data));
    281             env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
    282         }
    283         env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
    284     }
    285 }
    286 
    287 static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
    288     jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
    289     if (dataObj) {
    290         env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
    291     }
    292 }
    293 
    294 static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr,
    295         jint row, jint column, jobject bufferObj) {
    296     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    297     LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
    298 
    299     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    300     if (!fieldSlot) {
    301         throwExceptionWithRowCol(env, row, column);
    302         return;
    303     }
    304 
    305     int32_t type = window->getFieldSlotType(fieldSlot);
    306     if (type == CursorWindow::FIELD_TYPE_STRING) {
    307         size_t sizeIncludingNull;
    308         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    309         if (sizeIncludingNull > 1) {
    310             fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
    311         } else {
    312             clearCharArrayBuffer(env, bufferObj);
    313         }
    314     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    315         int64_t value = window->getFieldSlotValueLong(fieldSlot);
    316         char buf[32];
    317         snprintf(buf, sizeof(buf), "%lld", value);
    318         fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
    319     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    320         double value = window->getFieldSlotValueDouble(fieldSlot);
    321         char buf[32];
    322         snprintf(buf, sizeof(buf), "%g", value);
    323         fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
    324     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    325         clearCharArrayBuffer(env, bufferObj);
    326     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    327         throw_sqlite3_exception(env, "Unable to convert BLOB to string");
    328     } else {
    329         throwUnknownTypeException(env, type);
    330     }
    331 }
    332 
    333 static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr,
    334         jint row, jint column) {
    335     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    336     LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
    337 
    338     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    339     if (!fieldSlot) {
    340         throwExceptionWithRowCol(env, row, column);
    341         return 0;
    342     }
    343 
    344     int32_t type = window->getFieldSlotType(fieldSlot);
    345     if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    346         return window->getFieldSlotValueLong(fieldSlot);
    347     } else if (type == CursorWindow::FIELD_TYPE_STRING) {
    348         size_t sizeIncludingNull;
    349         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    350         return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
    351     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    352         return jlong(window->getFieldSlotValueDouble(fieldSlot));
    353     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    354         return 0;
    355     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    356         throw_sqlite3_exception(env, "Unable to convert BLOB to long");
    357         return 0;
    358     } else {
    359         throwUnknownTypeException(env, type);
    360         return 0;
    361     }
    362 }
    363 
    364 static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr,
    365         jint row, jint column) {
    366     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    367     LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
    368 
    369     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    370     if (!fieldSlot) {
    371         throwExceptionWithRowCol(env, row, column);
    372         return 0.0;
    373     }
    374 
    375     int32_t type = window->getFieldSlotType(fieldSlot);
    376     if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    377         return window->getFieldSlotValueDouble(fieldSlot);
    378     } else if (type == CursorWindow::FIELD_TYPE_STRING) {
    379         size_t sizeIncludingNull;
    380         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    381         return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
    382     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    383         return jdouble(window->getFieldSlotValueLong(fieldSlot));
    384     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    385         return 0.0;
    386     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    387         throw_sqlite3_exception(env, "Unable to convert BLOB to double");
    388         return 0.0;
    389     } else {
    390         throwUnknownTypeException(env, type);
    391         return 0.0;
    392     }
    393 }
    394 
    395 static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr,
    396         jbyteArray valueObj, jint row, jint column) {
    397     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    398     jsize len = env->GetArrayLength(valueObj);
    399 
    400     void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
    401     status_t status = window->putBlob(row, column, value, len);
    402     env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
    403 
    404     if (status) {
    405         LOG_WINDOW("Failed to put blob. error=%d", status);
    406         return false;
    407     }
    408 
    409     LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
    410     return true;
    411 }
    412 
    413 static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr,
    414         jstring valueObj, jint row, jint column) {
    415     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    416 
    417     size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
    418     const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
    419     if (!valueStr) {
    420         LOG_WINDOW("value can't be transferred to UTFChars");
    421         return false;
    422     }
    423     status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
    424     env->ReleaseStringUTFChars(valueObj, valueStr);
    425 
    426     if (status) {
    427         LOG_WINDOW("Failed to put string. error=%d", status);
    428         return false;
    429     }
    430 
    431     LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
    432     return true;
    433 }
    434 
    435 static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr,
    436         jlong value, jint row, jint column) {
    437     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    438     status_t status = window->putLong(row, column, value);
    439 
    440     if (status) {
    441         LOG_WINDOW("Failed to put long. error=%d", status);
    442         return false;
    443     }
    444 
    445     LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
    446     return true;
    447 }
    448 
    449 static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr,
    450         jdouble value, jint row, jint column) {
    451     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    452     status_t status = window->putDouble(row, column, value);
    453 
    454     if (status) {
    455         LOG_WINDOW("Failed to put double. error=%d", status);
    456         return false;
    457     }
    458 
    459     LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
    460     return true;
    461 }
    462 
    463 static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr,
    464         jint row, jint column) {
    465     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    466     status_t status = window->putNull(row, column);
    467 
    468     if (status) {
    469         LOG_WINDOW("Failed to put null. error=%d", status);
    470         return false;
    471     }
    472 
    473     LOG_WINDOW("%d,%d is NULL", row, column);
    474     return true;
    475 }
    476 
    477 static JNINativeMethod sMethods[] =
    478 {
    479     /* name, signature, funcPtr */
    480     { "nativeCreate", "(Ljava/lang/String;I)I",
    481             (void*)nativeCreate },
    482     { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I",
    483             (void*)nativeCreateFromParcel },
    484     { "nativeDispose", "(I)V",
    485             (void*)nativeDispose },
    486     { "nativeWriteToParcel", "(ILandroid/os/Parcel;)V",
    487             (void*)nativeWriteToParcel },
    488     { "nativeGetName", "(I)Ljava/lang/String;",
    489             (void*)nativeGetName },
    490     { "nativeClear", "(I)V",
    491             (void*)nativeClear },
    492     { "nativeGetNumRows", "(I)I",
    493             (void*)nativeGetNumRows },
    494     { "nativeSetNumColumns", "(II)Z",
    495             (void*)nativeSetNumColumns },
    496     { "nativeAllocRow", "(I)Z",
    497             (void*)nativeAllocRow },
    498     { "nativeFreeLastRow", "(I)V",
    499             (void*)nativeFreeLastRow },
    500     { "nativeGetType", "(III)I",
    501             (void*)nativeGetType },
    502     { "nativeGetBlob", "(III)[B",
    503             (void*)nativeGetBlob },
    504     { "nativeGetString", "(III)Ljava/lang/String;",
    505             (void*)nativeGetString },
    506     { "nativeGetLong", "(III)J",
    507             (void*)nativeGetLong },
    508     { "nativeGetDouble", "(III)D",
    509             (void*)nativeGetDouble },
    510     { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V",
    511             (void*)nativeCopyStringToBuffer },
    512     { "nativePutBlob", "(I[BII)Z",
    513             (void*)nativePutBlob },
    514     { "nativePutString", "(ILjava/lang/String;II)Z",
    515             (void*)nativePutString },
    516     { "nativePutLong", "(IJII)Z",
    517             (void*)nativePutLong },
    518     { "nativePutDouble", "(IDII)Z",
    519             (void*)nativePutDouble },
    520     { "nativePutNull", "(III)Z",
    521             (void*)nativePutNull },
    522 };
    523 
    524 #define FIND_CLASS(var, className) \
    525         var = env->FindClass(className); \
    526         LOG_FATAL_IF(! var, "Unable to find class " className);
    527 
    528 #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
    529         var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
    530         LOG_FATAL_IF(! var, "Unable to find field " fieldName);
    531 
    532 int register_android_database_CursorWindow(JNIEnv * env)
    533 {
    534     jclass clazz;
    535     FIND_CLASS(clazz, "android/database/CharArrayBuffer");
    536 
    537     GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz,
    538             "data", "[C");
    539     GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz,
    540             "sizeCopied", "I");
    541 
    542     gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF("")));
    543     LOG_FATAL_IF(!gEmptyString, "Unable to create empty string");
    544 
    545     return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
    546             sMethods, NELEM(sMethods));
    547 }
    548 
    549 } // namespace android
    550