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