chromeos-dbus-bindings: Add support for DBus Property

It is desired to have this tool auto generate codes for DBus Property,
both registering property to the DBus interface and prvoiding accessor
functions for the property. This make adding DBus property much easier
for the application developers.

The accessor functions are named based on the property name (GetPropertyName
and SetPropertyName). So the developer doesn't need to know the actual
variable name being used.

Note that DBus spec requires the user to specify access permission for the
property, which is not currently supported by chrome-dbus (always read/write
with write operation not supported). So this attribute is currently a noop.

Example:
<property name="InterfaceName" type="s" access="read"/>

dbus_interface_->AddProperty
    "InterfaceName",
    &interface_name_);

std::string GetInterfaceName() const {
  return interface_name_.GetValue.Get<std::string>();
}
void SetInterfaceName(
    const std::string& interface_name) {
  interface_name_.SetValue(interface_name);
}

chromeos::dbus_utils::ExportedProperty<std::string>
    interface_name_;

BUG=chromium:429027
TEST=unittests, emerge apmanager with DBus property binding

Change-Id: Ib05af799afdbe63d740c75196f6967b2b11f1748
Reviewed-on: https://chromium-review.googlesource.com/226946
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
diff --git a/chromeos-dbus-bindings/adaptor_generator.cc b/chromeos-dbus-bindings/adaptor_generator.cc
index 53f7e79..990976a 100644
--- a/chromeos-dbus-bindings/adaptor_generator.cc
+++ b/chromeos-dbus-bindings/adaptor_generator.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include <base/logging.h>
+#include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 
 #include "chromeos-dbus-bindings/dbus_signature.h"
@@ -58,9 +59,11 @@
   AddSendSignalMethods(interface, &text);
   text.AddLine(StringPrintf("virtual ~%s() = default;", adaptor_name.c_str()));
   text.AddLine("virtual void OnRegisterComplete(bool success) {}");
-  text.PopOffset();
 
   text.AddBlankLine();
+  AddPropertyMethods(interface, &text);
+  text.PopOffset();
+
   text.AddLineWithOffset("protected:", kScopeOffset);
   text.PushOffset(kBlockOffset);
   text.AddLine("chromeos::dbus_utils::DBusInterface* dbus_interface() {");
@@ -74,6 +77,8 @@
   text.AddLineWithOffset("private:", kScopeOffset);
 
   text.PushOffset(kBlockOffset);
+  text.AddLine("// Exported properties");
+  AddPropertyDataMembers(interface, &text);
   text.AddLine("MethodInterface* interface_;  // Owned by caller.");
   text.AddLine("chromeos::dbus_utils::DBusObject dbus_object_;");
   AddSignalDataMembers(interface, &text);
@@ -144,6 +149,15 @@
     block.AddLine(StringPrintf("&MethodInterface::%s);", method.name.c_str()));
     block.PopOffset();
   }
+  // Register exported properties.
+  for (const auto& property : interface.properties) {
+    string variable_name = GetPropertyVariableName(property.name);
+    block.AddLine("dbus_interface_->AddProperty(");
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf("\"%s\",", property.name.c_str()));
+    block.AddLine(StringPrintf("&%s_);", variable_name.c_str()));
+    block.PopOffset();
+  }
   block.AddLine("dbus_object_.RegisterAsync(base::Bind(");
   block.AddLineWithOffset(
       StringPrintf("&%s::OnRegisterComplete, base::Unretained(this)));",
@@ -258,6 +272,83 @@
   text->AddBlock(block);
 }
 
+// static
+void AdaptorGenerator::AddPropertyMethods(const Interface& interface,
+                                          IndentedText *text) {
+  IndentedText block;
+  DbusSignature signature;
 
+  for (const auto& property : interface.properties) {
+    string type;
+    CHECK(signature.Parse(property.type, &type));
+    string variable_name = GetPropertyVariableName(property.name);
+
+    // Getter method.
+    block.AddLine(StringPrintf("%s Get%s() const {",
+                               type.c_str(),
+                               property.name.c_str()));
+    block.PushOffset(kBlockOffset);
+    block.AddLine(StringPrintf("return %s_.GetValue().Get<%s>();",
+                               variable_name.c_str(),
+                               type.c_str()));
+    block.PopOffset();
+    block.AddLine("}");
+
+    // Setter method.
+    block.AddLine(StringPrintf("void Set%s(", property.name.c_str()));
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf("const %s& %s) {",
+                               type.c_str(),
+                               variable_name.c_str()));
+    block.PopOffset();
+    block.PushOffset(kBlockOffset);
+    block.AddLine(StringPrintf("%s_.SetValue(%s);",
+                               variable_name.c_str(),
+                               variable_name.c_str()));
+    block.PopOffset();
+    block.AddLine("}");
+    block.AddBlankLine();
+  }
+  text->AddBlock(block);
+}
+
+// static
+void AdaptorGenerator::AddPropertyDataMembers(const Interface& interface,
+                                              IndentedText *text) {
+  IndentedText block;
+  DbusSignature signature;
+
+  for (const auto& property : interface.properties) {
+    string type;
+    CHECK(signature.Parse(property.type, &type));
+    string variable_name = GetPropertyVariableName(property.name);
+
+    block.AddLine(StringPrintf("chromeos::dbus_utils::ExportedProperty<%s>",
+                               type.c_str()));
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf("%s_;", variable_name.c_str()));
+    block.PopOffset();
+  }
+  text->AddBlock(block);
+}
+
+// static
+string AdaptorGenerator::GetPropertyVariableName(const string& property_name) {
+  // Convert CamelCase property name to google_style variable name.
+  string result;
+  for (size_t i = 0; i < property_name.length(); i++) {
+    char c = property_name[i];
+    if (c < 'A' || c > 'Z') {
+      result += c;
+      continue;
+    }
+
+    if (i != 0) {
+      result += '_';
+    }
+    result += base::ToLowerASCII(c);
+  }
+  return result;
+}
 
 }  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/adaptor_generator.h b/chromeos-dbus-bindings/adaptor_generator.h
