blob: a9b7a1aabcf3c55b8ce1e57320fe625f12cbbc3a [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>
26#include <regex>
27#include <string>
28
29#include "CodeWriter.h"
30#include "Common.h"
31#include "sysprop.pb.h"
32
33namespace {
34
35constexpr const char* kIndent = " ";
36
37constexpr const char* kJavaFileImports =
38 R"(import android.annotation.SystemApi;
Inseob Kim0773b942018-10-04 19:29:27 +090039import android.os.SystemProperties;
Inseob Kim1ca03f32019-06-08 16:31:59 +090040
Inseob Kim5f8f32c2018-08-24 11:10:44 +090041import java.util.ArrayList;
42import java.util.function.Function;
43import java.util.List;
44import java.util.Optional;
45import java.util.StringJoiner;
Inseob Kim9c5147d2019-03-06 11:36:36 +090046import java.util.stream.Collectors;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090047
48)";
49
50constexpr const char* kJavaParsersAndFormatters =
51 R"(private static Boolean tryParseBoolean(String str) {
52 switch (str.toLowerCase()) {
53 case "1":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090054 case "true":
55 return Boolean.TRUE;
56 case "0":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090057 case "false":
58 return Boolean.FALSE;
59 default:
60 return null;
61 }
62}
63
64private static Integer tryParseInteger(String str) {
65 try {
66 return Integer.valueOf(str);
67 } catch (NumberFormatException e) {
68 return null;
69 }
70}
71
72private static Long tryParseLong(String str) {
73 try {
74 return Long.valueOf(str);
75 } catch (NumberFormatException e) {
76 return null;
77 }
78}
79
80private static Double tryParseDouble(String str) {
81 try {
82 return Double.valueOf(str);
83 } catch (NumberFormatException e) {
84 return null;
85 }
86}
87
88private static String tryParseString(String str) {
Inseob Kime76e2722018-12-13 00:18:25 +090089 return "".equals(str) ? null : str;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090090}
91
92private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
93 try {
Inseob Kim58bd7fe2019-01-04 16:03:33 +090094 return Enum.valueOf(enumType, str.toUpperCase());
Inseob Kim5f8f32c2018-08-24 11:10:44 +090095 } catch (IllegalArgumentException e) {
96 return null;
97 }
98}
99
100private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900101 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900102
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900103 List<T> ret = new ArrayList<>();
104
105 for (String element : str.split(",")) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900106 ret.add(elementParser.apply(element));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900107 }
108
109 return ret;
110}
111
112private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900113 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900114
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900115 List<T> ret = new ArrayList<>();
116
117 for (String element : str.split(",")) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900118 ret.add(tryParseEnum(enumType, element));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900119 }
120
121 return ret;
122}
123
124private static <T> String formatList(List<T> list) {
125 StringJoiner joiner = new StringJoiner(",");
126
127 for (T element : list) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900128 joiner.add(element == null ? "" : element.toString());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900129 }
130
131 return joiner.toString();
132}
Inseob Kimf346e502019-01-04 10:32:39 +0900133
134private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
135 StringJoiner joiner = new StringJoiner(",");
136
137 for (T element : list) {
138 joiner.add(element == null ? "" : elementFormatter.apply(element));
139 }
140
141 return joiner.toString();
142}
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900143)";
144
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900145const std::regex kRegexDot{"\\."};
146const std::regex kRegexUnderscore{"_"};
147
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900148std::string GetJavaTypeName(const sysprop::Property& prop);
149std::string GetJavaEnumTypeName(const sysprop::Property& prop);
150std::string GetJavaPackageName(const sysprop::Properties& props);
151std::string GetJavaClassName(const sysprop::Properties& props);
Inseob Kimf346e502019-01-04 10:32:39 +0900152std::string GetParsingExpression(const sysprop::Property& prop);
153std::string GetFormattingExpression(const sysprop::Property& prop);
Inseob Kim14e51872018-10-25 14:27:33 +0900154void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900155bool GenerateJavaClass(const sysprop::Properties& props,
156 std::string* java_result, std::string* err);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900157
158std::string GetJavaEnumTypeName(const sysprop::Property& prop) {
Inseob Kim14e51872018-10-25 14:27:33 +0900159 return ApiNameToIdentifier(prop.api_name()) + "_values";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900160}
161
162std::string GetJavaTypeName(const sysprop::Property& prop) {
163 switch (prop.type()) {
164 case sysprop::Boolean:
165 return "Boolean";
166 case sysprop::Integer:
167 return "Integer";
168 case sysprop::Long:
169 return "Long";
170 case sysprop::Double:
171 return "Double";
172 case sysprop::String:
173 return "String";
174 case sysprop::Enum:
175 return GetJavaEnumTypeName(prop);
176 case sysprop::BooleanList:
177 return "List<Boolean>";
178 case sysprop::IntegerList:
179 return "List<Integer>";
180 case sysprop::LongList:
181 return "List<Long>";
182 case sysprop::DoubleList:
183 return "List<Double>";
184 case sysprop::StringList:
185 return "List<String>";
186 case sysprop::EnumList:
187 return "List<" + GetJavaEnumTypeName(prop) + ">";
188 default:
189 __builtin_unreachable();
190 }
191}
192
193std::string GetParsingExpression(const sysprop::Property& prop) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900194 switch (prop.type()) {
195 case sysprop::Boolean:
Inseob Kim0773b942018-10-04 19:29:27 +0900196 return "tryParseBoolean(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900197 case sysprop::Integer:
Inseob Kim0773b942018-10-04 19:29:27 +0900198 return "tryParseInteger(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900199 case sysprop::Long:
Inseob Kim0773b942018-10-04 19:29:27 +0900200 return "tryParseLong(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900201 case sysprop::Double:
Inseob Kim0773b942018-10-04 19:29:27 +0900202 return "tryParseDouble(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900203 case sysprop::String:
Inseob Kim0773b942018-10-04 19:29:27 +0900204 return "tryParseString(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900205 case sysprop::Enum:
Inseob Kim0773b942018-10-04 19:29:27 +0900206 return "tryParseEnum(" + GetJavaEnumTypeName(prop) + ".class, value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900207 case sysprop::EnumList:
Inseob Kim0773b942018-10-04 19:29:27 +0900208 return "tryParseEnumList(" + GetJavaEnumTypeName(prop) +
209 ".class, "
210 "value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900211 default:
212 break;
213 }
214
215 // The remaining cases are lists for types other than Enum which share the
216 // same parsing function "tryParseList"
217 std::string element_parser;
218
219 switch (prop.type()) {
220 case sysprop::BooleanList:
221 element_parser = "v -> tryParseBoolean(v)";
222 break;
223 case sysprop::IntegerList:
224 element_parser = "v -> tryParseInteger(v)";
225 break;
226 case sysprop::LongList:
227 element_parser = "v -> tryParseLong(v)";
228 break;
229 case sysprop::DoubleList:
230 element_parser = "v -> tryParseDouble(v)";
231 break;
232 case sysprop::StringList:
233 element_parser = "v -> tryParseString(v)";
234 break;
235 default:
236 __builtin_unreachable();
237 }
238
Inseob Kim0773b942018-10-04 19:29:27 +0900239 return "tryParseList(" + element_parser + ", value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900240}
241
Inseob Kimf346e502019-01-04 10:32:39 +0900242std::string GetFormattingExpression(const sysprop::Property& prop) {
Inseob Kim9c5147d2019-03-06 11:36:36 +0900243 if (prop.integer_as_bool()) {
244 if (prop.type() == sysprop::Boolean) {
245 // Boolean -> Integer String
246 return "(value ? \"1\" : \"0\")";
247 } else {
248 // List<Boolean> -> String directly
249 return "value.stream().map("
250 "x -> x == null ? \"\" : (x ? \"1\" : \"0\"))"
251 ".collect(Collectors.joining(\",\"))";
252 }
253 } else if (prop.type() == sysprop::Enum) {
Inseob Kimf346e502019-01-04 10:32:39 +0900254 return "value.getPropValue()";
255 } else if (prop.type() == sysprop::EnumList) {
256 return "formatEnumList(value, " + GetJavaEnumTypeName(prop) +
257 "::getPropValue)";
258 } else if (IsListProp(prop)) {
259 return "formatList(value)";
260 } else {
261 return "value.toString()";
262 }
263}
264
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900265std::string GetJavaPackageName(const sysprop::Properties& props) {
266 const std::string& module = props.module();
267 return module.substr(0, module.rfind('.'));
268}
269
270std::string GetJavaClassName(const sysprop::Properties& props) {
271 const std::string& module = props.module();
272 return module.substr(module.rfind('.') + 1);
273}
274
Inseob Kim14e51872018-10-25 14:27:33 +0900275void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope) {
276 switch (scope) {
Inseob Kim1ca03f32019-06-08 16:31:59 +0900277 // This is to restrict access from modules linking against SDK.
278 // TODO(b/131637873): remove this after cutting dependency on
279 // java_sdk_library
280 case sysprop::Public:
Inseob Kimce4f0462018-12-26 14:36:59 +0900281 writer.Write("/** @hide */\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900282 writer.Write("@SystemApi\n");
283 break;
284 case sysprop::Internal:
285 writer.Write("/** @hide */\n");
286 break;
287 default:
288 break;
289 }
290}
291
292bool GenerateJavaClass(const sysprop::Properties& props,
293 std::string* java_result,
294 [[maybe_unused]] std::string* err) {
Inseob Kim1a58c042018-11-01 16:37:34 +0900295 sysprop::Scope classScope = sysprop::Internal;
296
297 for (int i = 0; i < props.prop_size(); ++i) {
298 // Get least restrictive scope among props. For example, if all props
299 // are internal, class can be as well internal. However, class should
300 // be public or system if at least one prop is so.
301 classScope = std::min(classScope, props.prop(i).scope());
302 }
303
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900304 std::string package_name = GetJavaPackageName(props);
305 std::string class_name = GetJavaClassName(props);
306
307 CodeWriter writer(kIndent);
308 writer.Write("%s", kGeneratedFileFooterComments);
309 writer.Write("package %s;\n\n", package_name.c_str());
310 writer.Write("%s", kJavaFileImports);
Inseob Kim1a58c042018-11-01 16:37:34 +0900311 WriteJavaAnnotation(writer, classScope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900312 writer.Write("public final class %s {\n", class_name.c_str());
313 writer.Indent();
314 writer.Write("private %s () {}\n\n", class_name.c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900315 writer.Write("%s", kJavaParsersAndFormatters);
316
317 for (int i = 0; i < props.prop_size(); ++i) {
318 writer.Write("\n");
319
320 const sysprop::Property& prop = props.prop(i);
321
Inseob Kim14e51872018-10-25 14:27:33 +0900322 std::string prop_id = ApiNameToIdentifier(prop.api_name()).c_str();
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900323 std::string prop_type = GetJavaTypeName(prop);
324
325 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
Inseob Kim1a58c042018-11-01 16:37:34 +0900326 if (prop.scope() != classScope) {
327 WriteJavaAnnotation(writer, prop.scope());
328 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900329 writer.Write("public static enum %s {\n",
330 GetJavaEnumTypeName(prop).c_str());
331 writer.Indent();
Inseob Kimf346e502019-01-04 10:32:39 +0900332 std::vector<std::string> values =
333 android::base::Split(prop.enum_values(), "|");
334 for (int i = 0; i < values.size(); ++i) {
335 const std::string& name = values[i];
336 writer.Write("%s(\"%s\")", ToUpper(name).c_str(), name.c_str());
337 if (i + 1 < values.size()) {
338 writer.Write(",\n");
339 } else {
340 writer.Write(";\n");
341 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900342 }
Inseob Kimf346e502019-01-04 10:32:39 +0900343 writer.Write(
344 "private final String propValue;\n"
345 "private %s(String propValue) {\n",
346 GetJavaEnumTypeName(prop).c_str());
347 writer.Indent();
348 writer.Write("this.propValue = propValue;\n");
349 writer.Dedent();
350 writer.Write(
351 "}\n"
352 "public String getPropValue() {\n");
353 writer.Indent();
354 writer.Write("return propValue;\n");
355 writer.Dedent();
356 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900357 writer.Dedent();
358 writer.Write("}\n\n");
359 }
360
Inseob Kim1a58c042018-11-01 16:37:34 +0900361 if (prop.scope() != classScope) {
362 WriteJavaAnnotation(writer, prop.scope());
363 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900364
Inseob Kime76e2722018-12-13 00:18:25 +0900365 if (IsListProp(prop)) {
366 writer.Write("public static %s %s() {\n", prop_type.c_str(),
367 prop_id.c_str());
368 writer.Indent();
369 writer.Write("String value = SystemProperties.get(\"%s\");\n",
370 prop.prop_name().c_str());
371 writer.Write("return %s;\n", GetParsingExpression(prop).c_str());
372 writer.Dedent();
373 writer.Write("}\n");
374 } else {
375 writer.Write("public static Optional<%s> %s() {\n", prop_type.c_str(),
376 prop_id.c_str());
377 writer.Indent();
378 writer.Write("String value = SystemProperties.get(\"%s\");\n",
379 prop.prop_name().c_str());
380 writer.Write("return Optional.ofNullable(%s);\n",
381 GetParsingExpression(prop).c_str());
382 writer.Dedent();
383 writer.Write("}\n");
384 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900385
Inseob Kim14e51872018-10-25 14:27:33 +0900386 if (prop.access() != sysprop::Readonly) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900387 writer.Write("\n");
Inseob Kim1a58c042018-11-01 16:37:34 +0900388 if (classScope != sysprop::Internal) {
389 WriteJavaAnnotation(writer, sysprop::Internal);
390 }
Inseob Kim0773b942018-10-04 19:29:27 +0900391 writer.Write("public static void %s(%s value) {\n", prop_id.c_str(),
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900392 prop_type.c_str());
393 writer.Indent();
Inseob Kimbd987ed2018-11-01 11:07:15 +0900394 writer.Write("SystemProperties.set(\"%s\", value == null ? \"\" : %s);\n",
Inseob Kim14e51872018-10-25 14:27:33 +0900395 prop.prop_name().c_str(),
Inseob Kimf346e502019-01-04 10:32:39 +0900396 GetFormattingExpression(prop).c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900397 writer.Dedent();
Inseob Kim14e51872018-10-25 14:27:33 +0900398 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900399 }
400 }
401
402 writer.Dedent();
403 writer.Write("}\n");
404
405 *java_result = writer.Code();
406 return true;
407}
408
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900409} // namespace
410
411bool GenerateJavaLibrary(const std::string& input_file_path,
Inseob Kim0773b942018-10-04 19:29:27 +0900412 const std::string& java_output_dir, std::string* err) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900413 sysprop::Properties props;
414
415 if (!ParseProps(input_file_path, &props, err)) {
416 return false;
417 }
418
Inseob Kim0773b942018-10-04 19:29:27 +0900419 std::string java_result;
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900420
421 if (!GenerateJavaClass(props, &java_result, err)) {
422 return false;
423 }
424
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900425 std::string package_name = GetJavaPackageName(props);
426 std::string java_package_dir =
427 java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/");
428
429 if (!IsDirectory(java_package_dir) && !CreateDirectories(java_package_dir)) {
430 *err = "Creating directory to " + java_package_dir +
431 " failed: " + strerror(errno);
432 return false;
433 }
434
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900435 std::string class_name = GetJavaClassName(props);
436 std::string java_output_file = java_package_dir + "/" + class_name + ".java";
437 if (!android::base::WriteStringToFile(java_result, java_output_file)) {
438 *err = "Writing generated java class to " + java_output_file +
439 " failed: " + strerror(errno);
440 return false;
441 }
442
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900443 return true;
444}