blob: 99c8978ab76b3ee6d6c1a402b418541c64b3fa17 [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;
39
Inseob Kim0773b942018-10-04 19:29:27 +090040import android.os.SystemProperties;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090041import java.util.ArrayList;
42import java.util.function.Function;
43import java.util.List;
Todd Kennedy93d233e2019-07-17 12:56:11 -070044import java.util.Locale;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090045import java.util.Optional;
46import java.util.StringJoiner;
Inseob Kim9c5147d2019-03-06 11:36:36 +090047import java.util.stream.Collectors;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090048
49)";
50
51constexpr const char* kJavaParsersAndFormatters =
52 R"(private static Boolean tryParseBoolean(String str) {
Todd Kennedy93d233e2019-07-17 12:56:11 -070053 switch (str.toLowerCase(Locale.US)) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +090054 case "1":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090055 case "true":
56 return Boolean.TRUE;
57 case "0":
Inseob Kim5f8f32c2018-08-24 11:10:44 +090058 case "false":
59 return Boolean.FALSE;
60 default:
61 return null;
62 }
63}
64
65private static Integer tryParseInteger(String str) {
66 try {
67 return Integer.valueOf(str);
68 } catch (NumberFormatException e) {
69 return null;
70 }
71}
72
73private static Long tryParseLong(String str) {
74 try {
75 return Long.valueOf(str);
76 } catch (NumberFormatException e) {
77 return null;
78 }
79}
80
81private static Double tryParseDouble(String str) {
82 try {
83 return Double.valueOf(str);
84 } catch (NumberFormatException e) {
85 return null;
86 }
87}
88
89private static String tryParseString(String str) {
Inseob Kime76e2722018-12-13 00:18:25 +090090 return "".equals(str) ? null : str;
Inseob Kim5f8f32c2018-08-24 11:10:44 +090091}
92
93private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
94 try {
Todd Kennedy93d233e2019-07-17 12:56:11 -070095 return Enum.valueOf(enumType, str.toUpperCase(Locale.US));
Inseob Kim5f8f32c2018-08-24 11:10:44 +090096 } catch (IllegalArgumentException e) {
97 return null;
98 }
99}
100
101private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900102 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900103
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900104 List<T> ret = new ArrayList<>();
105
106 for (String element : str.split(",")) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900107 ret.add(elementParser.apply(element));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900108 }
109
110 return ret;
111}
112
113private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
Inseob Kime76e2722018-12-13 00:18:25 +0900114 if ("".equals(str)) return new ArrayList<>();
Inseob Kim4c474eb2018-11-27 12:17:21 +0900115
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900116 List<T> ret = new ArrayList<>();
117
118 for (String element : str.split(",")) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900119 ret.add(tryParseEnum(enumType, element));
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900120 }
121
122 return ret;
123}
124
125private static <T> String formatList(List<T> list) {
126 StringJoiner joiner = new StringJoiner(",");
127
128 for (T element : list) {
Inseob Kim4c474eb2018-11-27 12:17:21 +0900129 joiner.add(element == null ? "" : element.toString());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900130 }
131
132 return joiner.toString();
133}
Inseob Kimf346e502019-01-04 10:32:39 +0900134
135private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
136 StringJoiner joiner = new StringJoiner(",");
137
138 for (T element : list) {
139 joiner.add(element == null ? "" : elementFormatter.apply(element));
140 }
141
142 return joiner.toString();
143}
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900144)";
145
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900146const std::regex kRegexDot{"\\."};
147const std::regex kRegexUnderscore{"_"};
148
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900149std::string GetJavaTypeName(const sysprop::Property& prop);
150std::string GetJavaEnumTypeName(const sysprop::Property& prop);
151std::string GetJavaPackageName(const sysprop::Properties& props);
152std::string GetJavaClassName(const sysprop::Properties& props);
Inseob Kimf346e502019-01-04 10:32:39 +0900153std::string GetParsingExpression(const sysprop::Property& prop);
154std::string GetFormattingExpression(const sysprop::Property& prop);
Inseob Kim14e51872018-10-25 14:27:33 +0900155void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900156bool GenerateJavaClass(const sysprop::Properties& props,
157 std::string* java_result, std::string* err);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900158
159std::string GetJavaEnumTypeName(const sysprop::Property& prop) {
Inseob Kim14e51872018-10-25 14:27:33 +0900160 return ApiNameToIdentifier(prop.api_name()) + "_values";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900161}
162
163std::string GetJavaTypeName(const sysprop::Property& prop) {
164 switch (prop.type()) {
165 case sysprop::Boolean:
166 return "Boolean";
167 case sysprop::Integer:
168 return "Integer";
169 case sysprop::Long:
170 return "Long";
171 case sysprop::Double:
172 return "Double";
173 case sysprop::String:
174 return "String";
175 case sysprop::Enum:
176 return GetJavaEnumTypeName(prop);
177 case sysprop::BooleanList:
178 return "List<Boolean>";
179 case sysprop::IntegerList:
180 return "List<Integer>";
181 case sysprop::LongList:
182 return "List<Long>";
183 case sysprop::DoubleList:
184 return "List<Double>";
185 case sysprop::StringList:
186 return "List<String>";
187 case sysprop::EnumList:
188 return "List<" + GetJavaEnumTypeName(prop) + ">";
189 default:
190 __builtin_unreachable();
191 }
192}
193
194std::string GetParsingExpression(const sysprop::Property& prop) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900195 switch (prop.type()) {
196 case sysprop::Boolean:
Inseob Kim0773b942018-10-04 19:29:27 +0900197 return "tryParseBoolean(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900198 case sysprop::Integer:
Inseob Kim0773b942018-10-04 19:29:27 +0900199 return "tryParseInteger(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900200 case sysprop::Long:
Inseob Kim0773b942018-10-04 19:29:27 +0900201 return "tryParseLong(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900202 case sysprop::Double:
Inseob Kim0773b942018-10-04 19:29:27 +0900203 return "tryParseDouble(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900204 case sysprop::String:
Inseob Kim0773b942018-10-04 19:29:27 +0900205 return "tryParseString(value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900206 case sysprop::Enum:
Inseob Kim0773b942018-10-04 19:29:27 +0900207 return "tryParseEnum(" + GetJavaEnumTypeName(prop) + ".class, value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900208 case sysprop::EnumList:
Inseob Kim0773b942018-10-04 19:29:27 +0900209 return "tryParseEnumList(" + GetJavaEnumTypeName(prop) +
210 ".class, "
211 "value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900212 default:
213 break;
214 }
215
216 // The remaining cases are lists for types other than Enum which share the
217 // same parsing function "tryParseList"
218 std::string element_parser;
219
220 switch (prop.type()) {
221 case sysprop::BooleanList:
222 element_parser = "v -> tryParseBoolean(v)";
223 break;
224 case sysprop::IntegerList:
225 element_parser = "v -> tryParseInteger(v)";
226 break;
227 case sysprop::LongList:
228 element_parser = "v -> tryParseLong(v)";
229 break;
230 case sysprop::DoubleList:
231 element_parser = "v -> tryParseDouble(v)";
232 break;
233 case sysprop::StringList:
234 element_parser = "v -> tryParseString(v)";
235 break;
236 default:
237 __builtin_unreachable();
238 }
239
Inseob Kim0773b942018-10-04 19:29:27 +0900240 return "tryParseList(" + element_parser + ", value)";
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900241}
242
Inseob Kimf346e502019-01-04 10:32:39 +0900243std::string GetFormattingExpression(const sysprop::Property& prop) {
Inseob Kim9c5147d2019-03-06 11:36:36 +0900244 if (prop.integer_as_bool()) {
245 if (prop.type() == sysprop::Boolean) {
246 // Boolean -> Integer String
247 return "(value ? \"1\" : \"0\")";
248 } else {
249 // List<Boolean> -> String directly
250 return "value.stream().map("
251 "x -> x == null ? \"\" : (x ? \"1\" : \"0\"))"
252 ".collect(Collectors.joining(\",\"))";
253 }
254 } else if (prop.type() == sysprop::Enum) {
Inseob Kimf346e502019-01-04 10:32:39 +0900255 return "value.getPropValue()";
256 } else if (prop.type() == sysprop::EnumList) {
257 return "formatEnumList(value, " + GetJavaEnumTypeName(prop) +
258 "::getPropValue)";
259 } else if (IsListProp(prop)) {
260 return "formatList(value)";
261 } else {
262 return "value.toString()";
263 }
264}
265
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900266std::string GetJavaPackageName(const sysprop::Properties& props) {
267 const std::string& module = props.module();
268 return module.substr(0, module.rfind('.'));
269}
270
271std::string GetJavaClassName(const sysprop::Properties& props) {
272 const std::string& module = props.module();
273 return module.substr(module.rfind('.') + 1);
274}
275
Inseob Kim14e51872018-10-25 14:27:33 +0900276void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope) {
277 switch (scope) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900278 case sysprop::System:
Inseob Kimce4f0462018-12-26 14:36:59 +0900279 writer.Write("/** @hide */\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900280 writer.Write("@SystemApi\n");
281 break;
282 case sysprop::Internal:
283 writer.Write("/** @hide */\n");
284 break;
285 default:
286 break;
287 }
288}
289
290bool GenerateJavaClass(const sysprop::Properties& props,
291 std::string* java_result,
292 [[maybe_unused]] std::string* err) {
Inseob Kim1a58c042018-11-01 16:37:34 +0900293 sysprop::Scope classScope = sysprop::Internal;
294
295 for (int i = 0; i < props.prop_size(); ++i) {
296 // Get least restrictive scope among props. For example, if all props
297 // are internal, class can be as well internal. However, class should
298 // be public or system if at least one prop is so.
299 classScope = std::min(classScope, props.prop(i).scope());
300 }
301
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900302 std::string package_name = GetJavaPackageName(props);
303 std::string class_name = GetJavaClassName(props);
304
305 CodeWriter writer(kIndent);
306 writer.Write("%s", kGeneratedFileFooterComments);
307 writer.Write("package %s;\n\n", package_name.c_str());
308 writer.Write("%s", kJavaFileImports);
Inseob Kim1a58c042018-11-01 16:37:34 +0900309 WriteJavaAnnotation(writer, classScope);
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900310 writer.Write("public final class %s {\n", class_name.c_str());
311 writer.Indent();
312 writer.Write("private %s () {}\n\n", class_name.c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900313 writer.Write("%s", kJavaParsersAndFormatters);
314
315 for (int i = 0; i < props.prop_size(); ++i) {
316 writer.Write("\n");
317
318 const sysprop::Property& prop = props.prop(i);
319
Inseob Kim14e51872018-10-25 14:27:33 +0900320 std::string prop_id = ApiNameToIdentifier(prop.api_name()).c_str();
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900321 std::string prop_type = GetJavaTypeName(prop);
322
323 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
Inseob Kim1a58c042018-11-01 16:37:34 +0900324 if (prop.scope() != classScope) {
325 WriteJavaAnnotation(writer, prop.scope());
326 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900327 writer.Write("public static enum %s {\n",
328 GetJavaEnumTypeName(prop).c_str());
329 writer.Indent();
Inseob Kimf346e502019-01-04 10:32:39 +0900330 std::vector<std::string> values =
331 android::base::Split(prop.enum_values(), "|");
332 for (int i = 0; i < values.size(); ++i) {
333 const std::string& name = values[i];
334 writer.Write("%s(\"%s\")", ToUpper(name).c_str(), name.c_str());
335 if (i + 1 < values.size()) {
336 writer.Write(",\n");
337 } else {
338 writer.Write(";\n");
339 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900340 }
Inseob Kimf346e502019-01-04 10:32:39 +0900341 writer.Write(
342 "private final String propValue;\n"
343 "private %s(String propValue) {\n",
344 GetJavaEnumTypeName(prop).c_str());
345 writer.Indent();
346 writer.Write("this.propValue = propValue;\n");
347 writer.Dedent();
348 writer.Write(
349 "}\n"
350 "public String getPropValue() {\n");
351 writer.Indent();
352 writer.Write("return propValue;\n");
353 writer.Dedent();
354 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900355 writer.Dedent();
356 writer.Write("}\n\n");
357 }
358
Inseob Kim1a58c042018-11-01 16:37:34 +0900359 if (prop.scope() != classScope) {
360 WriteJavaAnnotation(writer, prop.scope());
361 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900362
Inseob Kime76e2722018-12-13 00:18:25 +0900363 if (IsListProp(prop)) {
364 writer.Write("public static %s %s() {\n", prop_type.c_str(),
365 prop_id.c_str());
366 writer.Indent();
367 writer.Write("String value = SystemProperties.get(\"%s\");\n",
368 prop.prop_name().c_str());
369 writer.Write("return %s;\n", GetParsingExpression(prop).c_str());
370 writer.Dedent();
371 writer.Write("}\n");
372 } else {
373 writer.Write("public static Optional<%s> %s() {\n", prop_type.c_str(),
374 prop_id.c_str());
375 writer.Indent();
376 writer.Write("String value = SystemProperties.get(\"%s\");\n",
377 prop.prop_name().c_str());
378 writer.Write("return Optional.ofNullable(%s);\n",
379 GetParsingExpression(prop).c_str());
380 writer.Dedent();
381 writer.Write("}\n");
382 }
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900383
Inseob Kim14e51872018-10-25 14:27:33 +0900384 if (prop.access() != sysprop::Readonly) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900385 writer.Write("\n");
Inseob Kim1a58c042018-11-01 16:37:34 +0900386 if (classScope != sysprop::Internal) {
387 WriteJavaAnnotation(writer, sysprop::Internal);
388 }
Inseob Kim0773b942018-10-04 19:29:27 +0900389 writer.Write("public static void %s(%s value) {\n", prop_id.c_str(),
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900390 prop_type.c_str());
391 writer.Indent();
Inseob Kimbd987ed2018-11-01 11:07:15 +0900392 writer.Write("SystemProperties.set(\"%s\", value == null ? \"\" : %s);\n",
Inseob Kim14e51872018-10-25 14:27:33 +0900393 prop.prop_name().c_str(),
Inseob Kimf346e502019-01-04 10:32:39 +0900394 GetFormattingExpression(prop).c_str());
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900395 writer.Dedent();
Inseob Kim14e51872018-10-25 14:27:33 +0900396 writer.Write("}\n");
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900397 }
398 }
399
400 writer.Dedent();
401 writer.Write("}\n");
402
403 *java_result = writer.Code();
404 return true;
405}
406
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900407} // namespace
408
409bool GenerateJavaLibrary(const std::string& input_file_path,
Inseob Kim0773b942018-10-04 19:29:27 +0900410 const std::string& java_output_dir, std::string* err) {
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900411 sysprop::Properties props;
412
413 if (!ParseProps(input_file_path, &props, err)) {
414 return false;
415 }
416
Inseob Kim0773b942018-10-04 19:29:27 +0900417 std::string java_result;
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900418
419 if (!GenerateJavaClass(props, &java_result, err)) {
420 return false;
421 }
422
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900423 std::string package_name = GetJavaPackageName(props);
424 std::string java_package_dir =
425 java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/");
426
427 if (!IsDirectory(java_package_dir) && !CreateDirectories(java_package_dir)) {
428 *err = "Creating directory to " + java_package_dir +
429 " failed: " + strerror(errno);
430 return false;
431 }
432
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900433 std::string class_name = GetJavaClassName(props);
434 std::string java_output_file = java_package_dir + "/" + class_name + ".java";
435 if (!android::base::WriteStringToFile(java_result, java_output_file)) {
436 *err = "Writing generated java class to " + java_output_file +
437 " failed: " + strerror(errno);
438 return false;
439 }
440
Inseob Kim5f8f32c2018-08-24 11:10:44 +0900441 return true;
442}