Add support for properties without an object manager

This allows for simpler client code when daemons expose a single object.

BUG=none
TEST=unit tests, verified property changed callbacks are received w/o object
manager.

Change-Id: I751b57175906071dbc9e8a8ddb56bdd269eee320
Reviewed-on: https://chromium-review.googlesource.com/370301
Commit-Ready: Christopher Book <cbook@chromium.org>
Tested-by: Christopher Book <cbook@chromium.org>
Reviewed-by: Christopher Book <cbook@chromium.org>
diff --git a/chromeos-dbus-bindings/proxy_generator.cc b/chromeos-dbus-bindings/proxy_generator.cc
index ea0d1dc..7f41eee 100644
--- a/chromeos-dbus-bindings/proxy_generator.cc
+++ b/chromeos-dbus-bindings/proxy_generator.cc
@@ -211,11 +211,15 @@
   for (const auto& signal : interface.signals) {
     AddSignalHandlerRegistration(signal, interface.name, true, text);
   }
-  AddProperties(config, interface, true, text);
+  AddProperties(interface, true, text);
   text->AddBlankLine();
   text->AddLine("virtual const dbus::ObjectPath& GetObjectPath() const = 0;");
-  if (!config.object_manager.name.empty() && !interface.properties.empty())
-    AddPropertyPublicMethods(proxy_name, true, text);
+  if (!interface.properties.empty()) {
+    if (config.object_manager.name.empty())
+      AddInitializeProperties(proxy_name, true, text);
+    else
+      AddSetPropertyChanged(proxy_name, true, text);
+  }
 
   text->PopOffset();
   text->AddLine("};");
@@ -252,13 +256,18 @@
   AddReleaseObjectProxy(text);
   AddGetObjectPath(text);
   AddGetObjectProxy(text);
-  if (!config.object_manager.name.empty() && !interface.properties.empty())
-    AddPropertyPublicMethods(proxy_name, false, text);
+  if (!interface.properties.empty()) {
+    if (config.object_manager.name.empty())
+      AddInitializeProperties(proxy_name, false, text);
+    else
+      AddSetPropertyChanged(proxy_name, false, text);
+    AddGetProperties(text);
+  }
   for (const auto& method : interface.methods) {
     AddMethodProxy(method, interface.name, false, text);
     AddAsyncMethodProxy(method, interface.name, false, text);
   }
-  AddProperties(config, interface, false, text);
+  AddProperties(interface, false, text);
 
   text->PopOffset();
   text->AddBlankLine();
@@ -288,6 +297,8 @@
                      proxy_name.c_str()));
   }
   text->AddLine("dbus::ObjectProxy* dbus_object_proxy_;");
+  if (config.object_manager.name.empty() && !interface.properties.empty())
+    text->AddLine("std::unique_ptr<PropertySet> property_set_;");
   text->AddBlankLine();
 
   if (!config.object_manager.name.empty() && !interface.properties.empty()) {
@@ -450,9 +461,36 @@
 }
 
 // static
