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