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