Generate C++/Java sysprop library

This tool will generate C++ and Java libraries from sysprop
descrption files. The generated libraries will become API, and
direct use of existing API for system property will only remain as
internal API for generated libraries.

Bug: 80125326
Test: mma -j
Change-Id: Id320819bead7c8bbdb1e818bd5b838b1e7afb70f
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2b83a1f
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,11 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+PointerAlignment: Left
+TabWidth: 2
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..0fac61f
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+cc_defaults {
+    name: "sysprop-defaults",
+    srcs: ["sysprop.proto", "Common.cpp", "CodeWriter.cpp"],
+    cpp_std: "c++17",
+    shared_libs: ["libprotobuf-cpp-full", "libbase", "liblog"],
+}
+
+cc_binary_host {
+    name: "sysprop_cpp",
+    defaults: ["sysprop-defaults"],
+    srcs: ["CppGen.cpp", "CppMain.cpp"],
+}
+
+cc_binary_host {
+    name: "sysprop_java",
+    defaults: ["sysprop-defaults"],
+    srcs: ["JavaGen.cpp", "JavaMain.cpp"],
+}
diff --git a/CodeWriter.cpp b/CodeWriter.cpp
new file mode 100644
index 0000000..62b7330
--- /dev/null
+++ b/CodeWriter.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include "CodeWriter.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "sysprop_gen"
+
+#include <cstdarg>
+#include <cstdio>
+#include <vector>
+
+#include <android-base/logging.h>
+
+CodeWriter::CodeWriter(std::string indent) : indent_(std::move(indent)) {
+}
+
+void CodeWriter::Write(const char* format, ...) {
+  va_list ap, apc;
+  va_start(ap, format);
+  va_copy(apc, ap);
+
+  int size = std::vsnprintf(nullptr, 0, format, ap);
+  va_end(ap);
+
+  if (size < 0) {
+    va_end(apc);
+    PLOG(FATAL) << "vsnprintf failed";
+  }
+
+  std::vector<char> buf(size + 1);
+  if (std::vsnprintf(buf.data(), size + 1, format, apc) < 0) {
+    va_end(apc);
+    PLOG(FATAL) << "vsnprintf failed";
+  }
+  va_end(apc);
+
+  for (int i = 0; i < size; ++i) {
+    char ch = buf[i];
+    if (ch == '\n') {
+      start_of_line_ = true;
+    } else {
+      if (start_of_line_) {
+        for (int j = 0; j < indent_level_; ++j) {
+          code_ += indent_;
+        }
+        start_of_line_ = false;
+      }
+    }
+    code_.push_back(ch);
+  }
+}
+
+void CodeWriter::Indent() {
+  ++indent_level_;
+}
+
+void CodeWriter::Dedent() {
+  if (indent_level_ == 0) {
+    LOG(FATAL) << "Dedent failed: indent level is already 0";
+  }
+  --indent_level_;
+}
diff --git a/CodeWriter.h b/CodeWriter.h
new file mode 100644
index 0000000..8cff38c
--- /dev/null
+++ b/CodeWriter.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_TOOLS_SYSPROP_CODE_WRITER_H_
+#define SYSTEM_TOOLS_SYSPROP_CODE_WRITER_H_
+
+#include <string>
+
+class CodeWriter {
+ public:
+  explicit CodeWriter(std::string indent);
+
+  void Write(const char* format, ...) __attribute__((format(__printf__, 2, 3)));
+
+  void Indent();
+  void Dedent();
+
+  const std::string& Code() const {
+    return code_;
+  }
+
+ private:
+  CodeWriter(const CodeWriter&) = delete;
+  CodeWriter& operator=(const CodeWriter&) = delete;
+
+  int indent_level_ = 0;
+  bool start_of_line_ = true;
+  std::string code_;
+  const std::string indent_;
+};
+
+#endif  // SYSTEM_TOOLS_SYSPROP_CODE_WRITER_H_
diff --git a/Common.cpp b/Common.cpp
new file mode 100644
index 0000000..f3dc2ae
--- /dev/null
+++ b/Common.cpp
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+#include "Common.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cctype>
+#include <cerrno>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
+#include <memory>
+#include <regex>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <google/protobuf/text_format.h>
+
+#include "sysprop.pb.h"
+
+namespace {
+
+bool IsCorrectIdentifier(const std::string& name);
+bool IsCorrectPropertyName(const std::string& name);
+bool ValidateProp(const sysprop::Properties& props,
+                  const sysprop::Property& prop, std::string* err);
+bool ValidateProps(const sysprop::Properties& props, std::string* err);
+
+bool IsCorrectIdentifier(const std::string& name) {
+  if (name.empty()) return false;
+  if (std::isalpha(name[0]) == 0 && name[0] != '_') return false;
+
+  for (size_t i = 1; i < name.size(); ++i) {
+    if (std::isalpha(name[i]) || name[i] == '_') continue;
+    if (std::isdigit(name[i])) continue;
+
+    return false;
+  }
+
+  return true;
+}
+
+bool IsCorrectPropertyName(const std::string& name) {
+  if (name.empty()) return false;
+
+  for (const std::string& token : android::base::Split(name, ".")) {
+    if (!IsCorrectIdentifier(token)) return false;
+  }
+
+  return true;
+}
+
+bool ValidateProp(const sysprop::Properties& props,
+                  const sysprop::Property& prop, std::string* err) {
+  if (!IsCorrectPropertyName(prop.name())) {
+    if (err) *err = "Invalid prop name \"" + prop.name() + "\"";
+    return false;
+  }
+
+  if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+    std::vector<std::string> names =
+        android::base::Split(prop.enum_values(), "|");
+    if (names.empty()) {
+      if (err) *err = "Enum values are empty for prop \"" + prop.name() + "\"";
+      return false;
+    }
+
+    for (const std::string& name : names) {
+      if (!IsCorrectIdentifier(name)) {
+        if (err)
+          *err = "Invalid enum value \"" + name + "\" for prop \"" +
+                 prop.name() + "\"";
+        return false;
+      }
+    }
+
+    std::unordered_set<std::string> name_set;
+    for (const std::string& name : names) {
+      if (!name_set.insert(name).second) {
+        if (err)
+          *err = "Duplicated enum value \"" + name + "\" for prop \"" +
+                 prop.name() + "\"";
+        return false;
+      }
+    }
+  }
+
+  if (props.owner() == sysprop::Platform) {
+    std::string full_name = props.prefix() + prop.name();
+    if (android::base::StartsWith(full_name, "vendor.") ||
+        android::base::StartsWith(full_name, "odm.")) {
+      if (err)
+        *err = "Prop \"" + prop.name() +
+               "\" owned by platform cannot have vendor. or oem. namespace";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool ValidateProps(const sysprop::Properties& props, std::string* err) {
+  std::vector<std::string> names = android::base::Split(props.module(), ".");
+  if (names.size() <= 1) {
+    if (err) *err = "Invalid module name \"" + props.module() + "\"";
+    return false;
+  }
+
+  for (const auto& name : names) {
+    if (!IsCorrectIdentifier(name)) {
+      if (err) *err = "Invalid name \"" + name + "\" in module";
+      return false;
+    }
+  }
+
+  if (!props.prefix().empty() && !IsCorrectPropertyName(props.prefix())) {
+    if (err) *err = "Invalid prefix \"" + props.prefix() + "\"";
+    return false;
+  }
+
+  if (props.prop_size() == 0) {
+    if (err) *err = "There is no defined property";
+    return false;
+  }
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const auto& prop = props.prop(i);
+    if (!ValidateProp(props, prop, err)) return false;
+  }
+
+  std::unordered_set<std::string> prop_names;
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const auto& prop = props.prop(i);
+    auto res = prop_names.insert(PropNameToIdentifier(prop.name()));
+
+    if (!res.second) {
+      if (err) *err = "Duplicated prop name \"" + prop.name() + "\"";
+      return false;
+    }
+  }
+
+  if (props.owner() == sysprop::Platform) {
+    if (props.module() != "android.os.PlatformProperties") {
+      if (err)
+        *err =
+            "Platform-defined properties should have "
+            "\"android.os.PlatformProperties\" as module name";
+      return false;
+    }
+  } else {
+    if (props.module() == "android.os.PlatformProperties") {
+      if (err)
+        *err =
+            "Vendor or Odm cannot use \"android.os.PlatformProperties\" as "
+            "module name";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+// For directory functions, we could use <filesystem> of C++17 if supported..
+bool CreateDirectories(const std::string& path) {
+  struct stat st;
+
+  // If already exists..
+  if (stat(path.c_str(), &st) == 0) {
+    return false;
+  }
+
+  size_t last_slash = path.rfind('/');
+  if (last_slash > 0 && last_slash != std::string::npos) {
+    std::string parent = path.substr(0, last_slash);
+    if (!IsDirectory(parent) && !CreateDirectories(parent)) return false;
+  }
+
+  // It's very unlikely, but if path contains ".." or any symbolic links, it
+  // might already be created before this line.
+  return mkdir(path.c_str(), 0755) == 0 || IsDirectory(path);
+}
+
+bool IsDirectory(const std::string& path) {
+  struct stat st;
+
+  if (stat(path.c_str(), &st) == -1) return false;
+  return S_ISDIR(st.st_mode);
+}
+
+std::string GetModuleName(const sysprop::Properties& props) {
+  const std::string& module = props.module();
+  return module.substr(module.rfind('.') + 1);
+}
+
+bool ParseProps(const std::string& input_file_path, sysprop::Properties* props,
+                std::string* err) {
+  std::string file_contents;
+
+  if (!android::base::ReadFileToString(input_file_path, &file_contents, true)) {
+    *err = "Error reading file " + input_file_path + ": " + strerror(errno);
+    return false;
+  }
+
+  if (!google::protobuf::TextFormat::ParseFromString(file_contents, props)) {
+    *err = "Error parsing file " + input_file_path;
+    return false;
+  }
+
+  if (!ValidateProps(*props, err)) {
+    return false;
+  }
+
+  for (int i = 0; i < props->prop_size(); ++i) {
+    // set each optional field to its default value
+    sysprop::Property& prop = *props->mutable_prop(i);
+    if (!prop.has_readonly()) prop.set_readonly(true);
+  }
+
+  return true;
+}
+
+std::string PropNameToIdentifier(const std::string& name) {
+  static const std::regex kRegexDot{"\\."};
+  return std::regex_replace(name, kRegexDot, "_");
+}
diff --git a/Common.h b/Common.h
new file mode 100644
index 0000000..8963a57
--- /dev/null
+++ b/Common.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_TOOLS_SYSPROP_COMMON_H_
+#define SYSTEM_TOOLS_SYSPROP_COMMON_H_
+
+#include <string>
+#include "sysprop.pb.h"
+
+inline static constexpr const char* kGeneratedFileFooterComments =
+    "// Generated by the sysprop generator. DO NOT EDIT!\n\n";
+
+bool CreateDirectories(const std::string& path);
+std::string GetModuleName(const sysprop::Properties& props);
+bool IsDirectory(const std::string& path);
+bool ParseProps(const std::string& file_path, sysprop::Properties* props,
+                std::string* err);
+std::string PropNameToIdentifier(const std::string& name);
+
+#endif  // SYSTEM_TOOLS_SYSPROP_COMMON_H_
diff --git a/CppGen.cpp b/CppGen.cpp
new file mode 100644
index 0000000..cb0da78
--- /dev/null
+++ b/CppGen.cpp
@@ -0,0 +1,490 @@
+/*
+ * 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_cpp_gen"
+
+#include "CppGen.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cerrno>
+#include <regex>
+#include <string>
+
+#include "CodeWriter.h"
+#include "Common.h"
+#include "sysprop.pb.h"
+
+namespace {
+
+constexpr const char* kIndent = "    ";
+
+constexpr const char* kCppHeaderIncludes =
+    R"(#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+)";
+
+constexpr const char* kCppSourceIncludes =
+    R"(#include <cstring>
+#include <iterator>
+#include <utility>
+
+#include <dlfcn.h>
+#include <strings.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+)";
+
+constexpr const char* kCppParsersAndFormatters =
+    R"(template <typename T> constexpr bool is_vector = false;
+
+template <typename T> constexpr bool is_vector<std::vector<T>> = true;
+
+template <typename T> std::optional<T> DoParse(const char* str);
+
+template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) {
+    static constexpr const char* kYes[] = {"1", "on", "true", "y", "yes"};
+    static constexpr const char* kNo[] = {"0", "off", "false", "n", "no"};
+
+    for (const char* yes : kYes) {
+        if (strcasecmp(yes, str) == 0) return std::make_optional(true);
+    }
+
+    for (const char* no : kNo) {
+        if (strcasecmp(no, str) == 0) return std::make_optional(false);
+    }
+
+    return std::nullopt;
+}
+
+template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) {
+    std::int32_t ret;
+    bool success = android::base::ParseInt(str, &ret);
+    return success ? std::make_optional(ret) : std::nullopt;
+}
+
+template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) {
+    std::int64_t ret;
+    bool success = android::base::ParseInt(str, &ret);
+    return success ? std::make_optional(ret) : std::nullopt;
+}
+
+template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) {
+    int old_errno = errno;
+    errno = 0;
+    char* end;
+    double ret = std::strtod(str, &end);
+    if (errno != 0) {
+        return std::nullopt;
+    }
+    if (str == end || *end != '\0') {
+        errno = old_errno;
+        return std::nullopt;
+    }
+    errno = old_errno;
+    return std::make_optional(ret);
+}
+
+template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) {
+    return std::make_optional(str);
+}
+
+template <typename Vec> [[maybe_unused]] std::optional<Vec> DoParseList(const char* str) {
+    Vec ret;
+    for (auto&& element : android::base::Split(str, ",")) {
+        auto parsed = DoParse<typename Vec::value_type>(element.c_str());
+        if (!parsed) {
+            return std::nullopt;
+        }
+        ret.emplace_back(std::move(*parsed));
+    }
+    return std::make_optional(std::move(ret));
+}
+
+template <typename T> inline std::optional<T> TryParse(const char* str) {
+    if constexpr(is_vector<T>) {
+        return DoParseList<T>(str);
+    } else {
+        return DoParse<T>(str);
+    }
+}
+
+[[maybe_unused]] std::string FormatValue(std::int32_t value) {
+    return std::to_string(value);
+}
+
+[[maybe_unused]] std::string FormatValue(std::int64_t value) {
+    return std::to_string(value);
+}
+
+[[maybe_unused]] std::string FormatValue(double value) {
+    return android::base::StringPrintf("%.*g", std::numeric_limits<double>::max_digits10, value);
+}
+
+[[maybe_unused]] std::string FormatValue(bool value) {
+    return value ? "true" : "false";
+}
+
+[[maybe_unused]] std::string FormatValue(std::string value) {
+    return value;
+}
+
+template <typename T>
+[[maybe_unused]] std::string FormatValue(const std::vector<T>& value) {
+    if (value.empty()) return "";
+
+    std::string ret;
+
+    for (auto&& element : value) {
+        if (ret.empty()) ret.push_back(',');
+        ret += FormatValue(element);
+    }
+
+    return ret;
+}
+
+namespace libc {
+
+struct prop_info;
+
+const prop_info* (*system_property_find)(const char* name);
+
+void (*system_property_read_callback)(
+    const prop_info* pi,
+    void (*callback)(void* cookie, const char* name, const char* value, std::uint32_t serial),
+    void* cookie
+);
+
+int (*system_property_set)(const char* key, const char* value);
+
+void* handle;
+
+__attribute__((constructor)) void load_libc_functions() {
+    handle = dlopen("libc.so", RTLD_LAZY | RTLD_NOLOAD);
+
+    system_property_find = reinterpret_cast<decltype(system_property_find)>(dlsym(handle, "__system_property_find"));
+    system_property_read_callback = reinterpret_cast<decltype(system_property_read_callback)>(dlsym(handle, "__system_property_read_callback"));
+    system_property_set = reinterpret_cast<decltype(system_property_set)>(dlsym(handle, "__system_property_set"));
+}
+
+__attribute__((destructor)) void release_libc_functions() {
+    dlclose(handle);
+}
+
+template <typename T>
+std::optional<T> GetProp(const char* key) {
+    auto pi = system_property_find(key);
+    if (pi == nullptr) return std::nullopt;
+    std::optional<T> ret;
+    system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
+        *static_cast<std::optional<T>*>(cookie) = TryParse<T>(value);
+    }, &ret);
+    return ret;
+}
+
+}  // namespace libc
+
+)";
+
+const std::regex kRegexDot{"\\."};
+const std::regex kRegexUnderscore{"_"};
+
+std::string GetHeaderIncludeGuardName(const sysprop::Properties& props);
+std::string GetCppEnumName(const sysprop::Property& prop);
+std::string GetCppPropTypeName(const sysprop::Property& prop);
+std::string GetCppNamespace(const sysprop::Properties& props);
+
+bool GenerateHeader(const sysprop::Properties& props,
+                    std::string* header_result, std::string* err);
+bool GenerateSource(const sysprop::Properties& props,
+                    std::string* source_result, std::string* err);
+
+std::string GetHeaderIncludeGuardName(const sysprop::Properties& props) {
+  return "SYSPROPGEN_" + std::regex_replace(props.module(), kRegexDot, "_") +
+         "_H_";
+}
+
+std::string GetCppEnumName(const sysprop::Property& prop) {
+  return PropNameToIdentifier(prop.name()) + "_values";
+}
+
+std::string GetCppPropTypeName(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "bool";
+    case sysprop::Integer:
+      return "std::int32_t";
+    case sysprop::Long:
+      return "std::int64_t";
+    case sysprop::Double:
+      return "double";
+    case sysprop::String:
+      return "std::string";
+    case sysprop::Enum:
+      return GetCppEnumName(prop);
+    case sysprop::BooleanList:
+      return "std::vector<bool>";
+    case sysprop::IntegerList:
+      return "std::vector<std::int32_t>";
+    case sysprop::LongList:
+      return "std::vector<std::int64_t>";
+    case sysprop::DoubleList:
+      return "std::vector<double>";
+    case sysprop::StringList:
+      return "std::vector<std::string>";
+    case sysprop::EnumList:
+      return "std::vector<" + GetCppEnumName(prop) + ">";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GetCppNamespace(const sysprop::Properties& props) {
+  return std::regex_replace(props.module(), kRegexDot, "::");
+}
+
+bool GenerateHeader(const sysprop::Properties& props, std::string* header_result,
+                    [[maybe_unused]] std::string* err) {
+  CodeWriter writer(kIndent);
+
+  writer.Write("%s", kGeneratedFileFooterComments);
+
+  std::string include_guard_name = GetHeaderIncludeGuardName(props);
+  writer.Write("#ifndef %s\n#define %s\n\n", include_guard_name.c_str(),
+               include_guard_name.c_str());
+  writer.Write("%s", kCppHeaderIncludes);
+
+  std::string cpp_namespace = GetCppNamespace(props);
+  writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    if (i > 0) writer.Write("\n");
+
+    const sysprop::Property& prop = props.prop(i);
+    std::string prop_id = PropNameToIdentifier(prop.name());
+    std::string prop_type = GetCppPropTypeName(prop);
+
+    if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+      writer.Write("enum class %s {\n", GetCppEnumName(prop).c_str());
+      writer.Indent();
+      for (const std::string& name :
+           android::base::Split(prop.enum_values(), "|")) {
+        writer.Write("%s,\n", name.c_str());
+      }
+      writer.Dedent();
+      writer.Write("};\n\n");
+    }
+
+    writer.Write("std::optional<%s> %s();\n", prop_type.c_str(),
+                 prop_id.c_str());
+    if (!prop.readonly()) {
+      writer.Write("bool %s(const %s& value);\n", prop_id.c_str(),
+                   prop_type.c_str());
+    }
+  }
+
+  writer.Write("\n}  // namespace %s\n\n", cpp_namespace.c_str());
+
+  writer.Write("#endif  // %s\n", include_guard_name.c_str());
+
+  *header_result = writer.Code();
+  return true;
+}
+
+bool GenerateSource(const sysprop::Properties& props, std::string* source_result,
+                    [[maybe_unused]] std::string* err) {
+  CodeWriter writer(kIndent);
+  writer.Write("%s", kGeneratedFileFooterComments);
+  writer.Write("#include \"%s.h\"\n\n", GetModuleName(props).c_str());
+  writer.Write("%s", kCppSourceIncludes);
+
+  writer.Write("namespace {\n\n");
+  writer.Write("%s", kCppParsersAndFormatters);
+
+  bool using_emitted = false;
+
+  std::string cpp_namespace = GetCppNamespace(props);
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const sysprop::Property& prop = props.prop(i);
+    if (prop.type() != sysprop::Enum && prop.type() != sysprop::EnumList) {
+      continue;
+    }
+
+    if (!using_emitted) {
+      writer.Write("using namespace %s;\n\n", cpp_namespace.c_str());
+      using_emitted = true;
+    }
+
+    std::string prop_id = PropNameToIdentifier(prop.name());
+    std::string prefix = (prop.readonly() ? "ro." : "") + props.prefix();
+    std::string enum_name = GetCppEnumName(prop);
+
+    writer.Write("constexpr const std::pair<const char*, %s> %s_list[] = {\n",
+                 enum_name.c_str(), prop_id.c_str());
+    writer.Indent();
+    for (const std::string& name :
+         android::base::Split(prop.enum_values(), "|")) {
+      writer.Write("{\"%s\", %s::%s},\n", name.c_str(), enum_name.c_str(),
+                   name.c_str());
+    }
+    writer.Dedent();
+    writer.Write("};\n\n");
+
+    writer.Write("template <>\n");
+    writer.Write("std::optional<%s> DoParse(const char* str) {\n",
+                 enum_name.c_str());
+    writer.Indent();
+    writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
+    writer.Indent();
+    writer.Write("if (strcmp(str, name) == 0) {\n");
+    writer.Indent();
+    writer.Write("return val;\n");
+    writer.Dedent();
+    writer.Write("}\n");
+    writer.Dedent();
+    writer.Write("}\n");
+    writer.Write("return std::nullopt;\n");
+    writer.Dedent();
+    writer.Write("}\n\n");
+
+    if (!prop.readonly()) {
+      writer.Write("std::string FormatValue(%s value) {\n", enum_name.c_str());
+      writer.Indent();
+      writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
+      writer.Indent();
+      writer.Write("if (val == value) {\n");
+      writer.Indent();
+      writer.Write("return name;\n");
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Write(
+          "LOG(FATAL) << \"Invalid value \" << "
+          "static_cast<std::int32_t>(value) << "
+          "\" for property \" << %s%s;\n",
+          prefix.c_str(), prop_id.c_str());
+      writer.Dedent();
+      writer.Write("}\n\n");
+    }
+  }
+  writer.Write("}  // namespace\n");
+
+  writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    if (i > 0) writer.Write("\n");
+
+    const sysprop::Property& prop = props.prop(i);
+    std::string prop_id = PropNameToIdentifier(prop.name());
+    std::string prop_type = GetCppPropTypeName(prop);
+    std::string prefix = (prop.readonly() ? "ro." : "") + props.prefix();
+    if (!prefix.empty() && prefix.back() != '.') prefix.push_back('.');
+
+    if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+      std::string enum_name = GetCppEnumName(prop);
+    }
+
+    writer.Write("std::optional<%s> %s() {\n", prop_type.c_str(),
+                 prop_id.c_str());
+    writer.Indent();
+    writer.Write("return libc::GetProp<%s>(\"%s%s\");\n", prop_type.c_str(),
+                 prefix.c_str(), prop.name().c_str());
+    writer.Dedent();
+    writer.Write("}\n");
+
+    if (!prop.readonly()) {
+      writer.Write("\nbool %s(const %s& value) {\n", prop_id.c_str(),
+                   prop_type.c_str());
+      writer.Indent();
+      writer.Write(
+          "return libc::system_property_set(\"%s%s\", "
+          "FormatValue(value).c_str()) == 0;\n",
+          prefix.c_str(), prop.name().c_str());
+      writer.Dedent();
+      writer.Write("}\n");
+    }
+  }
+
+  writer.Write("\n}  // namespace %s\n\n", cpp_namespace.c_str());
+
+  *source_result = writer.Code();
+
+  return true;
+}
+
+}  // namespace
+
+bool GenerateCppFiles(const std::string& input_file_path,
+                      const std::string& header_output_dir,
+                      const std::string& source_output_dir, std::string* err) {
+  sysprop::Properties props;
+
+  if (!ParseProps(input_file_path, &props, err)) {
+    return false;
+  }
+
+  std::string header_result, source_result;
+
+  if (!GenerateHeader(props, &header_result, err)) {
+    return false;
+  }
+
+  if (!GenerateSource(props, &source_result, err)) {
+    return false;
+  }
+
+  std::string header_path =
+      header_output_dir + "/" + GetModuleName(props) + ".h";
+  std::string source_path =
+      source_output_dir + "/" + GetModuleName(props) + ".cpp";
+
+  if (!IsDirectory(header_output_dir) && !CreateDirectories(header_output_dir)) {
+    *err = "Creating directory to " + header_output_dir +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  if (!IsDirectory(source_output_dir) && !CreateDirectories(source_output_dir)) {
+    *err = "Creating directory to " + source_output_dir +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  if (!android::base::WriteStringToFile(header_result, header_path)) {
+    *err = "Writing generated header to " + header_path +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  if (!android::base::WriteStringToFile(source_result, source_path)) {
+    *err = "Writing generated source to " + source_path +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  return true;
+}
diff --git a/CppGen.h b/CppGen.h
new file mode 100644
index 0000000..6dae6c0
--- /dev/null
+++ b/CppGen.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_TOOLS_SYSPROP_CPPGEN_H_
+#define SYSTEM_TOOLS_SYSPROP_CPPGEN_H_
+
+#include <string>
+
+bool GenerateCppFiles(const std::string& input_file_path,
+                      const std::string& header_output_dir,
+                      const std::string& source_output_dir, std::string* err);
+
+#endif  // SYSTEM_TOOLS_SYSPROP_CPPGEN_H_
diff --git a/CppMain.cpp b/CppMain.cpp
new file mode 100644
index 0000000..012878a
--- /dev/null
+++ b/CppMain.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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_cpp"
+
+#include <android-base/logging.h>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include <getopt.h>
+
+#include "CppGen.h"
+
+namespace {
+
+struct Arguments {
+  std::string input_file_path_;
+  std::string header_output_dir_;
+  std::string source_output_dir_;
+};
+
+[[noreturn]] void PrintUsage(const char* exe_name) {
+  std::printf(
+      "Usage: %s [--header-output-dir dir] [--source-output-dir dir] "
+      "sysprop_file \n",
+      exe_name);
+  std::exit(EXIT_FAILURE);
+}
+
+bool ParseArgs(int argc, char* argv[], Arguments* args, std::string* err) {
+  for (;;) {
+    static struct option long_options[] = {
+        {"header-output-dir", required_argument, 0, 'h'},
+        {"source-output-dir", required_argument, 0, 's'},
+    };
+
+    int opt = getopt_long_only(argc, argv, "", long_options, nullptr);
+    if (opt == -1) break;
+
+    switch (opt) {
+      case 'h':
+        args->header_output_dir_ = optarg;
+        break;
+      case 's':
+        args->source_output_dir_ = optarg;
+        break;
+      default:
+        PrintUsage(argv[0]);
+    }
+  }
+
+  if (optind >= argc) {
+    *err = "No input file specified";
+    return false;
+  }
+
+  if (optind + 1 < argc) {
+    *err = "More than one input file";
+    return false;
+  }
+
+  args->input_file_path_ = argv[optind];
+  if (args->header_output_dir_.empty()) args->header_output_dir_ = ".";
+  if (args->source_output_dir_.empty()) args->source_output_dir_ = ".";
+
+  return true;
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  Arguments args;
+  std::string err;
+  if (!ParseArgs(argc, argv, &args, &err)) {
+    std::fprintf(stderr, "%s: %s\n", argv[0], err.c_str());
+    PrintUsage(argv[0]);
+  }
+
+  if (!GenerateCppFiles(args.input_file_path_, args.header_output_dir_,
+                        args.source_output_dir_, &err)) {
+    LOG(FATAL) << "Error during generating cpp sysprop from "
+               << args.input_file_path_ << ": " << err;
+  }
+}
diff --git a/JavaGen.cpp b/JavaGen.cpp
new file mode 100644
index 0000000..efe6c21
--- /dev/null
+++ b/JavaGen.cpp
@@ -0,0 +1,588 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cerrno>
+#include <regex>
+#include <string>
+
+#include "CodeWriter.h"
+#include "Common.h"
+#include "sysprop.pb.h"
+
+namespace {
+
+constexpr const char* kIndent = "    ";
+
+constexpr const char* kJavaFileImports =
+    R"(import android.annotation.SystemApi;
+
+import java.util.ArrayList;
+import java.util.function.Function;
+import java.util.List;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+)";
+
+constexpr const char* kJavaParsersAndFormatters =
+    R"(private static Boolean tryParseBoolean(String str) {
+    switch (str.toLowerCase()) {
+        case "1":
+        case "y":
+        case "yes":
+        case "on":
+        case "true":
+            return Boolean.TRUE;
+        case "0":
+        case "n":
+        case "no":
+        case "off":
+        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 Long tryParseLong(String str) {
+    try {
+        return Long.valueOf(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 str;
+}
+
+private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
+    try {
+        return Enum.valueOf(enumType, str);
+    } catch (IllegalArgumentException e) {
+        return null;
+    }
+}
+
+private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
+    List<T> ret = new ArrayList<>();
+
+    for (String element : str.split(",")) {
+        T parsed = elementParser.apply(element);
+        if (parsed == null) {
+            return null;
+        }
+        ret.add(parsed);
+    }
+
+    return ret;
+}
+
+private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
+    List<T> ret = new ArrayList<>();
+
+    for (String element : str.split(",")) {
+        T parsed = tryParseEnum(enumType, element);
+        if (parsed == null) {
+            return null;
+        }
+        ret.add(parsed);
+    }
+
+    return ret;
+}
+
+private static <T> String formatList(List<T> list) {
+    StringJoiner joiner = new StringJoiner(",");
+
+    for (T element : list) {
+        joiner.add(element.toString());
+    }
+
+    return joiner.toString();
+}
+
+)";
+
+constexpr const char* kJniLibraryIncludes =
+    R"(#include <cstdint>
+#include <iterator>
+#include <string>
+
+#include <dlfcn.h>
+#include <jni.h>
+
+#include <android-base/logging.h>
+
+)";
+
+constexpr const char* kJniLibraryUtils =
+    R"(namespace libc {
+
+struct prop_info;
+
+const prop_info* (*system_property_find)(const char* name);
+
+void (*system_property_read_callback)(
+    const prop_info* pi,
+    void (*callback)(void* cookie, const char* name, const char* value, std::uint32_t serial),
+    void* cookie
+);
+
+int (*system_property_set)(const char* key, const char* value);
+
+void* handle;
+
+__attribute__((constructor)) void load_libc_functions() {
+    handle = dlopen("libc.so", RTLD_LAZY | RTLD_NOLOAD);
+
+    system_property_find = reinterpret_cast<decltype(system_property_find)>(dlsym(handle, "__system_property_find"));
+    system_property_read_callback = reinterpret_cast<decltype(system_property_read_callback)>(dlsym(handle, "__system_property_read_callback"));
+    system_property_set = reinterpret_cast<decltype(system_property_set)>(dlsym(handle, "__system_property_set"));
+}
+
+__attribute__((destructor)) void release_libc_functions() {
+    dlclose(handle);
+}
+
+jstring GetProp(JNIEnv* env, const char* key) {
+    auto pi = system_property_find(key);
+    if (pi == nullptr) return env->NewStringUTF("");
+    std::string ret;
+    system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
+        *static_cast<std::string*>(cookie) = value;
+    }, &ret);
+    return env->NewStringUTF(ret.c_str());
+}
+
+}  // namespace libc
+
+class ScopedUtfChars {
+  public:
+    ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+        utf_chars_ = env->GetStringUTFChars(s, nullptr);
+    }
+
+    ~ScopedUtfChars() {
+        if (utf_chars_) {
+            env_->ReleaseStringUTFChars(string_, utf_chars_);
+        }
+    }
+
+    const char* c_str() const {
+        return utf_chars_;
+    }
+
+  private:
+    JNIEnv* env_;
+    jstring string_;
+    const char* utf_chars_;
+};
+
+)";
+
+constexpr const char* kJniOnload =
+    R"(jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv* env = nullptr;
+
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG(ERROR) << "GetEnv failed";
+        return -1;
+    }
+
+    jclass clazz = env->FindClass(kClassName);
+    if (clazz == nullptr) {
+        LOG(ERROR) << "Cannot find class " << kClassName;
+        return -1;
+    }
+
+    if (env->RegisterNatives(clazz, methods, std::size(methods)) < 0) {
+        LOG(ERROR) << "RegisterNatives failed";
+        return -1;
+    }
+
+    return JNI_VERSION_1_6;
+}
+)";
+
+const std::regex kRegexDot{"\\."};
+const std::regex kRegexUnderscore{"_"};
+
+struct Arguments {
+  std::string java_output_dir_;
+  std::string jni_output_dir_;
+  std::string input_file_path_;
+};
+
+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);
+bool IsListProp(const sysprop::Property& prop);
+void WriteJavaAnnotation(CodeWriter& writer, const sysprop::Property& prop);
+bool GenerateJavaClass(const sysprop::Properties& props,
+                       std::string* java_result, std::string* err);
+bool GenerateJniLibrary(const sysprop::Properties& props,
+                        std::string* jni_result, std::string* err);
+
+std::string GetJavaEnumTypeName(const sysprop::Property& prop) {
+  return PropNameToIdentifier(prop.name()) + "_values";
+}
+
+std::string GetJavaTypeName(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "Boolean";
+    case sysprop::Integer:
+      return "Integer";
+    case sysprop::Long:
+      return "Long";
+    case sysprop::Double:
+      return "Double";
+    case sysprop::String:
+      return "String";
+    case sysprop::Enum:
+      return GetJavaEnumTypeName(prop);
+    case sysprop::BooleanList:
+      return "List<Boolean>";
+    case sysprop::IntegerList:
+      return "List<Integer>";
+    case sysprop::LongList:
+      return "List<Long>";
+    case sysprop::DoubleList:
+      return "List<Double>";
+    case sysprop::StringList:
+      return "List<String>";
+    case sysprop::EnumList:
+      return "List<" + GetJavaEnumTypeName(prop) + ">";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GetParsingExpression(const sysprop::Property& prop) {
+  std::string native_method =
+      "native_" + PropNameToIdentifier(prop.name()) + "_get";
+
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "tryParseBoolean(" + native_method + "())";
+    case sysprop::Integer:
+      return "tryParseInteger(" + native_method + "())";
+    case sysprop::Long:
+      return "tryParseLong(" + native_method + "())";
+    case sysprop::Double:
+      return "tryParseDouble(" + native_method + "())";
+    case sysprop::String:
+      return "tryParseString(" + native_method + "())";
+    case sysprop::Enum:
+      return "tryParseEnum(" + GetJavaEnumTypeName(prop) + ".class, " +
+             native_method + "())";
+    case sysprop::EnumList:
+      return "tryParseEnumList(" + GetJavaEnumTypeName(prop) + ".class, " +
+             native_method + "())";
+    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;
+    default:
+      __builtin_unreachable();
+  }
+
+  return "tryParseList(" + element_parser + ", " + native_method + "())";
+}
+
+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);
+}
+
+bool IsListProp(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::BooleanList:
+    case sysprop::IntegerList:
+    case sysprop::LongList:
+    case sysprop::DoubleList:
+    case sysprop::StringList:
+    case sysprop::EnumList:
+      return true;
+    default:
+      return false;
+  }
+}
+
+void WriteJavaAnnotation(CodeWriter& writer, const sysprop::Property& prop) {
+  switch (prop.scope()) {
+    case sysprop::System:
+      writer.Write("@SystemApi\n");
+      break;
+    case sysprop::Internal:
+      writer.Write("/** @hide */\n");
+      break;
+    default:
+      break;
+  }
+}
+
+bool GenerateJavaClass(const sysprop::Properties& props,
+                       std::string* java_result,
+                       [[maybe_unused]] std::string* err) {
+  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("static {\n");
+  writer.Indent();
+  writer.Write("System.loadLibrary(\"%s_jni\");\n",
+               GetModuleName(props).c_str());
+  writer.Dedent();
+  writer.Write("}\n\n");
+  writer.Write("%s", kJavaParsersAndFormatters);
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    writer.Write("\n");
+
+    const sysprop::Property& prop = props.prop(i);
+
+    std::string prop_id = PropNameToIdentifier(prop.name()).c_str();
+    std::string prop_type = GetJavaTypeName(prop);
+
+    if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+      WriteJavaAnnotation(writer, prop);
+      writer.Write("public static enum %s {\n",
+                   GetJavaEnumTypeName(prop).c_str());
+      writer.Indent();
+      for (const std::string& name :
+           android::base::Split(prop.enum_values(), "|")) {
+        writer.Write("%s,\n", name.c_str());
+      }
+      writer.Dedent();
+      writer.Write("}\n\n");
+    }
+
+    WriteJavaAnnotation(writer, prop);
+
+    writer.Write("public static Optional<%s> %s() {\n", prop_type.c_str(),
+                 prop_id.c_str());
+    writer.Indent();
+    writer.Write("return Optional.ofNullable(%s);\n",
+                 GetParsingExpression(prop).c_str());
+    writer.Dedent();
+    writer.Write("}\n\n");
+
+    writer.Write("private static native String native_%s_get();\n",
+                 prop_id.c_str());
+
+    if (!prop.readonly()) {
+      writer.Write("\n");
+      WriteJavaAnnotation(writer, prop);
+      writer.Write("public static boolean %s(%s value) {\n", prop_id.c_str(),
+                   prop_type.c_str());
+      writer.Indent();
+      writer.Write("return native_%s_set(%s);\n", prop_id.c_str(),
+                   IsListProp(prop) ? "formatList(value)" : "value.toString()");
+      writer.Dedent();
+      writer.Write("}\n\n");
+      writer.Write("private static native boolean native_%s_set(String str);\n",
+                   prop_id.c_str());
+    }
+  }
+
+  writer.Dedent();
+  writer.Write("}\n");
+
+  *java_result = writer.Code();
+  return true;
+}
+
+bool GenerateJniLibrary(const sysprop::Properties& props,
+                        std::string* jni_result,
+                        [[maybe_unused]] std::string* err) {
+  CodeWriter writer(kIndent);
+  writer.Write("%s", kGeneratedFileFooterComments);
+  writer.Write("#define LOG_TAG \"%s_jni\"\n\n", props.module().c_str());
+  writer.Write("%s", kJniLibraryIncludes);
+  writer.Write("namespace {\n\n");
+  writer.Write("constexpr const char* kClassName = \"%s\";\n\n",
+               std::regex_replace(props.module(), kRegexDot, "/").c_str());
+  writer.Write("%s", kJniLibraryUtils);
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const sysprop::Property& prop = props.prop(i);
+    std::string prop_id = PropNameToIdentifier(prop.name());
+    std::string prefix = (prop.readonly() ? "ro." : "") + props.prefix();
+    if (!prefix.empty() && prefix.back() != '.') prefix.push_back('.');
+
+    writer.Write("jstring JNICALL %s_get(JNIEnv* env, jclass) {\n",
+                 prop_id.c_str());
+    writer.Indent();
+    writer.Write("return libc::GetProp(env, \"%s%s\");\n", prefix.c_str(),
+                 prop.name().c_str());
+    writer.Dedent();
+    writer.Write("}\n\n");
+
+    if (!prop.readonly()) {
+      writer.Write(
+          "jboolean JNICALL %s_set(JNIEnv* env, jclass, "
+          "jstring str) {\n",
+          prop_id.c_str());
+      writer.Indent();
+      writer.Write(
+          "return libc::system_property_set(\"%s%s\", ScopedUtfChars(env, "
+          "str).c_str()) == 0 ? JNI_TRUE : JNI_FALSE;\n",
+          prefix.c_str(), prop.name().c_str());
+      writer.Dedent();
+      writer.Write("}\n\n");
+    }
+  }
+
+  writer.Write("const JNINativeMethod methods[] = {\n");
+  writer.Indent();
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const sysprop::Property& prop = props.prop(i);
+    std::string prop_id = PropNameToIdentifier(prop.name());
+
+    writer.Write(
+        "{\"native_%s_get\", \"()Ljava/lang/String;\", "
+        "reinterpret_cast<void*>(%s_get)},\n",
+        prop_id.c_str(), prop_id.c_str());
+    if (!prop.readonly()) {
+      writer.Write(
+          "{\"native_%s_set\", \"(Ljava/lang/String;)Z\", "
+          "reinterpret_cast<void*>(%s_set)},\n",
+          prop_id.c_str(), prop_id.c_str());
+    }
+  }
+
+  writer.Dedent();
+  writer.Write("};\n\n");
+  writer.Write("}  // namespace\n\n");
+  writer.Write("%s", kJniOnload);
+
+  *jni_result = writer.Code();
+  return true;
+}
+
+}  // namespace
+
+bool GenerateJavaLibrary(const std::string& input_file_path,
+                         const std::string& java_output_dir,
+                         const std::string& jni_output_dir, std::string* err) {
+  sysprop::Properties props;
+
+  if (!ParseProps(input_file_path, &props, err)) {
+    return false;
+  }
+
+  std::string java_result, jni_result;
+
+  if (!GenerateJavaClass(props, &java_result, err)) {
+    return false;
+  }
+
+  if (!GenerateJniLibrary(props, &jni_result, err)) {
+    return false;
+  }
+
+  std::string package_name = GetJavaPackageName(props);
+  std::string java_package_dir =
+      java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/");
+
+  if (!IsDirectory(java_package_dir) && !CreateDirectories(java_package_dir)) {
+    *err = "Creating directory to " + java_package_dir +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  if (!IsDirectory(jni_output_dir) && !CreateDirectories(jni_output_dir)) {
+    *err = "Creating directory to " + jni_output_dir +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  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)) {
+    *err = "Writing generated java class to " + java_output_file +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  std::string jni_output_file = jni_output_dir + "/" + class_name + "_jni.cpp";
+  if (!android::base::WriteStringToFile(jni_result, jni_output_file)) {
+    *err = "Writing generated jni library to " + jni_output_file +
+           " failed: " + strerror(errno);
+    return false;
+  }
+
+  return true;
+}
diff --git a/JavaGen.h b/JavaGen.h
new file mode 100644
index 0000000..ec99da9
--- /dev/null
+++ b/JavaGen.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_TOOLS_SYSPROP_JAVAGEN_H_
+#define SYSTEM_TOOLS_SYSPROP_JAVAGEN_H_
+
+#include <string>
+
+bool GenerateJavaLibrary(const std::string& input_file_path,
+                         const std::string& java_output_dir,
+                         const std::string& jni_output_dir, std::string* err);
+
+#endif  // SYSTEM_TOOLS_SYSPROP_JAVAGEN_H_
diff --git a/JavaMain.cpp b/JavaMain.cpp
new file mode 100644
index 0000000..6f897d0
--- /dev/null
+++ b/JavaMain.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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"
+
+#include <android-base/logging.h>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include <getopt.h>
+
+#include "JavaGen.h"
+
+namespace {
+
+struct Arguments {
+  std::string input_file_path_;
+  std::string java_output_dir_;
+  std::string jni_output_dir_;
+};
+
+[[noreturn]] void PrintUsage(const char* exe_name) {
+  std::printf(
+      "Usage: %s [--java-output-dir dir] [--jni-output-dir dir] sysprop_file\n",
+      exe_name);
+  std::exit(EXIT_FAILURE);
+}
+
+bool ParseArgs(int argc, char* argv[], Arguments* args, std::string* err) {
+  for (;;) {
+    static struct option long_options[] = {
+        {"java-output-dir", required_argument, 0, 'j'},
+        {"jni-output-dir", required_argument, 0, 'n'},
+    };
+
+    int opt = getopt_long_only(argc, argv, "", long_options, nullptr);
+    if (opt == -1) break;
+
+    switch (opt) {
+      case 'j':
+        args->java_output_dir_ = optarg;
+        break;
+      case 'n':
+        args->jni_output_dir_ = optarg;
+        break;
+      default:
+        PrintUsage(argv[0]);
+    }
+  }
+
+  if (optind >= argc) {
+    *err = "No input file specified";
+    return false;
+  }
+
+  if (optind + 1 < argc) {
+    *err = "More than one input file";
+    return false;
+  }
+
+  args->input_file_path_ = argv[optind];
+  if (args->java_output_dir_.empty()) args->java_output_dir_ = ".";
+  if (args->jni_output_dir_.empty()) args->jni_output_dir_ = ".";
+
+  return true;
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  Arguments args;
+  std::string err;
+  if (!ParseArgs(argc, argv, &args, &err)) {
+    std::fprintf(stderr, "%s: %s\n", argv[0], err.c_str());
+    PrintUsage(argv[0]);
+  }
+
+  if (!GenerateJavaLibrary(args.input_file_path_, args.java_output_dir_,
+                           args.jni_output_dir_, &err)) {
+    LOG(FATAL) << "Error during generating java sysprop from "
+               << args.input_file_path_ << ": " << err;
+  }
+}
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..4cc8156
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+inseob@google.com
+jiyong@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..c8dbf77
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/sysprop.proto b/sysprop.proto
new file mode 100644
index 0000000..3075077
--- /dev/null
+++ b/sysprop.proto
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package sysprop;
+
+enum Scope {
+  Public = 0;
+  System = 1;
+  Internal = 2;
+}
+
+enum Owner {
+  Platform = 0;
+  Vendor = 1;
+  Odm = 2;
+}
+
+enum Type {
+  Boolean = 0;
+  Integer = 1;
+  Long = 2;
+  Double = 3;
+  String = 4;
+  Enum = 5;
+
+  BooleanList = 20;
+  IntegerList = 21;
+  LongList = 22;
+  DoubleList = 24;
+  StringList = 25;
+  EnumList = 26;
+}
+
+message Property {
+  required string name = 1;
+  required Type type = 2;
+  required Scope scope = 3;
+  optional bool readonly = 4;
+  optional string enum_values = 50;
+}
+
+message Properties {
+  required Owner owner = 1;
+  required string module = 2;
+  optional string prefix = 3;
+  repeated Property prop = 4;
+}