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