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