index 689d5bf..84adec7 100644
--- a/chromeos-dbus-bindings/adaptor_generator.h
+++ b/chromeos-dbus-bindings/adaptor_generator.h
@@ -49,6 +49,17 @@
   static void AddSignalDataMembers(const Interface& interface,
                                    IndentedText *text);
 
+  // Generates adaptor accessor methods for the properties.
+  static void AddPropertyMethods(const Interface& interface,
+                                 IndentedText *text);
+
+  // Generate ExportProperty data members for the properties.
+  static void AddPropertyDataMembers(const Interface& interface,
+                                     IndentedText *text);
+
+  // Return a variable name based on the given property name.
+  static std::string GetPropertyVariableName(const std::string& property_name);
+
   DISALLOW_COPY_AND_ASSIGN(AdaptorGenerator);
 };
 
diff --git a/chromeos-dbus-bindings/adaptor_generator_unittest.cc b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
index 62d25d8..41aeeb2 100644
--- a/chromeos-dbus-bindings/adaptor_generator_unittest.cc
+++ b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
@@ -40,6 +40,9 @@
 const char kSignal1Argument0[] = "s";
 const char kSignal1ArgumentName0[] = "key";
 const char kSignal1Argument1[] = "ao";
+const char kProperty0Name[] = "InterfaceName";
+const char kProperty0Type[] = "s";
+const char kProperty0Access[] = "read";
 
 const char kInterfaceName[] = "org.chromium.TestInterface";
 const char kExpectedContent[] = R"literal_string(
@@ -102,6 +105,9 @@
         "Kei",
         base::Unretained(interface_),
         &MethodInterface::Kei);
+    dbus_interface_->AddProperty(
+        "InterfaceName",
+        &interface_name_);
     dbus_object_.RegisterAsync(base::Bind(
         &TestInterfaceAdaptor::OnRegisterComplete, base::Unretained(this)));
   }
@@ -117,12 +123,23 @@
   virtual ~TestInterfaceAdaptor() = default;
   virtual void OnRegisterComplete(bool success) {}
 
+  std::string GetInterfaceName() const {
+    return interface_name_.GetValue().Get<std::string>();
+  }
+  void SetInterfaceName(
+      const std::string& interface_name) {
+    interface_name_.SetValue(interface_name);
+  }
+
  protected:
   chromeos::dbus_utils::DBusInterface* dbus_interface() {
     return dbus_interface_;
   }
 
  private:
+  // Exported properties
+  chromeos::dbus_utils::ExportedProperty<std::string>
+      interface_name_;
   MethodInterface* interface_;  // Owned by caller.
   chromeos::dbus_utils::DBusObject dbus_object_;
   chromeos::dbus_utils::DBusSignal<
@@ -188,6 +205,10 @@
       vector<Interface::Argument>{
           {kSignal1ArgumentName0, kSignal1Argument0},
           {"", kSignal1Argument1}});
+  interface.properties.emplace_back(
+      kProperty0Name,
+      kProperty0Type,
+      kProperty0Access);
 
   base::FilePath output_path = temp_dir_.path().Append("output.h");
   EXPECT_TRUE(AdaptorGenerator::GenerateAdaptor(interface, output_path));
diff --git a/chromeos-dbus-bindings/interface.h b/chromeos-dbus-bindings/interface.h
index 04de002..627dce4 100644
--- a/chromeos-dbus-bindings/interface.h
+++ b/chromeos-dbus-bindings/interface.h
@@ -41,15 +41,27 @@
     std::string name;
     std::vector<Argument> arguments;
   };
+  struct Property {
+    Property(const std::string& name_in,
+             const std::string& type_in,
+             const std::string& access_in)
+        : name(name_in), type(type_in), access(access_in) {}
+    std::string name;
+    std::string type;
+    std::string access;
+  };
 
   Interface() = default;
   Interface(const std::string& name_in,
             const std::vector<Method>& methods_in,
-            const std::vector<Signal>& signals_in)
-      : name(name_in), methods(methods_in), signals(signals_in) {}
+            const std::vector<Signal>& signals_in,
+            const std::vector<Property>& properties_in)
+      : name(name_in), methods(methods_in), signals(signals_in),
+        properties(properties_in) {}
   std::string name;
   std::vector<Method> methods;
   std::vector<Signal> signals;
