/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "sysprop_java_gen" #include "JavaGen.h" #include #include #include #include #include #include #include #include #include "CodeWriter.h" #include "Common.h" #include "sysprop.pb.h" using android::base::Result; namespace { constexpr const char* kIndent = " "; constexpr const char* kJavaFileImports = R"(import android.os.SystemProperties; import android.util.Log; import java.lang.StringBuilder; import java.util.ArrayList; import java.util.function.Function; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; )"; constexpr const char* kJavaParsersAndFormatters = R"s(private static Boolean tryParseBoolean(String str) { switch (str.toLowerCase(Locale.US)) { case "1": case "true": return Boolean.TRUE; case "0": case "false": return Boolean.FALSE; default: return null; } } private static Integer tryParseInteger(String str) { try { return Integer.valueOf(str); } catch (NumberFormatException e) { return null; } } private static Integer tryParseUInt(String str) { try { return Integer.parseUnsignedInt(str); } catch (NumberFormatException e) { return null; } } private static Long tryParseLong(String str) { try { return Long.valueOf(str); } catch (NumberFormatException e) { return null; } } private static Long tryParseULong(String str) { try { return Long.parseUnsignedLong(str); } catch (NumberFormatException e) { return null; } } private static Double tryParseDouble(String str) { try { return Double.valueOf(str); } catch (NumberFormatException e) { return null; } } private static String tryParseString(String str) { return "".equals(str) ? null : str; } private static > T tryParseEnum(Class enumType, String str) { try { return Enum.valueOf(enumType, str.toUpperCase(Locale.US)); } catch (IllegalArgumentException e) { return null; } } private static List tryParseList(Function elementParser, String str) { if ("".equals(str)) return new ArrayList<>(); List ret = new ArrayList<>(); int p = 0; for (;;) { StringBuilder sb = new StringBuilder(); while (p < str.length() && str.charAt(p) != ',') { if (str.charAt(p) == '\\') ++p; if (p == str.length()) break; sb.append(str.charAt(p++)); } ret.add(elementParser.apply(sb.toString())); if (p == str.length()) break; ++p; } return ret; } private static > List tryParseEnumList(Class enumType, String str) { if ("".equals(str)) return new ArrayList<>(); List ret = new ArrayList<>(); for (String element : str.split(",")) { ret.add(tryParseEnum(enumType, element)); } return ret; } private static String escape(String str) { return str.replaceAll("([\\\\,])", "\\\\$1"); } private static String formatList(List list) { StringJoiner joiner = new StringJoiner(","); for (T element : list) { joiner.add(element == null ? "" : escape(element.toString())); } return joiner.toString(); } private static String formatUIntList(List list) { StringJoiner joiner = new StringJoiner(","); for (Integer element : list) { joiner.add(element == null ? "" : escape(Integer.toUnsignedString(element))); } return joiner.toString(); } private static String formatULongList(List list) { StringJoiner joiner = new StringJoiner(","); for (Long element : list) { joiner.add(element == null ? "" : escape(Long.toUnsignedString(element))); } return joiner.toString(); } private static > String formatEnumList(List list, Function elementFormatter) { StringJoiner joiner = new StringJoiner(","); for (T element : list) { joiner.add(element == null ? "" : elementFormatter.apply(element)); } return joiner.toString(); } )s"; const std::regex kRegexDot{"\\."}; const std::regex kRegexUnderscore{"_"}; std::string GetJavaTypeName(const sysprop::Property& prop); std::string GetJavaEnumTypeName(const sysprop::Property& prop); std::string GetJavaPackageName(const sysprop::Properties& props); std::string GetJavaClassName(const sysprop::Properties& props); std::string GetParsingExpression(const sysprop::Property& prop); std::string GetFormattingExpression(const sysprop::Property& prop); std::string GenerateJavaClass(const sysprop::Properties& props, sysprop::Scope scope); std::string GetJavaEnumTypeName(const sysprop::Property& prop) { return ApiNameToIdentifier(prop.api_name()) + "_values"; } std::string GetJavaTypeName(const sysprop::Property& prop) { switch (prop.type()) { case sysprop::Boolean: return "Boolean"; case sysprop::Integer: case sysprop::UInt: return "Integer"; case sysprop::Long: case sysprop::ULong: return "Long"; case sysprop::Double: return "Double"; case sysprop::String: return "String"; case sysprop::Enum: return GetJavaEnumTypeName(prop); case sysprop::BooleanList: return "List"; case sysprop::IntegerList: case sysprop::UIntList: return "List"; case sysprop::LongList: case sysprop::ULongList: return "List"; case sysprop::DoubleList: return "List"; case sysprop::StringList: return "List"; case sysprop::EnumList: return "List<" + GetJavaEnumTypeName(prop) + ">"; default: __builtin_unreachable(); } } std::string GetParsingExpression(const sysprop::Property& prop) { switch (prop.type()) { case sysprop::Boolean: return "Optional.ofNullable(tryParseBoolean(value))"; case sysprop::Integer: return "Optional.ofNullable(tryParseInteger(value))"; case sysprop::UInt: return "Optional.ofNullable(tryParseUInt(value))"; case sysprop::Long: return "Optional.ofNullable(tryParseLong(value))"; case sysprop::ULong: return "Optional.ofNullable(tryParseULong(value))"; case sysprop::Double: return "Optional.ofNullable(tryParseDouble(value))"; case sysprop::String: return "Optional.ofNullable(tryParseString(value))"; case sysprop::Enum: return "Optional.ofNullable(tryParseEnum(" + GetJavaEnumTypeName(prop) + ".class, value))"; case sysprop::EnumList: return "tryParseEnumList(" + GetJavaEnumTypeName(prop) + ".class, " "value)"; default: break; } // The remaining cases are lists for types other than Enum which share the // same parsing function "tryParseList" std::string element_parser; switch (prop.type()) { case sysprop::BooleanList: element_parser = "v -> tryParseBoolean(v)"; break; case sysprop::IntegerList: element_parser = "v -> tryParseInteger(v)"; break; case sysprop::LongList: element_parser = "v -> tryParseLong(v)"; break; case sysprop::DoubleList: element_parser = "v -> tryParseDouble(v)"; break; case sysprop::StringList: element_parser = "v -> tryParseString(v)"; break; case sysprop::UIntList: element_parser = "v -> tryParseUInt(v)"; break; case sysprop::ULongList: element_parser = "v -> tryParseULong(v)"; break; default: __builtin_unreachable(); } return "tryParseList(" + element_parser + ", value)"; } std::string GetFormattingExpression(const sysprop::Property& prop) { if (prop.integer_as_bool()) { if (prop.type() == sysprop::Boolean) { // Boolean -> Integer String return "(value ? \"1\" : \"0\")"; } else { // List -> String directly return "value.stream().map(" "x -> x == null ? \"\" : (x ? \"1\" : \"0\"))" ".collect(Collectors.joining(\",\"))"; } } switch (prop.type()) { case sysprop::Enum: return "value.getPropValue()"; case sysprop::EnumList: return "formatEnumList(value, " + GetJavaEnumTypeName(prop) + "::getPropValue)"; case sysprop::UInt: return "Integer.toUnsignedString(value)"; case sysprop::ULong: return "Long.toUnsignedString(value)"; case sysprop::UIntList: return "formatUIntList(value)"; case sysprop::ULongList: return "formatULongList(value)"; default: break; } return IsListProp(prop) ? "formatList(value)" : "value.toString()"; } std::string GetJavaPackageName(const sysprop::Properties& props) { const std::string& module = props.module(); return module.substr(0, module.rfind('.')); } std::string GetJavaClassName(const sysprop::Properties& props) { const std::string& module = props.module(); return module.substr(module.rfind('.') + 1); } std::string GenerateJavaClass(const sysprop::Properties& props, sysprop::Scope scope) { std::string package_name = GetJavaPackageName(props); std::string class_name = GetJavaClassName(props); CodeWriter writer(kIndent); writer.Write("%s", kGeneratedFileFooterComments); writer.Write("package %s;\n\n", package_name.c_str()); writer.Write("%s", kJavaFileImports); writer.Write("public final class %s {\n", class_name.c_str()); writer.Indent(); writer.Write("private %s () {}\n\n", class_name.c_str()); writer.Write("%s", kJavaParsersAndFormatters); for (int i = 0; i < props.prop_size(); ++i) { const sysprop::Property& prop = props.prop(i); // skip if scope is internal and we are generating public class if (prop.scope() > scope) continue; writer.Write("\n"); std::string prop_id = ApiNameToIdentifier(prop.api_name()).c_str(); std::string prop_type = GetJavaTypeName(prop); if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) { writer.Write("public static enum %s {\n", GetJavaEnumTypeName(prop).c_str()); writer.Indent(); std::vector values = ParseEnumValues(prop.enum_values()); for (std::size_t i = 0; i < values.size(); ++i) { const std::string& name = values[i]; writer.Write("%s(\"%s\")", ToUpper(name).c_str(), name.c_str()); if (i + 1 < values.size()) { writer.Write(",\n"); } else { writer.Write(";\n"); } } writer.Write( "private final String propValue;\n" "private %s(String propValue) {\n", GetJavaEnumTypeName(prop).c_str()); writer.Indent(); writer.Write("this.propValue = propValue;\n"); writer.Dedent(); writer.Write( "}\n" "public String getPropValue() {\n"); writer.Indent(); writer.Write("return propValue;\n"); writer.Dedent(); writer.Write("}\n"); writer.Dedent(); writer.Write("}\n\n"); } if (prop.deprecated()) { writer.Write("@Deprecated\n"); } if (IsListProp(prop)) { writer.Write("public static %s %s() {\n", prop_type.c_str(), prop_id.c_str()); } else { writer.Write("public static Optional<%s> %s() {\n", prop_type.c_str(), prop_id.c_str()); } writer.Indent(); writer.Write("String value = SystemProperties.get(\"%s\");\n", prop.prop_name().c_str()); if (!prop.legacy_prop_name().empty()) { // SystemProperties.get() returns "" (empty string) when the property // doesn't exist writer.Write("if (\"\".equals(value)) {\n"); writer.Indent(); writer.Write( "Log.v(\"%s\", \"prop %s doesn't exist; fallback to legacy prop " "%s\");\n", class_name.c_str(), prop.prop_name().c_str(), prop.legacy_prop_name().c_str()); writer.Write("value = SystemProperties.get(\"%s\");\n", prop.legacy_prop_name().c_str()); writer.Dedent(); writer.Write("}\n"); } writer.Write("return %s;\n", GetParsingExpression(prop).c_str()); writer.Dedent(); writer.Write("}\n"); if (prop.access() != sysprop::Readonly) { writer.Write("\n"); if (prop.deprecated()) { writer.Write("@Deprecated\n"); } writer.Write("public static void %s(%s value) {\n", prop_id.c_str(), prop_type.c_str()); writer.Indent(); writer.Write("SystemProperties.set(\"%s\", value == null ? \"\" : %s);\n", prop.prop_name().c_str(), GetFormattingExpression(prop).c_str()); writer.Dedent(); writer.Write("}\n"); } } writer.Dedent(); writer.Write("}\n"); return writer.Code(); } } // namespace Result GenerateJavaLibrary(const std::string& input_file_path, sysprop::Scope scope, const std::string& java_output_dir) { sysprop::Properties props; if (auto res = ParseProps(input_file_path); res.ok()) { props = std::move(*res); } else { return res.error(); } std::string java_result = GenerateJavaClass(props, scope); std::string package_name = GetJavaPackageName(props); std::string java_package_dir = java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/"); std::error_code ec; std::filesystem::create_directories(java_package_dir, ec); if (ec) { return Errorf("Creating directory to {} failed: {}", java_package_dir, ec.message()); } std::string class_name = GetJavaClassName(props); std::string java_output_file = java_package_dir + "/" + class_name + ".java"; if (!android::base::WriteStringToFile(java_result, java_output_file)) { return ErrnoErrorf("Writing generated java class to {} failed", java_output_file); } return {}; }