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
39namespace android {
40
41static struct {
42    jfieldID data;
43    jfieldID sizeCopied;
44} gCharArrayBufferClassInfo;
45
46static jstring gEmptyString;
47
48static 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
56static 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
62static 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
80static 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
95static 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
103static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
104    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
105    return env->NewStringUTF(window->name().string());
106}
107
108static 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
121static 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
130static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
131    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
132    return window->getNumRows();
133}
134
135static 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
142static 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
148static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
149    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
150    window->freeLastRow();
151}
152
153static 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
168static 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
203static 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
247static 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
270static 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
288static 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
295static 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
334static 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
365static 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
396static 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
414static 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
436static 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
450static 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
464static 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
478static 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
533int 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