+  std::vector<Property> properties;
 };
 
 }  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/xml_interface_parser.cc b/chromeos-dbus-bindings/xml_interface_parser.cc
index bd3f0c3..7189ec4 100644
--- a/chromeos-dbus-bindings/xml_interface_parser.cc
+++ b/chromeos-dbus-bindings/xml_interface_parser.cc
@@ -22,9 +22,11 @@
 const char XmlInterfaceParser::kMethodTag[] = "method";
 const char XmlInterfaceParser::kNodeTag[] = "node";
 const char XmlInterfaceParser::kSignalTag[] = "signal";
+const char XmlInterfaceParser::kPropertyTag[] = "property";
 const char XmlInterfaceParser::kNameAttribute[] = "name";
 const char XmlInterfaceParser::kTypeAttribute[] = "type";
 const char XmlInterfaceParser::kDirectionAttribute[] = "direction";
+const char XmlInterfaceParser::kAccessAttribute[] = "access";
 const char XmlInterfaceParser::kArgumentDirectionIn[] = "in";
 const char XmlInterfaceParser::kArgumentDirectionOut[] = "out";
 
@@ -81,6 +83,9 @@
   } else if (element_path_ == vector<string> {
                  kNodeTag, kInterfaceTag, kSignalTag, kArgumentTag }) {
     AddSignalArgument(attributes);
+  } else if (element_path_ == vector<string> {
+                 kNodeTag, kInterfaceTag, kPropertyTag }) {
+    interface_.properties.push_back(ParseProperty(attributes));
   }
 }
 
@@ -174,6 +179,20 @@
 }
 
 // static
+Interface::Property XmlInterfaceParser::ParseProperty(
+    const XmlAttributeMap& attributes) {
+  string property_name = GetValidatedElementName(attributes,
+                                                 kPropertyTag);
+  string property_type = GetValidatedElementAttribute(attributes,
+                                                      kPropertyTag,
+                                                      kTypeAttribute);
+  string property_access = GetValidatedElementAttribute(attributes,
+                                                        kPropertyTag,
+                                                        kAccessAttribute);
+  return Interface::Property(property_name, property_type, property_access);
+}
+
+// static
 void XmlInterfaceParser::HandleElementStart(void* user_data,
                                             const XML_Char* element,
                                             const XML_Char** attr) {
diff --git a/chromeos-dbus-bindings/xml_interface_parser.h b/chromeos-dbus-bindings/xml_interface_parser.h
index 84c7dd8..f890806 100644
--- a/chromeos-dbus-bindings/xml_interface_parser.h
+++ b/chromeos-dbus-bindings/xml_interface_parser.h
@@ -42,11 +42,13 @@
   static const char kMethodTag[];
   static const char kNodeTag[];
   static const char kSignalTag[];
+  static const char kPropertyTag[];
 
   // XML attribute names.
   static const char kNameAttribute[];
   static const char kTypeAttribute[];
   static const char kDirectionAttribute[];
+  static const char kAccessAttribute[];
 
   // XML argument directions.
   static const char kArgumentDirectionIn[];
@@ -85,6 +87,9 @@
   static Interface::Argument ParseArgument(const XmlAttributeMap& attributes,
                                            const std::string& element_type);
 
+  // Method for extracting property tag attributes to a struct.
+  static Interface::Property ParseProperty(const XmlAttributeMap& attributes);
+
   // Expat element callback functions.
   static void HandleElementStart(void* user_data,
                                  const XML_Char* element,
diff --git a/chromeos-dbus-bindings/xml_interface_parser_unittest.cc b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc
index 5d23db5..9c61253 100644
--- a/chromeos-dbus-bindings/xml_interface_parser_unittest.cc
+++ b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc
@@ -48,6 +48,8 @@
 const char kBssRemovedSignal[] = "BSSRemoved";
 const char kBssArgument[] = "BSS";
 const char kObjectType[] = "o";
+const char kCapabilitiesProperty[] = "Capabilities";
+const char kReadAccess[] = "read";
 }  // namespace
 
 class XmlInterfaceParserTest : public Test {
@@ -110,6 +112,11 @@
   // <arg name="BSS" type="o"/>
   EXPECT_EQ(kBssArgument, interface.signals[0].arguments[0].name);
   EXPECT_EQ(kObjectType, interface.signals[0].arguments[0].type);
+
+  // <property name="Capabilities" type="s" access="read"/>
+  EXPECT_EQ(kCapabilitiesProperty, interface.properties[0].name);
+  EXPECT_EQ(kArrayStringVariantType, interface.properties[0].type);
+  EXPECT_EQ(kReadAccess, interface.properties[0].access);
 }
 
 }  // namespace chromeos_dbus_bindings