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