Line data Source code
1 : /**
2 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPString.h"
24 : #include "SPDso.h"
25 :
26 : #if LINUX
27 :
28 : #include <sys/random.h>
29 :
30 : namespace STAPPLER_VERSIONIZED stappler::platform {
31 :
32 : constexpr uint32_t U_COMPARE_CODE_POINT_ORDER = 0x8000;
33 :
34 : struct unistring_iface {
35 : using u8_case_fn = uint8_t * (*) (const uint8_t *s, size_t n, const char *iso639_language, void *nf, uint8_t *resultbuf, size_t *lengthp);
36 : using u16_case_fn = uint16_t* (*) (const uint16_t *s, size_t n, const char *iso639_language, void *nf, uint16_t *resultbuf, size_t *lengthp);
37 :
38 : int32_t (*tolower_fn) (int32_t) = nullptr;
39 : int32_t (*toupper_fn) (int32_t) = nullptr;
40 : int32_t (*totitle_fn) (int32_t) = nullptr;
41 :
42 : const char * (* uc_locale_language) () = nullptr;
43 :
44 : u8_case_fn u8_toupper = nullptr;
45 : u8_case_fn u8_tolower = nullptr;
46 : u8_case_fn u8_totitle = nullptr;
47 :
48 : int (*u8_cmp2) (const uint8_t *s1, size_t n1, const uint8_t *s2, size_t n2) = nullptr;
49 : int (* u8_casecoll) (const uint8_t *s1, size_t n1, const uint8_t *s2, size_t n2, const char *iso639_language, void *nf, int *resultp) = nullptr;
50 :
51 :
52 : u16_case_fn u16_toupper = nullptr;
53 : u16_case_fn u16_tolower = nullptr;
54 : u16_case_fn u16_totitle = nullptr;
55 :
56 : int (*u16_cmp2) (const uint16_t *s1, size_t n1, const uint16_t *s2, size_t n2) = nullptr;
57 : int (* u16_casecoll) (const uint16_t *s1, size_t n1, const uint16_t *s2, size_t n2, const char *iso639_language, void *nf, int *resultp) = nullptr;
58 :
59 25 : void load(Dso &handle) {
60 25 : tolower_fn = handle.sym<decltype(tolower_fn)>("uc_tolower");
61 25 : toupper_fn = handle.sym<decltype(toupper_fn)>("uc_toupper");
62 25 : totitle_fn = handle.sym<decltype(totitle_fn)>("uc_totitle");
63 :
64 25 : uc_locale_language = handle.sym<decltype(uc_locale_language)>("uc_locale_language");
65 :
66 25 : u8_toupper = handle.sym<decltype(u8_toupper)>("u8_toupper");
67 25 : u8_tolower = handle.sym<decltype(u8_tolower)>("u8_tolower");
68 25 : u8_totitle = handle.sym<decltype(u8_totitle)>("u8_totitle");
69 :
70 25 : u8_cmp2 = handle.sym<decltype(u8_cmp2)>("u8_cmp2");
71 25 : u8_casecoll = handle.sym<decltype(u8_casecoll)>("u8_casecoll");
72 :
73 25 : u16_toupper = handle.sym<decltype(u16_toupper)>("u16_toupper");
74 25 : u16_tolower = handle.sym<decltype(u16_tolower)>("u16_tolower");
75 25 : u16_totitle = handle.sym<decltype(u16_totitle)>("u16_totitle");
76 :
77 25 : u16_cmp2 = handle.sym<decltype(u16_cmp2)>("u16_cmp2");
78 25 : u16_casecoll = handle.sym<decltype(u16_casecoll)>("u16_casecoll");
79 25 : }
80 :
81 25 : explicit operator bool() const {
82 25 : return uc_locale_language
83 25 : && tolower_fn && toupper_fn && totitle_fn
84 25 : && u8_toupper && u8_tolower && u8_totitle && u8_cmp2 && u8_casecoll
85 50 : && u16_toupper && u16_tolower && u16_totitle && u16_cmp2 && u16_casecoll;
86 : }
87 :
88 0 : void clear() {
89 0 : tolower_fn = nullptr;
90 0 : toupper_fn = nullptr;
91 0 : totitle_fn = nullptr;
92 :
93 0 : uc_locale_language = nullptr;
94 :
95 0 : u8_toupper = nullptr;
96 0 : u8_tolower = nullptr;
97 0 : u8_totitle = nullptr;
98 :
99 0 : u8_cmp2 = nullptr;
100 0 : u8_casecoll = nullptr;
101 :
102 0 : u16_toupper = nullptr;
103 0 : u16_tolower = nullptr;
104 0 : u16_totitle = nullptr;
105 :
106 0 : u16_cmp2 = nullptr;
107 0 : u16_casecoll = nullptr;
108 0 : }
109 : };
110 :
111 : struct icu_iface {
112 : using case_fn = int32_t (*) (char16_t *dest, int32_t destCapacity, const char16_t *src, int32_t srcLength,
113 : const char *locale, int *pErrorCode);
114 : using case_iter_fn = int32_t (*) (char16_t *dest, int32_t destCapacity, const char16_t *src, int32_t srcLength,
115 : void *iter, const char *locale, int *pErrorCode);
116 :
117 : using cmp_fn = int32_t (*) (const char16_t *s1, int32_t length1, const char16_t *s2, int32_t length2, int8_t codePointOrder);
118 : using case_cmp_fn = int32_t (*) ( const char16_t *s1, int32_t length1, const char16_t *s2, int32_t length2,
119 : uint32_t options, int *pErrorCode);
120 :
121 : int32_t (*tolower_fn) (int32_t) = nullptr;
122 : int32_t (*toupper_fn) (int32_t) = nullptr;
123 : int32_t (*totitle_fn) (int32_t) = nullptr;
124 :
125 : case_fn u_strToLower = nullptr;
126 : case_fn u_strToUpper = nullptr;
127 : case_iter_fn u_strToTitle = nullptr;
128 :
129 : cmp_fn u_strCompare = nullptr;
130 : case_cmp_fn u_strCaseCompare = nullptr;
131 :
132 : static void *loadIcu(Dso &h, const char *name, StringView ver) {
133 : char buf[256] = { 0 };
134 : auto ret = h.sym<void *>(name);
135 : if (!ret && !ver.empty()) {
136 : strcpy(buf, name);
137 : strcat(buf, "_");
138 : strncat(buf, ver.data(), ver.size());
139 :
140 : ret = h.sym<void *>(buf);
141 : }
142 : return ret;
143 : }
144 :
145 : void load(Dso &handle, StringView verSuffix) {
146 : tolower_fn = reinterpret_cast<decltype(tolower_fn)>(loadIcu(handle, "u_tolower", verSuffix));
147 : toupper_fn = reinterpret_cast<decltype(toupper_fn)>(loadIcu(handle, "u_toupper", verSuffix));
148 : totitle_fn = reinterpret_cast<decltype(totitle_fn)>(loadIcu(handle, "u_totitle", verSuffix));
149 : u_strToLower = reinterpret_cast<decltype(u_strToLower)>(loadIcu(handle, "u_strToLower", verSuffix));
150 : u_strToUpper = reinterpret_cast<decltype(u_strToUpper)>(loadIcu(handle, "u_strToUpper", verSuffix));
151 : u_strToTitle = reinterpret_cast<decltype(u_strToTitle)>(loadIcu(handle, "u_strToTitle", verSuffix));
152 : u_strCompare = reinterpret_cast<decltype(u_strCompare)>(loadIcu(handle, "u_strCompare", verSuffix));
153 : u_strCaseCompare = reinterpret_cast<decltype(u_strCaseCompare)>(loadIcu(handle, "u_strCaseCompare", verSuffix));
154 : }
155 :
156 28530 : explicit operator bool() const {
157 0 : return tolower_fn && toupper_fn && totitle_fn
158 28530 : && u_strToLower && u_strToUpper && u_strToTitle && u_strCompare && u_strCaseCompare;
159 : }
160 :
161 : void clear() {
162 : tolower_fn = nullptr;
163 : toupper_fn = nullptr;
164 : totitle_fn = nullptr;
165 : u_strToLower = nullptr;
166 : u_strToUpper = nullptr;
167 : u_strToTitle = nullptr;
168 : u_strCompare = nullptr;
169 : u_strCaseCompare = nullptr;
170 : }
171 : };
172 :
173 : struct i18n {
174 25 : static i18n *getInstance() {
175 25 : static i18n s_instance;
176 25 : return &s_instance;
177 : }
178 :
179 25 : i18n() {
180 : // try unistring
181 : // try version 0 or 1 if no general symlink
182 25 : _handle = Dso("libunistring.so");
183 25 : if (!_handle) {
184 0 : _handle = Dso("libunistring.so.1");
185 : }
186 25 : if (!_handle) {
187 0 : _handle = Dso("libunistring.so.0");
188 : }
189 25 : if (_handle) {
190 25 : unistring.load(_handle);
191 25 : if (unistring) {
192 25 : return;
193 : } else {
194 0 : unistring.clear();
195 0 : _handle.close();
196 : }
197 : }
198 :
199 : // try ICU
200 0 : char buf[256] = { 0 };
201 0 : const char *paramName = nullptr;
202 0 : StringView verSuffix;
203 :
204 0 : auto dbg = Dso("libicutu.so");
205 0 : if (dbg) {
206 0 : auto getSystemParameterNameByIndex = dbg.sym<const char *(*)(int32_t)>("udbg_getSystemParameterNameByIndex");
207 0 : auto getSystemParameterValueByIndex = dbg.sym<int32_t (*) (int32_t i, char *, int32_t, int *)>("udbg_getSystemParameterValueByIndex");
208 :
209 0 : if (getSystemParameterNameByIndex && getSystemParameterValueByIndex) {
210 : int status;
211 0 : for (int32_t i = 0; (paramName = getSystemParameterNameByIndex(i)) != nullptr; ++i) {
212 0 : getSystemParameterValueByIndex(i, buf, 256, &status);
213 0 : if (StringView(paramName) == "version") {
214 0 : break;
215 : }
216 : }
217 : }
218 : }
219 :
220 0 : if (StringView(paramName) == "version") {
221 0 : verSuffix = StringView(buf).readUntil<StringView::Chars<'.'>>();
222 : }
223 :
224 0 : _handle = Dso("libicuuc.so");
225 0 : if (_handle) {
226 :
227 : }
228 0 : _handle.close();
229 0 : }
230 :
231 25 : ~i18n() { }
232 :
233 22050 : char32_t tolower(char32_t c) {
234 22050 : return _handle ? char32_t(icu ? icu.tolower_fn(int32_t(c)) : unistring.tolower_fn(int32_t(c))) : 0;
235 : }
236 :
237 6480 : char32_t toupper(char32_t c) {
238 6480 : return _handle ? char32_t(icu ? icu.toupper_fn(int32_t(c)) : unistring.toupper_fn(int32_t(c))) : 0;
239 : }
240 :
241 0 : char32_t totitle(char32_t c) {
242 0 : return _handle ? char32_t(icu ? icu.totitle_fn(int32_t(c)) : unistring.totitle_fn(int32_t(c))) : 0;
243 : }
244 :
245 : template <typename Interface>
246 0 : auto applyIcoFunction(WideStringView data, icu_iface::case_fn icuFn) {
247 0 : typename Interface::WideStringType ret;
248 0 : ret.resize(data.size());
249 :
250 0 : int status = 0;
251 0 : auto len = icuFn(ret.data(), ret.size(), data.data(), data.size(), NULL, &status);
252 0 : if (len <= int32_t(ret.size())) {
253 0 : ret.resize(len);
254 : } else {
255 0 : ret.resize(len);
256 0 : icuFn(ret.data(), ret.size(), data.data(), data.size(), NULL, &status);
257 : }
258 0 : return ret;
259 0 : }
260 :
261 : template <typename Interface>
262 19889 : auto applyUnistringFunction(StringView data, unistring_iface::u8_case_fn ustrFn) {
263 19889 : typename Interface::StringType ret;
264 19889 : ret.resize(data.size());
265 :
266 19889 : size_t targetSize = ret.size();
267 19889 : auto buf = ustrFn((const uint8_t *)data.data(), data.size(), unistring.uc_locale_language(), NULL,
268 19889 : (uint8_t *)ret.data(), &targetSize);
269 19889 : if (targetSize > ret.size()) {
270 0 : ret = typename Interface::StringType((char *)buf);
271 0 : ::free(buf);
272 : } else {
273 19889 : ret.resize(targetSize);
274 : }
275 39778 : return ret;
276 0 : }
277 :
278 : template <typename Interface>
279 111550 : auto applyUnistringFunction(WideStringView data, unistring_iface::u16_case_fn ustrFn) {
280 111550 : typename Interface::WideStringType ret;
281 111550 : ret.resize(data.size());
282 :
283 111550 : size_t targetSize = ret.size();
284 111550 : auto buf = ustrFn((const uint16_t *)data.data(), data.size(), unistring.uc_locale_language(), NULL,
285 111550 : (uint16_t *)ret.data(), &targetSize);
286 111550 : if (targetSize > ret.size()) {
287 0 : ret = typename Interface::WideStringType((char16_t *)buf);
288 0 : ::free(buf);
289 : } else {
290 111550 : ret.resize(targetSize);
291 : }
292 223100 : return ret;
293 0 : }
294 :
295 : template <typename Interface>
296 19839 : auto applyFunction(StringView data, icu_iface::case_fn icuFn, unistring_iface::u8_case_fn ustrFn) {
297 19839 : if (!_handle) {
298 0 : return data.str<Interface>();
299 : }
300 :
301 19839 : if (icuFn) {
302 : return string::toUtf8<Interface>(
303 : applyIcoFunction<Interface>(
304 : string::toUtf16<Interface>(data),
305 0 : icuFn));
306 : } else {
307 19839 : return applyUnistringFunction<Interface>(data, ustrFn);
308 : }
309 : }
310 :
311 : template <typename Interface>
312 111550 : auto applyFunction(WideStringView data, icu_iface::case_fn icuFn, unistring_iface::u16_case_fn ustrFn) {
313 111550 : if (!_handle) {
314 0 : return data.str<Interface>();
315 : }
316 :
317 111550 : if (icuFn) {
318 0 : return applyIcoFunction<Interface>(data, icuFn);
319 : } else {
320 111550 : return applyUnistringFunction<Interface>(data, ustrFn);
321 : }
322 : }
323 :
324 : template <typename Interface>
325 19506 : auto tolower(StringView data) {
326 19506 : return applyFunction<Interface>(data, icu.u_strToLower, unistring.u8_tolower);
327 : }
328 :
329 : template <typename Interface>
330 111550 : auto tolower(WideStringView data) {
331 111550 : return applyFunction<Interface>(data, icu.u_strToLower, unistring.u16_tolower);
332 : }
333 :
334 : template <typename Interface>
335 333 : auto toupper(StringView data) {
336 333 : return applyFunction<Interface>(data, icu.u_strToUpper, unistring.u8_toupper);
337 : }
338 :
339 : template <typename Interface>
340 0 : auto toupper(WideStringView data) {
341 0 : return applyFunction<Interface>(data, icu.u_strToUpper, unistring.u16_toupper);
342 : }
343 :
344 : template <typename Interface>
345 50 : auto totitle(StringView data) {
346 50 : if (!_handle) {
347 0 : return data.str<Interface>();
348 : }
349 :
350 50 : if (icu.u_strToTitle) {
351 0 : return string::toUtf8<Interface>(totitle<Interface>(string::toUtf16<Interface>(data)));
352 : } else {
353 50 : return applyUnistringFunction<Interface>(data, unistring.u8_totitle);
354 : }
355 : }
356 :
357 : template <typename Interface>
358 0 : auto totitle(WideStringView data) {
359 0 : if (!_handle) {
360 0 : return data.str<Interface>();
361 : }
362 :
363 0 : typename Interface::WideStringType ret;
364 0 : ret.resize(data.size());
365 :
366 0 : if (icu.u_strToTitle) {
367 0 : int status = 0;
368 0 : auto len = icu.u_strToTitle(ret.data(), ret.size(), data.data(), data.size(), NULL, NULL, &status);
369 0 : if (len <= int32_t(ret.size())) {
370 0 : ret.resize(len);
371 : } else {
372 0 : ret.resize(len);
373 0 : icu.u_strToTitle(ret.data(), ret.size(), data.data(), data.size(), NULL, NULL, &status);
374 : }
375 : } else {
376 0 : return applyUnistringFunction<Interface>(data, unistring.u16_totitle);
377 : }
378 :
379 0 : return ret;
380 0 : }
381 :
382 0 : int compare(StringView l, StringView r) {
383 0 : if (unistring.u8_cmp2) {
384 0 : return unistring.u8_cmp2((const uint8_t *)l.data(), l.size(), (const uint8_t *)r.data(), r.size());
385 0 : } else if (icu.u_strCompare) {
386 0 : auto lStr = string::toUtf16<memory::StandartInterface>(l);
387 0 : auto rStr = string::toUtf16<memory::StandartInterface>(r);
388 0 : return icu.u_strCompare(lStr.data(), lStr.size(), rStr.data(), rStr.size(), 1);
389 0 : }
390 0 : return string::compare_c(l, r);
391 : }
392 :
393 0 : int compare(WideStringView l, WideStringView r) {
394 0 : if (unistring.u16_cmp2) {
395 0 : return unistring.u16_cmp2((const uint16_t *)l.data(), l.size(), (const uint16_t *)r.data(), r.size());
396 0 : } else if (icu.u_strCompare) {
397 0 : return icu.u_strCompare(l.data(), l.size(), r.data(), r.size(), 1);
398 : }
399 0 : return string::compare_c(l, r);
400 : }
401 15925 : int caseCompare(StringView l, StringView r) {
402 15925 : if (unistring.u8_casecoll) {
403 15925 : int32_t ret = 0;
404 15925 : if (unistring.u8_casecoll((const uint8_t *)l.data(), l.size(), (const uint8_t *)r.data(), r.size(), unistring.uc_locale_language(), nullptr, &ret) == 0) {
405 250 : return ret;
406 : }
407 0 : } else if (icu.u_strCaseCompare) {
408 0 : int status = 0;
409 0 : auto lStr = string::toUtf16<memory::StandartInterface>(l);
410 0 : auto rStr = string::toUtf16<memory::StandartInterface>(r);
411 0 : return icu.u_strCaseCompare(lStr.data(), lStr.size(), rStr.data(), rStr.size(), U_COMPARE_CODE_POINT_ORDER, &status);
412 0 : }
413 15675 : return string::caseCompare_c(l, r);
414 : }
415 :
416 0 : int caseCompare(WideStringView l, WideStringView r) {
417 0 : if (unistring.u16_casecoll) {
418 0 : int32_t ret = 0;
419 0 : if (unistring.u16_casecoll((const uint16_t *)l.data(), l.size(), (const uint16_t *)r.data(), r.size(), unistring.uc_locale_language(), nullptr, &ret) == 0) {
420 0 : return ret;
421 : }
422 0 : } else if (icu.u_strCaseCompare) {
423 0 : int status = 0;
424 0 : return icu.u_strCaseCompare(l.data(), l.size(), r.data(), r.size(), U_COMPARE_CODE_POINT_ORDER, &status);
425 : }
426 0 : return string::caseCompare_c(l, r);
427 : }
428 :
429 : icu_iface icu;
430 : unistring_iface unistring;
431 :
432 : Dso _handle;
433 : };
434 :
435 : static i18n *s_instance = i18n::getInstance();
436 :
437 22050 : char32_t tolower(char32_t c) {
438 22050 : return s_instance->tolower(c);
439 : }
440 :
441 6480 : char32_t toupper(char32_t c) {
442 6480 : return s_instance->toupper(c);
443 : }
444 :
445 0 : char32_t totitle(char32_t c) {
446 0 : return s_instance->totitle(c);
447 : }
448 :
449 : template <>
450 750 : auto tolower<memory::PoolInterface>(StringView data) -> memory::PoolInterface::StringType {
451 750 : return s_instance->tolower<memory::PoolInterface>(data);
452 : }
453 :
454 : template <>
455 18756 : auto tolower<memory::StandartInterface>(StringView data) -> memory::StandartInterface::StringType {
456 18756 : return s_instance->tolower<memory::StandartInterface>(data);
457 : }
458 :
459 : template <>
460 183 : auto toupper<memory::PoolInterface>(StringView data) -> memory::PoolInterface::StringType {
461 183 : return s_instance->toupper<memory::PoolInterface>(data);
462 : }
463 :
464 : template <>
465 150 : auto toupper<memory::StandartInterface>(StringView data) -> memory::StandartInterface::StringType {
466 150 : return s_instance->toupper<memory::StandartInterface>(data);
467 : }
468 :
469 : template <>
470 0 : auto totitle<memory::PoolInterface>(StringView data) -> memory::PoolInterface::StringType {
471 0 : return s_instance->totitle<memory::PoolInterface>(data);
472 : }
473 :
474 : template <>
475 50 : auto totitle<memory::StandartInterface>(StringView data) -> memory::StandartInterface::StringType {
476 50 : return s_instance->totitle<memory::StandartInterface>(data);
477 : }
478 :
479 : template <>
480 111550 : auto tolower<memory::PoolInterface>(WideStringView data) -> memory::PoolInterface::WideStringType {
481 111550 : return s_instance->tolower<memory::PoolInterface>(data);
482 : }
483 :
484 : template <>
485 0 : auto tolower<memory::StandartInterface>(WideStringView data) -> memory::StandartInterface::WideStringType {
486 0 : return s_instance->tolower<memory::StandartInterface>(data);
487 : }
488 :
489 : template <>
490 0 : auto toupper<memory::PoolInterface>(WideStringView data) -> memory::PoolInterface::WideStringType {
491 0 : return s_instance->toupper<memory::PoolInterface>(data);
492 : }
493 :
494 : template <>
495 0 : auto toupper<memory::StandartInterface>(WideStringView data) -> memory::StandartInterface::WideStringType {
496 0 : return s_instance->toupper<memory::StandartInterface>(data);
497 : }
498 :
499 : template <>
500 0 : auto totitle<memory::PoolInterface>(WideStringView data) -> memory::PoolInterface::WideStringType {
501 0 : return s_instance->totitle<memory::PoolInterface>(data);
502 : }
503 :
504 : template <>
505 0 : auto totitle<memory::StandartInterface>(WideStringView data) -> memory::StandartInterface::WideStringType {
506 0 : return s_instance->totitle<memory::StandartInterface>(data);
507 : }
508 :
509 0 : int compare_u(StringView l, StringView r) {
510 0 : return s_instance->compare(l, r);
511 : }
512 :
513 0 : int compare_u(WideStringView l, WideStringView r) {
514 0 : return s_instance->compare(l, r);
515 : }
516 :
517 15925 : int caseCompare_u(StringView l, StringView r) {
518 15925 : return s_instance->caseCompare(l, r);
519 : }
520 :
521 0 : int caseCompare_u(WideStringView l, WideStringView r) {
522 0 : return s_instance->caseCompare(l, r);
523 : }
524 :
525 2957 : size_t makeRandomBytes(uint8_t * buf, size_t count) {
526 2957 : size_t generated = 0;
527 2957 : auto ret = ::getrandom(buf, count, GRND_RANDOM | GRND_NONBLOCK);
528 2957 : if (ret < ssize_t(count)) {
529 0 : buf += ret;
530 0 : count -= ret;
531 0 : generated += ret;
532 :
533 0 : ret = ::getrandom(buf, count, GRND_NONBLOCK);
534 0 : if (ret >= 0) {
535 0 : generated += ret;
536 : }
537 : }
538 2957 : return generated;
539 : }
540 :
541 : }
542 :
543 : #endif
|