react-native-update 10.43.3 → 10.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/jni/Android.mk +0 -1
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +24 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +33 -9
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +64 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +8 -4
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +8 -8
- package/cpp/patch_core/archive_patch_core.cpp +16 -0
- package/cpp/patch_core/archive_patch_core.h +5 -0
- package/cpp/patch_core/jni_util.h +56 -0
- package/cpp/patch_core/patch_core.cpp +16 -1
- package/cpp/patch_core/patch_core.h +6 -0
- package/cpp/patch_core/patch_core_android.cpp +4 -37
- package/cpp/patch_core/state_ops.h +24 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +109 -2
- package/cpp/patch_core/update_core_android.cpp +43 -69
- package/harmony/pushy/src/main/cpp/pushy.cpp +163 -128
- package/harmony/pushy/src/main/ets/DownloadTask.ts +72 -19
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -6
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +10 -10
- package/harmony/pushy/src/main/ets/UpdateContext.ts +26 -9
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +142 -111
- package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
- package/package.json +7 -3
- package/src/client.ts +161 -101
- package/src/context.ts +21 -7
- package/src/core.ts +5 -1
- package/src/index.ts +7 -1
- package/src/provider.tsx +91 -56
- package/src/utils.ts +58 -18
- package/android/jni/DownloadTask.c +0 -56
- package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
- package/harmony/pushy/src/main/cpp/pushy.c +0 -117
- package/harmony/pushy/src/main/cpp/pushy.h +0 -8
- package/src/__tests__/setup.ts +0 -44
|
@@ -175,6 +175,81 @@ void TestApplyPatchFromFileSourceMergesAndCopies() {
|
|
|
175
175
|
Expect(!Exists(JoinPath(target, "assets/delete.txt")), "deleted asset should not be copied");
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
void TestApplyPatchMergeHardLinksUnchangedFiles() {
|
|
179
|
+
TempDir temp;
|
|
180
|
+
const std::string source = JoinPath(temp.path, "origin");
|
|
181
|
+
const std::string target = JoinPath(temp.path, "target");
|
|
182
|
+
const std::string patch = JoinPath(temp.path, "bundle.patch");
|
|
183
|
+
|
|
184
|
+
WriteFile(JoinPath(source, "index.bundlejs"), "old bundle");
|
|
185
|
+
WriteFile(JoinPath(source, "assets/keep.txt"), "keep");
|
|
186
|
+
WriteFile(patch, "unused patch");
|
|
187
|
+
|
|
188
|
+
FakeBundlePatcher patcher("patched bundle");
|
|
189
|
+
FileSourcePatchOptions options;
|
|
190
|
+
options.source_root = source;
|
|
191
|
+
options.target_root = target;
|
|
192
|
+
options.origin_bundle_path = JoinPath(source, "index.bundlejs");
|
|
193
|
+
options.bundle_patch_path = patch;
|
|
194
|
+
options.bundle_output_path = JoinPath(target, "index.bundlejs");
|
|
195
|
+
options.manifest.copies.push_back(CopyOperation{"assets/keep.txt", "assets/copied.txt"});
|
|
196
|
+
|
|
197
|
+
Status status = ApplyPatchFromFileSource(options, patcher);
|
|
198
|
+
Expect(status.ok, status.message);
|
|
199
|
+
|
|
200
|
+
// Unchanged files must share the source inode (hard link) instead of being
|
|
201
|
+
// byte-copied: same filesystem, so the fast path has to kick in.
|
|
202
|
+
struct stat source_stat;
|
|
203
|
+
struct stat merged_stat;
|
|
204
|
+
struct stat copied_stat;
|
|
205
|
+
Expect(stat(JoinPath(source, "assets/keep.txt").c_str(), &source_stat) == 0, "stat source");
|
|
206
|
+
Expect(stat(JoinPath(target, "assets/keep.txt").c_str(), &merged_stat) == 0, "stat merged");
|
|
207
|
+
Expect(stat(JoinPath(target, "assets/copied.txt").c_str(), &copied_stat) == 0, "stat copied");
|
|
208
|
+
Expect(merged_stat.st_ino == source_stat.st_ino, "merged file should hard-link the source");
|
|
209
|
+
Expect(copied_stat.st_ino == source_stat.st_ino, "manifest copy should hard-link the source");
|
|
210
|
+
Expect(source_stat.st_nlink >= 3, "source should have three names");
|
|
211
|
+
ExpectEq(ReadFile(JoinPath(target, "assets/keep.txt")), "keep", "merged content mismatch");
|
|
212
|
+
|
|
213
|
+
// The patched bundle must be a fresh file, never a link into the source dir.
|
|
214
|
+
struct stat bundle_stat;
|
|
215
|
+
struct stat origin_stat;
|
|
216
|
+
Expect(stat(JoinPath(target, "index.bundlejs").c_str(), &bundle_stat) == 0, "stat bundle");
|
|
217
|
+
Expect(stat(JoinPath(source, "index.bundlejs").c_str(), &origin_stat) == 0, "stat origin bundle");
|
|
218
|
+
Expect(bundle_stat.st_ino != origin_stat.st_ino, "patched bundle must not link the origin");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
void TestApplyPatchMergeFallsBackToByteCopy() {
|
|
222
|
+
TempDir temp;
|
|
223
|
+
const std::string source = JoinPath(temp.path, "origin");
|
|
224
|
+
const std::string target = JoinPath(temp.path, "target");
|
|
225
|
+
const std::string patch = JoinPath(temp.path, "bundle.patch");
|
|
226
|
+
|
|
227
|
+
WriteFile(JoinPath(source, "index.bundlejs"), "old bundle");
|
|
228
|
+
WriteFile(JoinPath(source, "assets/keep.txt"), "keep");
|
|
229
|
+
WriteFile(patch, "unused patch");
|
|
230
|
+
|
|
231
|
+
FakeBundlePatcher patcher("patched bundle");
|
|
232
|
+
FileSourcePatchOptions options;
|
|
233
|
+
options.source_root = source;
|
|
234
|
+
options.target_root = target;
|
|
235
|
+
options.origin_bundle_path = JoinPath(source, "index.bundlejs");
|
|
236
|
+
options.bundle_patch_path = patch;
|
|
237
|
+
options.bundle_output_path = JoinPath(target, "index.bundlejs");
|
|
238
|
+
|
|
239
|
+
pushy::patch::internal::g_disable_hard_links = true;
|
|
240
|
+
Status status = ApplyPatchFromFileSource(options, patcher);
|
|
241
|
+
pushy::patch::internal::g_disable_hard_links = false;
|
|
242
|
+
Expect(status.ok, status.message);
|
|
243
|
+
|
|
244
|
+
struct stat source_stat;
|
|
245
|
+
struct stat merged_stat;
|
|
246
|
+
Expect(stat(JoinPath(source, "assets/keep.txt").c_str(), &source_stat) == 0, "stat source");
|
|
247
|
+
Expect(stat(JoinPath(target, "assets/keep.txt").c_str(), &merged_stat) == 0, "stat merged");
|
|
248
|
+
Expect(merged_stat.st_ino != source_stat.st_ino, "fallback should produce an independent copy");
|
|
249
|
+
Expect(merged_stat.st_nlink == 1, "fallback copy should have a single name");
|
|
250
|
+
ExpectEq(ReadFile(JoinPath(target, "assets/keep.txt")), "keep", "fallback content mismatch");
|
|
251
|
+
}
|
|
252
|
+
|
|
178
253
|
void TestApplyPatchFromFileSourceCanLimitMergeSubdir() {
|
|
179
254
|
TempDir temp;
|
|
180
255
|
const std::string source = JoinPath(temp.path, "origin");
|
|
@@ -426,6 +501,28 @@ void TestArchivePatchCoreRejectsMissingEntries() {
|
|
|
426
501
|
"manifest entry should be skipped");
|
|
427
502
|
}
|
|
428
503
|
|
|
504
|
+
void TestTryParseArchivePatchType() {
|
|
505
|
+
pushy::archive_patch::ArchivePatchType type;
|
|
506
|
+
Expect(
|
|
507
|
+
pushy::archive_patch::TryParseArchivePatchType(1, &type) &&
|
|
508
|
+
type == pushy::archive_patch::ArchivePatchType::kFull,
|
|
509
|
+
"1 should parse to kFull");
|
|
510
|
+
Expect(
|
|
511
|
+
pushy::archive_patch::TryParseArchivePatchType(2, &type) &&
|
|
512
|
+
type == pushy::archive_patch::ArchivePatchType::kPatchFromPackage,
|
|
513
|
+
"2 should parse to kPatchFromPackage");
|
|
514
|
+
Expect(
|
|
515
|
+
pushy::archive_patch::TryParseArchivePatchType(3, &type) &&
|
|
516
|
+
type == pushy::archive_patch::ArchivePatchType::kPatchFromPpk,
|
|
517
|
+
"3 should parse to kPatchFromPpk");
|
|
518
|
+
Expect(
|
|
519
|
+
!pushy::archive_patch::TryParseArchivePatchType(0, &type),
|
|
520
|
+
"0 should be rejected");
|
|
521
|
+
Expect(
|
|
522
|
+
!pushy::archive_patch::TryParseArchivePatchType(4, &type),
|
|
523
|
+
"unknown type should be rejected, not silently coerced to kFull");
|
|
524
|
+
}
|
|
525
|
+
|
|
429
526
|
void TestArchivePatchCoreSupportsCustomBundlePatchEntry() {
|
|
430
527
|
PatchManifest manifest;
|
|
431
528
|
manifest.copies.push_back(CopyOperation{"assets/a.png", "assets/a.png"});
|
|
@@ -515,6 +612,8 @@ void TestStateCoreSwitchToSameVersion() {
|
|
|
515
612
|
int main() {
|
|
516
613
|
const std::vector<std::pair<std::string, void (*)()>> tests = {
|
|
517
614
|
{"ApplyPatchFromFileSourceMergesAndCopies", TestApplyPatchFromFileSourceMergesAndCopies},
|
|
615
|
+
{"ApplyPatchMergeHardLinksUnchangedFiles", TestApplyPatchMergeHardLinksUnchangedFiles},
|
|
616
|
+
{"ApplyPatchMergeFallsBackToByteCopy", TestApplyPatchMergeFallsBackToByteCopy},
|
|
518
617
|
{"ApplyPatchFromFileSourceCanLimitMergeSubdir", TestApplyPatchFromFileSourceCanLimitMergeSubdir},
|
|
519
618
|
{"ApplyPatchFromFileSourceRejectsUnsafePaths", TestApplyPatchFromFileSourceRejectsUnsafePaths},
|
|
520
619
|
{"CleanupOldEntriesRemovesOnlyExpiredPaths", TestCleanupOldEntriesRemovesOnlyExpiredPaths},
|
|
@@ -524,6 +623,7 @@ int main() {
|
|
|
524
623
|
{"StateCoreCanClearMarkers", TestStateCoreCanClearMarkers},
|
|
525
624
|
{"ArchivePatchCoreBuildPlanAndCopyGroups", TestArchivePatchCoreBuildPlanAndCopyGroups},
|
|
526
625
|
{"ArchivePatchCoreRejectsMissingEntries", TestArchivePatchCoreRejectsMissingEntries},
|
|
626
|
+
{"TryParseArchivePatchType", TestTryParseArchivePatchType},
|
|
527
627
|
{"ArchivePatchCoreSupportsCustomBundlePatchEntry", TestArchivePatchCoreSupportsCustomBundlePatchEntry},
|
|
528
628
|
{"ArchivePatchCoreHarmonyBundlePatchFromPackage", TestArchivePatchCoreHarmonyBundlePatchFromPackage},
|
|
529
629
|
{"StateCoreRollbackToEmptyVersion", TestStateCoreRollbackToEmptyVersion},
|
|
@@ -531,15 +631,22 @@ int main() {
|
|
|
531
631
|
{"StateCoreSwitchToSameVersion", TestStateCoreSwitchToSameVersion},
|
|
532
632
|
};
|
|
533
633
|
|
|
634
|
+
int failures = 0;
|
|
534
635
|
for (const auto& test : tests) {
|
|
535
636
|
try {
|
|
536
637
|
test.second();
|
|
537
638
|
std::fprintf(stdout, "[PASS] %s\n", test.first.c_str());
|
|
538
639
|
} catch (const std::exception& error) {
|
|
539
640
|
std::fprintf(stderr, "[FAIL] %s: %s\n", test.first.c_str(), error.what());
|
|
540
|
-
|
|
641
|
+
++failures;
|
|
541
642
|
}
|
|
542
643
|
}
|
|
543
644
|
|
|
544
|
-
|
|
645
|
+
std::fprintf(
|
|
646
|
+
stdout,
|
|
647
|
+
"\n%zu tests, %d passed, %d failed\n",
|
|
648
|
+
tests.size(),
|
|
649
|
+
static_cast<int>(tests.size()) - failures,
|
|
650
|
+
failures);
|
|
651
|
+
return failures == 0 ? 0 : 1;
|
|
545
652
|
}
|
|
@@ -4,56 +4,16 @@
|
|
|
4
4
|
#include <vector>
|
|
5
5
|
|
|
6
6
|
#include "archive_patch_core.h"
|
|
7
|
+
#include "jni_util.h"
|
|
7
8
|
#include "state_core.h"
|
|
9
|
+
#include "state_ops.h"
|
|
8
10
|
|
|
9
11
|
namespace {
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
kClearFirstTime = 4,
|
|
16
|
-
kClearRollbackMark = 5,
|
|
17
|
-
kResolveLaunch = 6,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
std::string JStringToString(JNIEnv* env, jstring value) {
|
|
21
|
-
if (value == nullptr) {
|
|
22
|
-
return std::string();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const char* chars = env->GetStringUTFChars(value, nullptr);
|
|
26
|
-
if (chars == nullptr) {
|
|
27
|
-
return std::string();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
std::string result(chars);
|
|
31
|
-
env->ReleaseStringUTFChars(value, chars);
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
std::vector<std::string> JArrayToVector(JNIEnv* env, jobjectArray values) {
|
|
36
|
-
std::vector<std::string> result;
|
|
37
|
-
if (values == nullptr) {
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const jsize size = env->GetArrayLength(values);
|
|
42
|
-
result.reserve(static_cast<size_t>(size));
|
|
43
|
-
for (jsize index = 0; index < size; ++index) {
|
|
44
|
-
auto* item = static_cast<jstring>(env->GetObjectArrayElement(values, index));
|
|
45
|
-
result.push_back(JStringToString(env, item));
|
|
46
|
-
env->DeleteLocalRef(item);
|
|
47
|
-
}
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
void ThrowRuntimeException(JNIEnv* env, const std::string& message) {
|
|
52
|
-
jclass exception = env->FindClass("java/lang/RuntimeException");
|
|
53
|
-
if (exception != nullptr) {
|
|
54
|
-
env->ThrowNew(exception, message.c_str());
|
|
55
|
-
}
|
|
56
|
-
}
|
|
13
|
+
using pushy::jni_util::JArrayToVector;
|
|
14
|
+
using pushy::jni_util::JStringToString;
|
|
15
|
+
using pushy::jni_util::ThrowRuntimeException;
|
|
16
|
+
using pushy::state_ops::StateOperation;
|
|
57
17
|
|
|
58
18
|
void SetStringField(
|
|
59
19
|
JNIEnv* env,
|
|
@@ -64,6 +24,9 @@ void SetStringField(
|
|
|
64
24
|
jfieldID field =
|
|
65
25
|
env->GetFieldID(target_class, field_name, "Ljava/lang/String;");
|
|
66
26
|
if (field == nullptr) {
|
|
27
|
+
// Clear the pending NoSuchFieldError so subsequent JNI calls are not
|
|
28
|
+
// executed with an exception pending (undefined behavior / CheckJNI abort).
|
|
29
|
+
env->ExceptionClear();
|
|
67
30
|
return;
|
|
68
31
|
}
|
|
69
32
|
if (value.empty()) {
|
|
@@ -83,9 +46,11 @@ void SetBooleanField(
|
|
|
83
46
|
const char* field_name,
|
|
84
47
|
bool value) {
|
|
85
48
|
jfieldID field = env->GetFieldID(target_class, field_name, "Z");
|
|
86
|
-
if (field
|
|
87
|
-
env->
|
|
49
|
+
if (field == nullptr) {
|
|
50
|
+
env->ExceptionClear();
|
|
51
|
+
return;
|
|
88
52
|
}
|
|
53
|
+
env->SetBooleanField(target, field, value ? JNI_TRUE : JNI_FALSE);
|
|
89
54
|
}
|
|
90
55
|
|
|
91
56
|
std::string GetStringField(
|
|
@@ -96,6 +61,7 @@ std::string GetStringField(
|
|
|
96
61
|
jfieldID field =
|
|
97
62
|
env->GetFieldID(target_class, field_name, "Ljava/lang/String;");
|
|
98
63
|
if (field == nullptr) {
|
|
64
|
+
env->ExceptionClear();
|
|
99
65
|
return std::string();
|
|
100
66
|
}
|
|
101
67
|
auto* value = static_cast<jstring>(env->GetObjectField(target, field));
|
|
@@ -112,7 +78,11 @@ bool GetBooleanField(
|
|
|
112
78
|
jclass target_class,
|
|
113
79
|
const char* field_name) {
|
|
114
80
|
jfieldID field = env->GetFieldID(target_class, field_name, "Z");
|
|
115
|
-
|
|
81
|
+
if (field == nullptr) {
|
|
82
|
+
env->ExceptionClear();
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return env->GetBooleanField(target, field) == JNI_TRUE;
|
|
116
86
|
}
|
|
117
87
|
|
|
118
88
|
pushy::state::State ReadState(
|
|
@@ -241,13 +211,9 @@ jobject NewArchivePatchPlanResult(
|
|
|
241
211
|
|
|
242
212
|
jobject NewCopyGroupResult(
|
|
243
213
|
JNIEnv* env,
|
|
214
|
+
jclass result_class,
|
|
215
|
+
jclass string_class,
|
|
244
216
|
const pushy::archive_patch::CopyGroup& group) {
|
|
245
|
-
jclass result_class =
|
|
246
|
-
env->FindClass("cn/reactnative/modules/update/CopyGroupResult");
|
|
247
|
-
if (result_class == nullptr) {
|
|
248
|
-
return nullptr;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
217
|
jmethodID constructor = env->GetMethodID(result_class, "<init>", "()V");
|
|
252
218
|
if (constructor == nullptr) {
|
|
253
219
|
return nullptr;
|
|
@@ -262,7 +228,6 @@ jobject NewCopyGroupResult(
|
|
|
262
228
|
jfieldID to_paths_field =
|
|
263
229
|
env->GetFieldID(result_class, "toPaths", "[Ljava/lang/String;");
|
|
264
230
|
if (to_paths_field != nullptr) {
|
|
265
|
-
jclass string_class = env->FindClass("java/lang/String");
|
|
266
231
|
jobjectArray to_paths = env->NewObjectArray(
|
|
267
232
|
static_cast<jsize>(group.to_paths.size()), string_class, nullptr);
|
|
268
233
|
if (to_paths != nullptr) {
|
|
@@ -275,22 +240,20 @@ jobject NewCopyGroupResult(
|
|
|
275
240
|
env->SetObjectField(result, to_paths_field, to_paths);
|
|
276
241
|
env->DeleteLocalRef(to_paths);
|
|
277
242
|
}
|
|
243
|
+
} else {
|
|
244
|
+
env->ExceptionClear();
|
|
278
245
|
}
|
|
279
246
|
|
|
280
247
|
return result;
|
|
281
248
|
}
|
|
282
249
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return pushy::archive_patch::ArchivePatchType::kPatchFromPpk;
|
|
291
|
-
default:
|
|
292
|
-
return pushy::archive_patch::ArchivePatchType::kFull;
|
|
293
|
-
}
|
|
250
|
+
bool ToArchivePatchType(
|
|
251
|
+
jint patch_type,
|
|
252
|
+
pushy::archive_patch::ArchivePatchType* out) {
|
|
253
|
+
// Delegate to the shared parser so Android and HarmonyOS agree on which
|
|
254
|
+
// integer values are valid and neither silently falls back to kFull.
|
|
255
|
+
return pushy::archive_patch::TryParseArchivePatchType(
|
|
256
|
+
static_cast<int>(patch_type), out);
|
|
294
257
|
}
|
|
295
258
|
|
|
296
259
|
pushy::patch::PatchManifest BuildManifest(
|
|
@@ -411,9 +374,14 @@ Java_cn_reactnative_modules_update_DownloadTask_buildArchivePatchPlan(
|
|
|
411
374
|
|
|
412
375
|
pushy::patch::PatchManifest manifest =
|
|
413
376
|
BuildManifest(from_values, to_values, JArrayToVector(env, deletes));
|
|
377
|
+
pushy::archive_patch::ArchivePatchType archive_type;
|
|
378
|
+
if (!ToArchivePatchType(patch_type, &archive_type)) {
|
|
379
|
+
ThrowRuntimeException(env, "Unknown archive patch type");
|
|
380
|
+
return nullptr;
|
|
381
|
+
}
|
|
414
382
|
pushy::archive_patch::ArchivePatchPlan plan;
|
|
415
383
|
pushy::patch::Status status = pushy::archive_patch::BuildArchivePatchPlan(
|
|
416
|
-
|
|
384
|
+
archive_type,
|
|
417
385
|
manifest,
|
|
418
386
|
JArrayToVector(env, entry_names),
|
|
419
387
|
&plan);
|
|
@@ -454,6 +422,11 @@ Java_cn_reactnative_modules_update_DownloadTask_buildCopyGroups(
|
|
|
454
422
|
return nullptr;
|
|
455
423
|
}
|
|
456
424
|
|
|
425
|
+
jclass string_class = env->FindClass("java/lang/String");
|
|
426
|
+
if (string_class == nullptr) {
|
|
427
|
+
return nullptr;
|
|
428
|
+
}
|
|
429
|
+
|
|
457
430
|
jobjectArray result = env->NewObjectArray(
|
|
458
431
|
static_cast<jsize>(groups.size()), result_class, nullptr);
|
|
459
432
|
if (result == nullptr) {
|
|
@@ -461,9 +434,10 @@ Java_cn_reactnative_modules_update_DownloadTask_buildCopyGroups(
|
|
|
461
434
|
}
|
|
462
435
|
|
|
463
436
|
for (jsize index = 0; index < static_cast<jsize>(groups.size()); ++index) {
|
|
464
|
-
jobject group = NewCopyGroupResult(env, groups[index]);
|
|
437
|
+
jobject group = NewCopyGroupResult(env, result_class, string_class, groups[index]);
|
|
465
438
|
env->SetObjectArrayElement(result, index, group);
|
|
466
439
|
env->DeleteLocalRef(group);
|
|
467
440
|
}
|
|
441
|
+
env->DeleteLocalRef(string_class);
|
|
468
442
|
return result;
|
|
469
443
|
}
|