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