blob: 7e23dfbab9a839139627e792d427a1687eb75f3b [file] [log] [blame]
Inseob Kim5f8f32c2018-08-24 11:10:44 +09001/*
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#define LOG_TAG "sysprop_java_gen"
18
19#include "JavaGen.h"
20
21#include <android-base/file.h>
22#include <android-base/logging.h>
23#include <android-base/stringprintf.h>
24#include <android-base/strings.h>
25#include <cerrno>
Inseob Kim4584b8f2019-06-17 15:52:48 +090026#include <filesystem>
Inseob Kim5f8f32c2018-08-24 11:10:44 +090027#include <regex>
28#include <string>
29
30#include "CodeWriter.h"
31#include "Common.h"
32#include "sysprop.pb.h"
33
Inseob Kim053b83d2019-06-26 13:41:51 +090034using android::base::ErrnoErrorf;
35using android::base::Errorf;
36using android::base::Result;
37
Inseob Kim5f8f32c2018-08-24 11:10:44 +090038namespace {
39
40constexpr const char* kIndent = " ";
41
42constexpr const char* kJavaFileImports =
Inseob Kim38569c72019-07-30 18:36:28 +090043 R"(import android.os.SystemProperties;
Inseob Kim1ca03f32019-06-08 16:31:59 +090044
Inseob Kim51313222020-01-15 14:37:49 +090045import java.lang.StringBuilder;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090046import java.util.ArrayList;
47import java.util.function.Function;
48import java.util.List;
Todd Kennedye37e10d2019-07-17 12:56:11 -070049import java.util.Locale;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090050import java.util.Optional;
51import java.util.StringJoiner;
Inseob Kim9c5147d2019-03-06 11:36:36 +090052import java.util.stream.Collectors;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090053
54)";
55
56constexpr const char* kJavaParsersAndFormatters =
Inseob Kim51313222020-01-15 14:37:49 +090057 R"s(private static Boolean tryParseBoolean(String str) {
Todd Kennedye37e10d2019-07-17 12:56:11 -070058 switch (str.toLowerCase(Locale.US)) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +090059 case "1":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090060 case "true":
61 return Boolean.TRUE;
62 case "0":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090063 case "false":
64 return Boolean.FALSE;
65 default:
66 return null;
67 }
68}
69
70private static Integer tryParseInteger(String str) {
71 try {
72 return Integer.valueOf(str);
73 } catch (NumberFormatException e) {
74 return null;
75 }
76}
77
78private static Long tryParseLong(String str) {
79 try {
80 return Long.valueOf(str);
81 } catch (NumberFormatException e) {
82 return null;
83 }
84}
85
86private static Double tryParseDouble(String str) {
87 try {
88 return Double.valueOf(str);
89 } catch (NumberFormatException e) {
90 return null;
91 }
92}
93
94private static String tryParseString(String str) {
Inseob Kime76e2722018-12-13 00:18:25 +090095 return "".equals(str) ? null : str;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090096}
97
98private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
99 try {
Todd Kennedye37e10d2019-07-17 12:56:11 -0700100 return Enum.valueOf(enumType, str.toUpperCase(Locale.US));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900101 } catch (IllegalArgumentException e) {
102 return null;
103 }
104}
105
106private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900107 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900108
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900109 List<T> ret = new ArrayList<>();
110
Inseob Kim51313222020-01-15 14:37:49 +0900111 int p = 0;
112 for (;;) {
113 StringBuilder sb = new StringBuilder();
114 while (p < str.length() && str.charAt(p) != ',') {
115 if (str.charAt(p) == '\\') ++p;
116 if (p == str.length()) break;
117 sb.append(str.charAt(p++));
118 }
119 ret.add(elementParser.apply(sb.toString()));
120 if (p == str.length()) break;
121 ++p;
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900122 }
123
124 return ret;
125}
126
127private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900128 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900129
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900130 List<T> ret = new ArrayList<>();
131
132 for (String element : str.split(",")) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900133 ret.add(tryParseEnum(enumType, element));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900134 }
135
136 return ret;
137}
138
Inseob Kim51313222020-01-15 14:37:49 +0900139private static String escape(String str) {
140 return str.replaceAll("([\\\\,])", "\\\\$1");
141}
142
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900143private static <T> String formatList(List<T> list) {
144 StringJoiner joiner = new StringJoiner(",");
145
146 for (T element : list) {
Inseob Kim51313222020-01-15 14:37:49 +0900147 joiner.add(element == null ? "" : escape(element.toString()));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900148 }
149
150 return joiner.toString();
151}
Inseob Kimf346e502019-01-04 10:32:39 +0900152
153private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
154 StringJoiner joiner = new StringJoiner(",");
155
156 for (T element : list) {
157 joiner.add(element == null ? "" : elementFormatter.apply(element));
158 }
159
160 return joiner.toString();
161}
Inseob Kim51313222020-01-15 14:37:49 +0900162)s";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900163
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900164const std::regex kRegexDot{"\\."};
165const std::regex kRegexUnderscore{"_"};
166
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900167std::string GetJavaTypeName(const sysprop::Property& prop);
168std::string GetJavaEnumTypeName(const sysprop::Property& prop);
169std::string GetJavaPackageName(const sysprop::Properties& props);
170std::string GetJavaClassName(const sysprop::Properties& props);
Inseob Kimf346e502019-01-04 10:32:39 +0900171std::string GetParsingExpression(const sysprop::Property& prop);
172std::string GetFormattingExpression(const sysprop::Property& prop);
Inseob Kim38569c72019-07-30 18:36:28 +0900173std::string GenerateJavaClass(const sysprop::Properties& props,
174 sysprop::Scope scope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900175
176std::string GetJavaEnumTypeName(const sysprop::Property& prop) {
Inseob Kim14e51872018-10-25 14:27:33 +0900177 return ApiNameToIdentifier(prop.api_name()) + "_values";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900178}
179
180std::string GetJavaTypeName(const sysprop::Property& prop) {
181 switch (prop.type()) {
182 case sysprop::Boolean:
183 return "Boolean";
184 case sysprop::Integer:
185 return "Integer";
186 case sysprop::Long:
187 return "Long";
188 case sysprop::Double:
189 return "Double";
190 case sysprop::String:
191 return "String";
192 case sysprop::Enum:
193 return GetJavaEnumTypeName(prop);
194 case sysprop::BooleanList:
195 return "List<Boolean>";
196 case sysprop::IntegerList:
197 return "List<Integer>";
198 case sysprop::LongList:
199 return "List<Long>";
200 case sysprop::DoubleList:
201 return "List<Double>";
202 case sysprop::StringList:
203 return "List<String>";
204 case sysprop::EnumList:
205 return "List<" + GetJavaEnumTypeName(prop) + ">";
206 default:
207 __builtin_unreachable();
208 }
209}
210
211std::string GetParsingExpression(const sysprop::Property& prop) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900212 switch (prop.type()) {
213 case sysprop::Boolean:
Inseob Kim0773b942018-10-04 19:29:27 +0900214 return "tryParseBoolean(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900215 case sysprop::Integer:
Inseob Kim0773b942018-10-04 19:29:27 +0900216 return "tryParseInteger(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900217 case sysprop::Long:
Inseob Kim0773b942018-10-04 19:29:27 +0900218 return "tryParseLong(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900219 case sysprop::Double:
Inseob Kim0773b942018-10-04 19:29:27 +0900220 return "tryParseDouble(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900221 case sysprop::String:
Inseob Kim0773b942018-10-04 19:29:27 +0900222 return "tryParseString(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900223 case sysprop::Enum:
Inseob Kim0773b942018-10-04 19:29:27 +0900224 return "tryParseEnum(" + GetJavaEnumTypeName(prop) + ".class, value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900225 case sysprop::EnumList:
Inseob Kim0773b942018-10-04 19:29:27 +0900226 return "tryParseEnumList(" + GetJavaEnumTypeName(prop) +
227 ".class, "
228 "value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900229 default:
230 break;
231 }
232
233 // The remaining cases are lists for types other than Enum which share the
234 // same parsing function "tryParseList"
235 std::string element_parser;
236
237 switch (prop.type()) {
238 case sysprop::BooleanList:
239 element_parser = "v -> tryParseBoolean(v)";
240 break;
241 case sysprop::IntegerList:
242 element_parser = "v -> tryParseInteger(v)";
243 break;
244 case sysprop::LongList:
245 element_parser = "v -> tryParseLong(v)";
246 break;
247 case sysprop::DoubleList:
248 element_parser = "v -> tryParseDouble(v)";
249 break;
250 case sysprop::StringList:
251 element_parser = "v -> tryParseString(v)";
252 break;
253 default:
254 __builtin_unreachable();
255 }
256
Inseob Kim0773b942018-10-04 19:29:27 +0900257 return "tryParseList(" + element_parser + ", value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900258}
259
Inseob Kimf346e502019-01-04 10:32:39 +0900260std::string GetFormattingExpression(const sysprop::Property& prop) {
Inseob Kim9c5147d2019-03-06 11:36:36 +0900261 if (prop.integer_as_bool()) {
262 if (prop.type() == sysprop::Boolean) {
263 // Boolean -> Integer String
264 return "(value ? \"1\" : \"0\")";
265 } else {
266 // List<Boolean> -> String directly
267 return "value.stream().map("
268 "x -> x == null ? \"\" : (x ? \"1\" : \"0\"))"
269 ".collect(Collectors.joining(\",\"))";
270 }
271 } else if (prop.type() == sysprop::Enum) {
Inseob Kimf346e502019-01-04 10:32:39 +0900272 return "value.getPropValue()";
273 } else if (prop.type() == sysprop::EnumList) {
274 return "formatEnumList(value, " + GetJavaEnumTypeName(prop) +
275 "::getPropValue)";
276 } else if (IsListProp(prop)) {
277 return "formatList(value)";
278 } else {
279 return "value.toString()";
280 }
281}
282
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900283std::string GetJavaPackageName(const sysprop::Properties& props) {
284 const std::string& module = props.module();
285 return module.substr(0, module.rfind('.'));
286}
287
288std::string GetJavaClassName(const sysprop::Properties& props) {
289 const std::string& module = props.module();
290 return module.substr(module.rfind('.') + 1);
291}
292
Inseob Kim38569c72019-07-30 18:36:28 +0900293std::string GenerateJavaClass(const sysprop::Properties& props,
294 sysprop::Scope scope) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900295 std::string package_name = GetJavaPackageName(props);
296 std::string class_name = GetJavaClassName(props);
297
298 CodeWriter writer(kIndent);
299 writer.Write("%s", kGeneratedFileFooterComments);
300 writer.Write("package %s;\n\n", package_name.c_str());
301 writer.Write("%s", kJavaFileImports);
302 writer.Write("public final class %s {\n", class_name.c_str());
303 writer.Indent();
304 writer.Write("private %s () {}\n\n", class_name.c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900305 writer.Write("%s", kJavaParsersAndFormatters);
306
307 for (int i = 0; i < props.prop_size(); ++i) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900308 const sysprop::Property& prop = props.prop(i);
309
Inseob Kim38569c72019-07-30 18:36:28 +0900310 // skip if scope is internal and we are generating public class
311 if (prop.scope() > scope) continue;
312
313 writer.Write("\n");
314
Inseob Kim14e51872018-10-25 14:27:33 +0900315 std::string prop_id = ApiNameToIdentifier(prop.api_name()).c_str();
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900316 std::string prop_type = GetJavaTypeName(prop);
317
318 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900319 writer.Write("public static enum %s {\n",
320 GetJavaEnumTypeName(prop).c_str());
321 writer.Indent();
Inseob Kimf346e502019-01-04 10:32:39 +0900322 std::vector<std::string> values =
323 android::base::Split(prop.enum_values(), "|");
324 for (int i = 0; i < values.size(); ++i) {
325 const std::string& name = values[i];
326 writer.Write("%s(\"%s\")", ToUpper(name).c_str(), name.c_str());
327 if (i + 1 < values.size()) {
328 writer.Write(",\n");
329 } else {
330 writer.Write(";\n");
331 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900332 }
Inseob Kimf346e502019-01-04 10:32:39 +0900333 writer.Write(
334 "private final String propValue;\n"
335 "private %s(String propValue) {\n",
336 GetJavaEnumTypeName(prop).c_str());
337 writer.Indent();
338 writer.Write("this.propValue = propValue;\n");
339 writer.Dedent();
340 writer.Write(
341 "}\n"
342 "public String getPropValue() {\n");
343 writer.Indent();
344 writer.Write("return propValue;\n");
345 writer.Dedent();
346 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900347 writer.Dedent();
348 writer.Write("}\n\n");
349 }
350
Inseob Kim5e64f892019-06-17 15:09:48 +0900351 if (prop.deprecated()) {
352 writer.Write("@Deprecated\n");
353 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900354
Inseob Kime76e2722018-12-13 00:18:25 +0900355 if (IsListProp(prop)) {
356 writer.Write("public static %s %s() {\n", prop_type.c_str(),
357 prop_id.c_str());
358 writer.Indent();
359 writer.Write("String value = SystemProperties.get(\"%s\");\n",
360 prop.prop_name().c_str());
361 writer.Write("return %s;\n", GetParsingExpression(prop).c_str());
362 writer.Dedent();
363 writer.Write("}\n");
364 } else {
365 writer.Write("public static Optional<%s> %s() {\n", prop_type.c_str(),
366 prop_id.c_str());
367 writer.Indent();
368 writer.Write("String value = SystemProperties.get(\"%s\");\n",
369 prop.prop_name().c_str());
370 writer.Write("return Optional.ofNullable(%s);\n",
371 GetParsingExpression(prop).c_str());
372 writer.Dedent();
373 writer.Write("}\n");
374 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900375
Inseob Kim4ab20d32019-12-03 13:44:55 +0900376 if (prop.access() != sysprop::Readonly) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900377 writer.Write("\n");
Inseob Kim5e64f892019-06-17 15:09:48 +0900378 if (prop.deprecated()) {
379 writer.Write("@Deprecated\n");
380 }
Inseob Kim0773b942018-10-04 19:29:27 +0900381 writer.Write("public static void %s(%s value) {\n", prop_id.c_str(),
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900382 prop_type.c_str());
383 writer.Indent();
Inseob Kimbd987ed2018-11-01 11:07:15 +0900384 writer.Write("SystemProperties.set(\"%s\", value == null ? \"\" : %s);\n",
Inseob Kim14e51872018-10-25 14:27:33 +0900385 prop.prop_name().c_str(),
Inseob Kimf346e502019-01-04 10:32:39 +0900386 GetFormattingExpression(prop).c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900387 writer.Dedent();
Inseob Kim14e51872018-10-25 14:27:33 +0900388 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900389 }
390 }
391
392 writer.Dedent();
393 writer.Write("}\n");
394
Inseob Kim053b83d2019-06-26 13:41:51 +0900395 return writer.Code();
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900396}
397
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900398} // namespace
399
Inseob Kim053b83d2019-06-26 13:41:51 +0900400Result<void> GenerateJavaLibrary(const std::string& input_file_path,
Inseob Kim38569c72019-07-30 18:36:28 +0900401 sysprop::Scope scope,
Inseob Kim053b83d2019-06-26 13:41:51 +0900402 const std::string& java_output_dir) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900403 sysprop::Properties props;
404
Bernie Innocenti7ac8da42020-02-06 23:30:12 +0900405 if (auto res = ParseProps(input_file_path); res.ok()) {
Inseob Kim053b83d2019-06-26 13:41:51 +0900406 props = std::move(*res);
407 } else {
408 return res.error();
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900409 }
410
Inseob Kim38569c72019-07-30 18:36:28 +0900411 std::string java_result = GenerateJavaClass(props, scope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900412 std::string package_name = GetJavaPackageName(props);
413 std::string java_package_dir =
414 java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/");
415
Inseob Kim4584b8f2019-06-17 15:52:48 +0900416 std::error_code ec;
417 std::filesystem::create_directories(java_package_dir, ec);
418 if (ec) {
Inseob Kim053b83d2019-06-26 13:41:51 +0900419 return Errorf("Creating directory to {} failed: {}", java_package_dir,
420 ec.message());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900421 }
422
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900423 std::string class_name = GetJavaClassName(props);
424 std::string java_output_file = java_package_dir + "/" + class_name + ".java";
425 if (!android::base::WriteStringToFile(java_result, java_output_file)) {
Inseob Kim053b83d2019-06-26 13:41:51 +0900426 return ErrnoErrorf("Writing generated java class to {} failed",
427 java_output_file);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900428 }
429
Inseob Kim053b83d2019-06-26 13:41:51 +0900430 return {};
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900431}