1 /*
2 * Copyright (C) 2018 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 <unistd.h>
18 #include <string>
19
20 #include <android-base/file.h>
21 #include <android-base/test_utils.h>
22 #include <gtest/gtest.h>
23
24 #include "JavaGen.h"
25
26 namespace {
27
28 constexpr const char* kTestSyspropFile =
29 R"(owner: Vendor
30 module: "com.somecompany.TestProperties"
31
32 prop {
33 api_name: "test_double"
34 type: Double
35 prop_name: "vendor.test_double"
36 scope: Internal
37 access: ReadWrite
38 }
39 prop {
40 api_name: "test_int"
41 type: Integer
42 prop_name: "vendor.test_int"
43 scope: Public
44 access: ReadWrite
45 }
46 prop {
47 api_name: "test_string"
48 type: String
49 prop_name: "vendor.test.string"
50 scope: Public
51 access: Readonly
52 legacy_prop_name: "vendor.old.string"
53 }
54 prop {
55 api_name: "test_enum"
56 type: Enum
57 prop_name: "vendor.test.enum"
58 enum_values: "a|b|c|D|e|f|G"
59 scope: Internal
60 access: ReadWrite
61 }
62 prop {
63 api_name: "test_BOOLeaN"
64 type: Boolean
65 prop_name: "ro.vendor.test.b"
66 scope: Public
67 access: Writeonce
68 }
69 prop {
70 api_name: "vendor_os_test-long"
71 type: Long
72 scope: Public
73 access: ReadWrite
74 }
75 prop {
76 api_name: "test_double_list"
77 type: DoubleList
78 scope: Internal
79 access: ReadWrite
80 }
81 prop {
82 api_name: "test_list_int"
83 type: IntegerList
84 scope: Public
85 access: ReadWrite
86 }
87 prop {
88 api_name: "test_strlist"
89 type: StringList
90 scope: Public
91 access: ReadWrite
92 deprecated: true
93 }
94 prop {
95 api_name: "el"
96 type: EnumList
97 enum_values: "enu|mva|lue"
98 scope: Internal
99 access: ReadWrite
100 deprecated: true
101 }
102 )";
103
104 constexpr const char* kExpectedPublicOutput =
105 R"s(// Generated by the sysprop generator. DO NOT EDIT!
106
107 package com.somecompany;
108
109 import android.os.SystemProperties;
110 import android.util.Log;
111
112 import java.lang.StringBuilder;
113 import java.util.ArrayList;
114 import java.util.function.Function;
115 import java.util.List;
116 import java.util.Locale;
117 import java.util.Optional;
118 import java.util.StringJoiner;
119 import java.util.stream.Collectors;
120
121 public final class TestProperties {
122 private TestProperties () {}
123
124 private static Boolean tryParseBoolean(String str) {
125 if (str == null) return null;
126 switch (str.toLowerCase(Locale.US)) {
127 case "1":
128 case "true":
129 return Boolean.TRUE;
130 case "0":
131 case "false":
132 return Boolean.FALSE;
133 default:
134 return null;
135 }
136 }
137
138 private static Integer tryParseInteger(String str) {
139 try {
140 return Integer.valueOf(str);
141 } catch (NumberFormatException e) {
142 return null;
143 }
144 }
145
146 private static Integer tryParseUInt(String str) {
147 try {
148 return Integer.parseUnsignedInt(str);
149 } catch (NumberFormatException e) {
150 return null;
151 }
152 }
153
154 private static Long tryParseLong(String str) {
155 try {
156 return Long.valueOf(str);
157 } catch (NumberFormatException e) {
158 return null;
159 }
160 }
161
162 private static Long tryParseULong(String str) {
163 try {
164 return Long.parseUnsignedLong(str);
165 } catch (NumberFormatException e) {
166 return null;
167 }
168 }
169
170 private static Double tryParseDouble(String str) {
171 try {
172 return Double.valueOf(str);
173 } catch (NumberFormatException e) {
174 return null;
175 }
176 }
177
178 private static String tryParseString(String str) {
179 return "".equals(str) ? null : str;
180 }
181
182 private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
183 try {
184 return Enum.valueOf(enumType, str.toUpperCase(Locale.US));
185 } catch (IllegalArgumentException e) {
186 return null;
187 }
188 }
189
190 private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
191 if ("".equals(str)) return new ArrayList<>();
192
193 List<T> ret = new ArrayList<>();
194
195 int p = 0;
196 for (;;) {
197 StringBuilder sb = new StringBuilder();
198 while (p < str.length() && str.charAt(p) != ',') {
199 if (str.charAt(p) == '\\') ++p;
200 if (p == str.length()) break;
201 sb.append(str.charAt(p++));
202 }
203 ret.add(elementParser.apply(sb.toString()));
204 if (p == str.length()) break;
205 ++p;
206 }
207
208 return ret;
209 }
210
211 private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
212 if ("".equals(str)) return new ArrayList<>();
213
214 List<T> ret = new ArrayList<>();
215
216 for (String element : str.split(",")) {
217 ret.add(tryParseEnum(enumType, element));
218 }
219
220 return ret;
221 }
222
223 private static String escape(String str) {
224 return str.replaceAll("([\\\\,])", "\\\\$1");
225 }
226
227 private static <T> String formatList(List<T> list) {
228 StringJoiner joiner = new StringJoiner(",");
229
230 for (T element : list) {
231 joiner.add(element == null ? "" : escape(element.toString()));
232 }
233
234 return joiner.toString();
235 }
236
237 private static String formatUIntList(List<Integer> list) {
238 StringJoiner joiner = new StringJoiner(",");
239
240 for (Integer element : list) {
241 joiner.add(element == null ? "" : escape(Integer.toUnsignedString(element)));
242 }
243
244 return joiner.toString();
245 }
246
247 private static String formatULongList(List<Long> list) {
248 StringJoiner joiner = new StringJoiner(",");
249
250 for (Long element : list) {
251 joiner.add(element == null ? "" : escape(Long.toUnsignedString(element)));
252 }
253
254 return joiner.toString();
255 }
256
257 private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
258 StringJoiner joiner = new StringJoiner(",");
259
260 for (T element : list) {
261 joiner.add(element == null ? "" : elementFormatter.apply(element));
262 }
263
264 return joiner.toString();
265 }
266
267 public static Optional<Integer> test_int() {
268 String value = SystemProperties.get("vendor.test_int");
269 return Optional.ofNullable(tryParseInteger(value));
270 }
271
272 public static void test_int(Integer value) {
273 SystemProperties.set("vendor.test_int", value == null ? "" : value.toString());
274 }
275
276 public static Optional<String> test_string() {
277 String value = SystemProperties.get("vendor.test.string");
278 if ("".equals(value)) {
279 Log.v("TestProperties", "prop vendor.test.string doesn't exist; fallback to legacy prop vendor.old.string");
280 value = SystemProperties.get("vendor.old.string");
281 }
282 return Optional.ofNullable(tryParseString(value));
283 }
284
285 public static Optional<Boolean> test_BOOLeaN() {
286 String value = SystemProperties.get("ro.vendor.test.b");
287 return Optional.ofNullable(tryParseBoolean(value));
288 }
289
290 public static void test_BOOLeaN(Boolean value) {
291 SystemProperties.set("ro.vendor.test.b", value == null ? "" : value.toString());
292 }
293
294 public static Optional<Long> vendor_os_test_long() {
295 String value = SystemProperties.get("vendor.vendor_os_test-long");
296 return Optional.ofNullable(tryParseLong(value));
297 }
298
299 public static void vendor_os_test_long(Long value) {
300 SystemProperties.set("vendor.vendor_os_test-long", value == null ? "" : value.toString());
301 }
302
303 public static List<Integer> test_list_int() {
304 String value = SystemProperties.get("vendor.test_list_int");
305 return tryParseList(v -> tryParseInteger(v), value);
306 }
307
308 public static void test_list_int(List<Integer> value) {
309 SystemProperties.set("vendor.test_list_int", value == null ? "" : formatList(value));
310 }
311
312 @Deprecated
313 public static List<String> test_strlist() {
314 String value = SystemProperties.get("vendor.test_strlist");
315 return tryParseList(v -> tryParseString(v), value);
316 }
317
318 @Deprecated
319 public static void test_strlist(List<String> value) {
320 SystemProperties.set("vendor.test_strlist", value == null ? "" : formatList(value));
321 }
322 }
323 )s";
324
325 constexpr const char* kExpectedInternalOutput =
326 R"s(// Generated by the sysprop generator. DO NOT EDIT!
327
328 package com.somecompany;
329
330 import android.os.SystemProperties;
331 import android.util.Log;
332
333 import java.lang.StringBuilder;
334 import java.util.ArrayList;
335 import java.util.function.Function;
336 import java.util.List;
337 import java.util.Locale;
338 import java.util.Optional;
339 import java.util.StringJoiner;
340 import java.util.stream.Collectors;
341
342 public final class TestProperties {
343 private TestProperties () {}
344
345 private static Boolean tryParseBoolean(String str) {
346 if (str == null) return null;
347 switch (str.toLowerCase(Locale.US)) {
348 case "1":
349 case "true":
350 return Boolean.TRUE;
351 case "0":
352 case "false":
353 return Boolean.FALSE;
354 default:
355 return null;
356 }
357 }
358
359 private static Integer tryParseInteger(String str) {
360 try {
361 return Integer.valueOf(str);
362 } catch (NumberFormatException e) {
363 return null;
364 }
365 }
366
367 private static Integer tryParseUInt(String str) {
368 try {
369 return Integer.parseUnsignedInt(str);
370 } catch (NumberFormatException e) {
371 return null;
372 }
373 }
374
375 private static Long tryParseLong(String str) {
376 try {
377 return Long.valueOf(str);
378 } catch (NumberFormatException e) {
379 return null;
380 }
381 }
382
383 private static Long tryParseULong(String str) {
384 try {
385 return Long.parseUnsignedLong(str);
386 } catch (NumberFormatException e) {
387 return null;
388 }
389 }
390
391 private static Double tryParseDouble(String str) {
392 try {
393 return Double.valueOf(str);
394 } catch (NumberFormatException e) {
395 return null;
396 }
397 }
398
399 private static String tryParseString(String str) {
400 return "".equals(str) ? null : str;
401 }
402
403 private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
404 try {
405 return Enum.valueOf(enumType, str.toUpperCase(Locale.US));
406 } catch (IllegalArgumentException e) {
407 return null;
408 }
409 }
410
411 private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
412 if ("".equals(str)) return new ArrayList<>();
413
414 List<T> ret = new ArrayList<>();
415
416 int p = 0;
417 for (;;) {
418 StringBuilder sb = new StringBuilder();
419 while (p < str.length() && str.charAt(p) != ',') {
420 if (str.charAt(p) == '\\') ++p;
421 if (p == str.length()) break;
422 sb.append(str.charAt(p++));
423 }
424 ret.add(elementParser.apply(sb.toString()));
425 if (p == str.length()) break;
426 ++p;
427 }
428
429 return ret;
430 }
431
432 private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
433 if ("".equals(str)) return new ArrayList<>();
434
435 List<T> ret = new ArrayList<>();
436
437 for (String element : str.split(",")) {
438 ret.add(tryParseEnum(enumType, element));
439 }
440
441 return ret;
442 }
443
444 private static String escape(String str) {
445 return str.replaceAll("([\\\\,])", "\\\\$1");
446 }
447
448 private static <T> String formatList(List<T> list) {
449 StringJoiner joiner = new StringJoiner(",");
450
451 for (T element : list) {
452 joiner.add(element == null ? "" : escape(element.toString()));
453 }
454
455 return joiner.toString();
456 }
457
458 private static String formatUIntList(List<Integer> list) {
459 StringJoiner joiner = new StringJoiner(",");
460
461 for (Integer element : list) {
462 joiner.add(element == null ? "" : escape(Integer.toUnsignedString(element)));
463 }
464
465 return joiner.toString();
466 }
467
468 private static String formatULongList(List<Long> list) {
469 StringJoiner joiner = new StringJoiner(",");
470
471 for (Long element : list) {
472 joiner.add(element == null ? "" : escape(Long.toUnsignedString(element)));
473 }
474
475 return joiner.toString();
476 }
477
478 private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
479 StringJoiner joiner = new StringJoiner(",");
480
481 for (T element : list) {
482 joiner.add(element == null ? "" : elementFormatter.apply(element));
483 }
484
485 return joiner.toString();
486 }
487
488 public static Optional<Double> test_double() {
489 String value = SystemProperties.get("vendor.test_double");
490 return Optional.ofNullable(tryParseDouble(value));
491 }
492
493 public static void test_double(Double value) {
494 SystemProperties.set("vendor.test_double", value == null ? "" : value.toString());
495 }
496
497 public static Optional<Integer> test_int() {
498 String value = SystemProperties.get("vendor.test_int");
499 return Optional.ofNullable(tryParseInteger(value));
500 }
501
502 public static void test_int(Integer value) {
503 SystemProperties.set("vendor.test_int", value == null ? "" : value.toString());
504 }
505
506 public static Optional<String> test_string() {
507 String value = SystemProperties.get("vendor.test.string");
508 if ("".equals(value)) {
509 Log.v("TestProperties", "prop vendor.test.string doesn't exist; fallback to legacy prop vendor.old.string");
510 value = SystemProperties.get("vendor.old.string");
511 }
512 return Optional.ofNullable(tryParseString(value));
513 }
514
515 public static enum test_enum_values {
516 A("a"),
517 B("b"),
518 C("c"),
519 D("D"),
520 E("e"),
521 F("f"),
522 G("G");
523 private final String propValue;
524 private test_enum_values(String propValue) {
525 this.propValue = propValue;
526 }
527 public String getPropValue() {
528 return propValue;
529 }
530 }
531
532 public static Optional<test_enum_values> test_enum() {
533 String value = SystemProperties.get("vendor.test.enum");
534 return Optional.ofNullable(tryParseEnum(test_enum_values.class, value));
535 }
536
537 public static void test_enum(test_enum_values value) {
538 SystemProperties.set("vendor.test.enum", value == null ? "" : value.getPropValue());
539 }
540
541 public static Optional<Boolean> test_BOOLeaN() {
542 String value = SystemProperties.get("ro.vendor.test.b");
543 return Optional.ofNullable(tryParseBoolean(value));
544 }
545
546 public static void test_BOOLeaN(Boolean value) {
547 SystemProperties.set("ro.vendor.test.b", value == null ? "" : value.toString());
548 }
549
550 public static Optional<Long> vendor_os_test_long() {
551 String value = SystemProperties.get("vendor.vendor_os_test-long");
552 return Optional.ofNullable(tryParseLong(value));
553 }
554
555 public static void vendor_os_test_long(Long value) {
556 SystemProperties.set("vendor.vendor_os_test-long", value == null ? "" : value.toString());
557 }
558
559 public static List<Double> test_double_list() {
560 String value = SystemProperties.get("vendor.test_double_list");
561 return tryParseList(v -> tryParseDouble(v), value);
562 }
563
564 public static void test_double_list(List<Double> value) {
565 SystemProperties.set("vendor.test_double_list", value == null ? "" : formatList(value));
566 }
567
568 public static List<Integer> test_list_int() {
569 String value = SystemProperties.get("vendor.test_list_int");
570 return tryParseList(v -> tryParseInteger(v), value);
571 }
572
573 public static void test_list_int(List<Integer> value) {
574 SystemProperties.set("vendor.test_list_int", value == null ? "" : formatList(value));
575 }
576
577 @Deprecated
578 public static List<String> test_strlist() {
579 String value = SystemProperties.get("vendor.test_strlist");
580 return tryParseList(v -> tryParseString(v), value);
581 }
582
583 @Deprecated
584 public static void test_strlist(List<String> value) {
585 SystemProperties.set("vendor.test_strlist", value == null ? "" : formatList(value));
586 }
587
588 public static enum el_values {
589 ENU("enu"),
590 MVA("mva"),
591 LUE("lue");
592 private final String propValue;
593 private el_values(String propValue) {
594 this.propValue = propValue;
595 }
596 public String getPropValue() {
597 return propValue;
598 }
599 }
600
601 @Deprecated
602 public static List<el_values> el() {
603 String value = SystemProperties.get("vendor.el");
604 return tryParseEnumList(el_values.class, value);
605 }
606
607 @Deprecated
608 public static void el(List<el_values> value) {
609 SystemProperties.set("vendor.el", value == null ? "" : formatEnumList(value, el_values::getPropValue));
610 }
611 }
612 )s";
613
614 } // namespace
615
616 using namespace std::string_literals;
617
TEST(SyspropTest,JavaGenTest)618 TEST(SyspropTest, JavaGenTest) {
619 TemporaryFile temp_file;
620
621 // strlen is optimized for constants, so don't worry about it.
622 ASSERT_EQ(write(temp_file.fd, kTestSyspropFile, strlen(kTestSyspropFile)),
623 strlen(kTestSyspropFile));
624 close(temp_file.fd);
625 temp_file.fd = -1;
626
627 TemporaryDir temp_dir;
628
629 std::pair<sysprop::Scope, const char*> tests[] = {
630 {sysprop::Scope::Internal, kExpectedInternalOutput},
631 {sysprop::Scope::Public, kExpectedPublicOutput},
632 };
633
634 for (auto [scope, expected_output] : tests) {
635 ASSERT_RESULT_OK(GenerateJavaLibrary(temp_file.path, scope, temp_dir.path));
636
637 std::string java_output_path =
638 temp_dir.path + "/com/somecompany/TestProperties.java"s;
639
640 std::string java_output;
641 ASSERT_TRUE(
642 android::base::ReadFileToString(java_output_path, &java_output, true));
643 EXPECT_EQ(java_output, expected_output);
644
645 unlink(java_output_path.c_str());
646 rmdir((temp_dir.path + "/com/somecompany"s).c_str());
647 rmdir((temp_dir.path + "/com"s).c_str());
648 }
649 }
650