-void ProxyGenerator::AddPropertyPublicMethods(const string& class_name,
-                                              bool declaration_only,
-                                              IndentedText* text) {
+void ProxyGenerator::AddInitializeProperties(const string& class_name,
+                                             bool declaration_only,
+                                             IndentedText* text) {
+  text->AddBlankLine();
+  text->AddLine(StringPrintf("%svoid InitializeProperties(",
+                             declaration_only ? "virtual " : ""));
+  text->AddLineWithOffset(
+      StringPrintf("const base::Callback<void(%sInterface*, "
+                   "const std::string&)>& callback) %s",
+                   class_name.c_str(),
+                   declaration_only ? "= 0;" : "override {"),
+      kLineContinuationOffset);
+  if (!declaration_only) {
+    IndentedText block;
+    block.PushOffset(kBlockOffset);
+    block.AddLine("property_set_.reset(");
+    block.AddLineWithOffset(
+        "new PropertySet(dbus_object_proxy_, base::Bind(callback, this)));",
+        kLineContinuationOffset);
+    block.AddLine("property_set_->ConnectSignals();");
+    block.AddLine("property_set_->GetAll();");
+    text->AddBlock(block);
+    text->AddLine("}");
+  }
+}
+
+// static
+void ProxyGenerator::AddSetPropertyChanged(const string& class_name,
+                                           bool declaration_only,
+                                           IndentedText* text) {
   text->AddBlankLine();
   text->AddLine(StringPrintf("%svoid SetPropertyChangedCallback(",
                              declaration_only ? "virtual " : ""));
@@ -465,15 +503,21 @@
   if (!declaration_only) {
     text->AddLineWithOffset("on_property_changed_ = callback;", kBlockOffset);
     text->AddLine("}");
-    text->AddBlankLine();
-
-    text->AddLine(
-        "const PropertySet* GetProperties() const { return property_set_; }");
-    text->AddLine("PropertySet* GetProperties() { return property_set_; }");
   }
 }
 
 // static
+void ProxyGenerator::AddGetProperties(IndentedText* text) {
+  text->AddBlankLine();
+  // Dereference and take the address since sometimes property_set_ is a raw
+  // pointer and sometimes it is a std::unique_ptr.
+  text->AddLine(
+      "const PropertySet* GetProperties() const { "
+      "return &(*property_set_); }");
+  text->AddLine("PropertySet* GetProperties() { return &(*property_set_); }");
+}
+
+// static
 void ProxyGenerator::AddOnPropertyChanged(IndentedText* text) {
   text->AddLine("void OnPropertyChanged(const std::string& property_name) {");
   text->PushOffset(kBlockOffset);
@@ -522,8 +566,7 @@
 void ProxyGenerator::AddPropertySet(const ServiceConfig& config,
                                     const Interface& interface,
                                     IndentedText* text) {
-  // Must have ObjectManager in order for property system to work correctly.
-  if (config.object_manager.name.empty())
+  if (config.object_manager.name.empty() && interface.properties.empty())
     return;
 
   IndentedText block;
@@ -573,14 +616,9 @@
 }
 
 // static
-void ProxyGenerator::AddProperties(const ServiceConfig& config,
-                                   const Interface& interface,
+void ProxyGenerator::AddProperties(const Interface& interface,
                                    bool declaration_only,
                                    IndentedText* text) {
-  // Must have ObjectManager in order for property system to work correctly.
-  if (config.object_manager.name.empty())
-    return;
-
   if (declaration_only && !interface.properties.empty())
     text->AddBlankLine();
 
diff --git a/chromeos-dbus-bindings/proxy_generator.h b/chromeos-dbus-bindings/proxy_generator.h
index 57af0e0..dca6e07 100644
--- a/chromeos-dbus-bindings/proxy_generator.h
+++ b/chromeos-dbus-bindings/proxy_generator.h
@@ -73,10 +73,18 @@
   // Generates GetObjectProxy() method.
   static void AddGetObjectProxy(IndentedText* text);
 
-  // Generates SetPropertyChangedCallback/GetProperties() methods.
-  static void AddPropertyPublicMethods(const std::string& class_name,
-                                       bool declaration_only,
-                                       IndentedText* text);
+  // Generates InitializeProperties() method and callback.
+  static void AddInitializeProperties(const std::string& class_name,
+                                      bool declaration_only,
+                                      IndentedText* text);
+
+  // Generates SetPropertyChanged() method and callback.
+  static void AddSetPropertyChanged(const std::string& class_name,
+                                    bool declaration_only,
+                                    IndentedText* text);
+
+  // Generates GetProperties() methods.
+  static void AddGetProperties(IndentedText* text);
 
   // Generates OnPropertyChanged() method.
   static void AddOnPropertyChanged(IndentedText* text);
@@ -93,8 +101,7 @@
                              IndentedText* text);
 
   // Generates the property accessors.
-  static void AddProperties(const ServiceConfig& config,
-                            const Interface& interface,
+  static void AddProperties(const Interface& interface,
                             bool declaration_only,
                             IndentedText* text);
 
diff --git a/chromeos-dbus-bindings/proxy_generator_unittest.cc b/chromeos-dbus-bindings/proxy_generator_unittest.cc
index 728e977..8e3c0f0 100644
--- a/chromeos-dbus-bindings/proxy_generator_unittest.cc
+++ b/chromeos-dbus-bindings/proxy_generator_unittest.cc
@@ -668,8 +668,8 @@
     on_property_changed_ = callback;
   }
 
-  const PropertySet* GetProperties() const { return property_set_; }
-  PropertySet* GetProperties() { return property_set_; }
+  const PropertySet* GetProperties() const { return &(*property_set_); }
+  PropertySet* GetProperties() { return &(*property_set_); }
 
   const std::string& data() const override {
     return property_set_->data.value();
@@ -952,6 +952,133 @@
 }  // namespace org
 )literal_string";
 
+const char kExpectedContentWithProperties[] = R"literal_string(
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::Test.
+class TestProxyInterface {
+ public:
+  virtual ~TestProxyInterface() = default;
+
+  static const char* DataName() { return "Data"; }
+  virtual const std::string& data() const = 0;
+  static const char* NameName() { return "Name"; }
+  virtual const std::string& name() const = 0;
+  virtual void set_name(const std::string& value,
+                        const base::Callback<void(bool)>& callback) = 0;
+
+  virtual const dbus::ObjectPath& GetObjectPath() const = 0;
+
+  virtual void InitializeProperties(
+      const base::Callback<void(TestProxyInterface*, const std::string&)>& callback) = 0;
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::Test.
+class TestProxy final : public TestProxyInterface {
+ public:
+  class PropertySet : public dbus::PropertySet {
+   public:
+    PropertySet(dbus::ObjectProxy* object_proxy,
+                const PropertyChangedCallback& callback)
+        : dbus::PropertySet{object_proxy,
+                            "org.chromium.Test",
+                            callback} {
+      RegisterProperty(DataName(), &data);
+      RegisterProperty(NameName(), &name);
+    }
+
+    brillo::dbus_utils::Property<std::string> data;
+    brillo::dbus_utils::Property<std::string> name;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PropertySet);
+  };
+
+  TestProxy(const scoped_refptr<dbus::Bus>& bus) :
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~TestProxy() override {
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const override {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  void InitializeProperties(
+      const base::Callback<void(TestProxyInterface*, const std::string&)>& callback) override {
+    property_set_.reset(
+        new PropertySet(dbus_object_proxy_, base::Bind(callback, this)));
+    property_set_->ConnectSignals();
+    property_set_->GetAll();
+  }
+
+  const PropertySet* GetProperties() const { return &(*property_set_); }
+  PropertySet* GetProperties() { return &(*property_set_); }
+
+  const std::string& data() const override {
+    return property_set_->data.value();
+  }
+
+  const std::string& name() const override {
+    return property_set_->name.value();
+  }
+
+  void set_name(const std::string& value,
+                const base::Callback<void(bool)>& callback) override {
+    property_set_->name.Set(value, callback);
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.Test"};
+  const dbus::ObjectPath object_path_{"/org/chromium/Test"};
+  dbus::ObjectProxy* dbus_object_proxy_;
+  std::unique_ptr<PropertySet> property_set_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+)literal_string";
+
 const char kExpectedContentWithObjectManagerAndServiceName[] = R"literal_string(
 #include <memory>
 #include <string>
@@ -1375,6 +1502,24 @@
   test_utils::EXPECT_TEXT_CONTAINED(kExpectedContentWithService, contents);
 }
 
+TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithProperties) {
+  Interface interface;
+  interface.name = "org.chromium.Test";
+  interface.path = "/org/chromium/Test";
+  interface.properties.emplace_back("Data", "s", "read");
+  interface.properties.emplace_back("Name", "s", "readwrite");
+  vector<Interface> interfaces{interface};
+  base::FilePath output_path = temp_dir_.path().Append("output2.h");
+  ServiceConfig config;
+  config.service_name = "org.chromium.Test";
+  EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path));
+  string contents;
+  EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
+  // The header guards contain the (temporary) filename, so we search for
+  // the content we need within the string.
+  test_utils::EXPECT_TEXT_CONTAINED(kExpectedContentWithProperties, contents);
+}
+
 TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithObjectManager) {
   Interface interface;
   interface.name = "org.chromium.Itf1";