1 /*
2 * Copyright (c) 2021, 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 #include "JniUtils.h"
18
19 #include "nativehelper/scoped_local_ref.h"
20
21 namespace com {
22 namespace android {
23 namespace car {
24 namespace scriptexecutor {
25
pushBundleToLuaTable(JNIEnv * env,lua_State * lua,jobject bundle)26 void pushBundleToLuaTable(JNIEnv* env, lua_State* lua, jobject bundle) {
27 lua_newtable(lua);
28 // null bundle object is allowed. We will treat it as an empty table.
29 if (bundle == nullptr) {
30 return;
31 }
32
33 // TODO(b/188832769): Consider caching some of these JNI references for
34 // performance reasons.
35 ScopedLocalRef<jclass> persistableBundleClass(env,
36 env->FindClass("android/os/PersistableBundle"));
37 jmethodID getKeySetMethod =
38 env->GetMethodID(persistableBundleClass.get(), "keySet", "()Ljava/util/Set;");
39 ScopedLocalRef<jobject> keys(env, env->CallObjectMethod(bundle, getKeySetMethod));
40 ScopedLocalRef<jclass> setClass(env, env->FindClass("java/util/Set"));
41 jmethodID iteratorMethod =
42 env->GetMethodID(setClass.get(), "iterator", "()Ljava/util/Iterator;");
43 ScopedLocalRef<jobject> keySetIteratorObject(env,
44 env->CallObjectMethod(keys.get(), iteratorMethod));
45
46 ScopedLocalRef<jclass> iteratorClass(env, env->FindClass("java/util/Iterator"));
47 jmethodID hasNextMethod = env->GetMethodID(iteratorClass.get(), "hasNext", "()Z");
48 jmethodID nextMethod = env->GetMethodID(iteratorClass.get(), "next", "()Ljava/lang/Object;");
49
50 ScopedLocalRef<jclass> booleanClass(env, env->FindClass("java/lang/Boolean"));
51 ScopedLocalRef<jclass> integerClass(env, env->FindClass("java/lang/Integer"));
52 ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long"));
53 ScopedLocalRef<jclass> numberClass(env, env->FindClass("java/lang/Number"));
54 ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String"));
55 ScopedLocalRef<jclass> intArrayClass(env, env->FindClass("[I"));
56 ScopedLocalRef<jclass> longArrayClass(env, env->FindClass("[J"));
57 ScopedLocalRef<jclass> stringArrayClass(env, env->FindClass("[Ljava/lang/String;"));
58 // TODO(b/188816922): Handle more types such as float and integer arrays,
59 // and perhaps nested Bundles.
60
61 jmethodID getMethod = env->GetMethodID(persistableBundleClass.get(), "get",
62 "(Ljava/lang/String;)Ljava/lang/Object;");
63
64 // Iterate over key set of the bundle one key at a time.
65 while (env->CallBooleanMethod(keySetIteratorObject.get(), hasNextMethod)) {
66 // Read the value object that corresponds to this key.
67 ScopedLocalRef<jstring> key(env,
68 (jstring)env->CallObjectMethod(keySetIteratorObject.get(),
69 nextMethod));
70 ScopedLocalRef<jobject> value(env, env->CallObjectMethod(bundle, getMethod, key.get()));
71
72 // Get the value of the type, extract it accordingly from the bundle and
73 // push the extracted value and the key to the Lua table.
74 if (env->IsInstanceOf(value.get(), booleanClass.get())) {
75 jmethodID boolMethod = env->GetMethodID(booleanClass.get(), "booleanValue", "()Z");
76 bool boolValue = static_cast<bool>(env->CallBooleanMethod(value.get(), boolMethod));
77 lua_pushboolean(lua, boolValue);
78 } else if (env->IsInstanceOf(value.get(), integerClass.get())) {
79 jmethodID intMethod = env->GetMethodID(integerClass.get(), "intValue", "()I");
80 lua_pushinteger(lua, env->CallIntMethod(value.get(), intMethod));
81 } else if (env->IsInstanceOf(value.get(), longClass.get())) {
82 jmethodID longMethod = env->GetMethodID(longClass.get(), "longValue", "()J");
83 lua_pushinteger(lua, env->CallLongMethod(value.get(), longMethod));
84 } else if (env->IsInstanceOf(value.get(), numberClass.get())) {
85 // Condense other numeric types using one class. Because lua supports only
86 // integer or double, and we handled integer in previous if clause.
87 jmethodID numberMethod = env->GetMethodID(numberClass.get(), "doubleValue", "()D");
88 /* Pushes a double onto the stack */
89 lua_pushnumber(lua, env->CallDoubleMethod(value.get(), numberMethod));
90 } else if (env->IsInstanceOf(value.get(), stringClass.get())) {
91 // Produces a string in Modified UTF-8 encoding. Any null character
92 // inside the original string is converted into two-byte encoding.
93 // This way we can directly use the output of GetStringUTFChars in C API that
94 // expects a null-terminated string.
95 const char* rawStringValue =
96 env->GetStringUTFChars(static_cast<jstring>(value.get()), nullptr);
97 lua_pushstring(lua, rawStringValue);
98 env->ReleaseStringUTFChars(static_cast<jstring>(value.get()), rawStringValue);
99 } else if (env->IsInstanceOf(value.get(), intArrayClass.get())) {
100 jintArray intArray = static_cast<jintArray>(value.get());
101 const auto kLength = env->GetArrayLength(intArray);
102 // Arrays are represented as a table of sequential elements in Lua.
103 // We are creating a nested table to represent this array. We specify number of elements
104 // in the Java array to preallocate memory accordingly.
105 lua_createtable(lua, kLength, 0);
106 jint* rawIntArray = env->GetIntArrayElements(intArray, nullptr);
107 // Fills in the table at stack idx -2 with key value pairs, where key is a
108 // Lua index and value is an integer from the byte array at that index
109 for (int i = 0; i < kLength; i++) {
110 // Stack at index -1 is rawIntArray[i] after this push.
111 lua_pushinteger(lua, rawIntArray[i]);
112 lua_rawseti(lua, /* idx= */ -2,
113 i + 1); // lua index starts from 1
114 }
115 // JNI_ABORT is used because we do not need to copy back elements.
116 env->ReleaseIntArrayElements(intArray, rawIntArray, JNI_ABORT);
117 } else if (env->IsInstanceOf(value.get(), longArrayClass.get())) {
118 jlongArray longArray = static_cast<jlongArray>(value.get());
119 const auto kLength = env->GetArrayLength(longArray);
120 // Arrays are represented as a table of sequential elements in Lua.
121 // We are creating a nested table to represent this array. We specify number of elements
122 // in the Java array to preallocate memory accordingly.
123 lua_createtable(lua, kLength, 0);
124 jlong* rawLongArray = env->GetLongArrayElements(longArray, nullptr);
125 // Fills in the table at stack idx -2 with key value pairs, where key is a
126 // Lua index and value is an integer from the byte array at that index
127 for (int i = 0; i < kLength; i++) {
128 lua_pushinteger(lua, rawLongArray[i]);
129 lua_rawseti(lua, /* idx= */ -2,
130 i + 1); // lua index starts from 1
131 }
132 // JNI_ABORT is used because we do not need to copy back elements.
133 env->ReleaseLongArrayElements(longArray, rawLongArray, JNI_ABORT);
134 } else if (env->IsInstanceOf(value.get(), stringArrayClass.get())) {
135 jobjectArray stringArray = static_cast<jobjectArray>(value.get());
136 const auto kLength = env->GetArrayLength(stringArray);
137 // Arrays are represented as a table of sequential elements in Lua.
138 // We are creating a nested table to represent this array. We specify number of elements
139 // in the Java array to preallocate memory accordingly.
140 lua_createtable(lua, kLength, 0);
141 // Fills in the table at stack idx -2 with key value pairs, where key is a Lua index and
142 // value is an string value extracted from the object array at that index
143 for (int i = 0; i < kLength; i++) {
144 ScopedLocalRef<jobject> localStringRef(env,
145 env->GetObjectArrayElement(stringArray, i));
146 jstring element = static_cast<jstring>(localStringRef.get());
147 const char* rawStringValue = env->GetStringUTFChars(element, nullptr);
148 lua_pushstring(lua, rawStringValue);
149 env->ReleaseStringUTFChars(element, rawStringValue);
150 // lua index starts from 1
151 lua_rawseti(lua, /* idx= */ -2, i + 1);
152 }
153 } else {
154 // Other types are not implemented yet, skipping.
155 continue;
156 }
157
158 const char* rawKey = env->GetStringUTFChars(key.get(), nullptr);
159 // table[rawKey] = value, where value is on top of the stack,
160 // and the table is the next element in the stack.
161 lua_setfield(lua, /* idx= */ -2, rawKey);
162 env->ReleaseStringUTFChars(key.get(), rawKey);
163 }
164 }
165
166 } // namespace scriptexecutor
167 } // namespace car
168 } // namespace android
169 } // namespace com
170