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