chromeos-dbus-bindings: Create proxy generator

Consolidate common functions to a parent HeaderGenerator class,
and create a new ProxyGenerator class.  This new class converts
from native C++ method calls to D-Bus method invocations.  It
also takes an interface class (populated with default null
implementations) for all signals supported by the interface.

CQ-DEPEND=CL:221015
BUG=chromium:404505
TEST=New unit test

Change-Id: Id6ed1f6f6e0868f32911a96e23e3dc74c9df0212
Reviewed-on: https://chromium-review.googlesource.com/221365
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/chromeos-dbus-bindings/adaptor_generator.cc b/chromeos-dbus-bindings/adaptor_generator.cc
index ea220c3..df77629 100644
--- a/chromeos-dbus-bindings/adaptor_generator.cc
+++ b/chromeos-dbus-bindings/adaptor_generator.cc
@@ -6,11 +6,7 @@
 
 #include <string>
 
-#include <base/file_util.h>
-#include <base/files/file_path.h>
 #include <base/logging.h>
-#include <base/strings/string_split.h>
-#include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 
 #include "chromeos-dbus-bindings/dbus_signature.h"
@@ -23,31 +19,26 @@
 
 namespace chromeos_dbus_bindings {
 
-namespace {
-const int kScopeOffset = 1;
-const int kBlockOffset = 2;
-const int kLineContinuationOffset = 4;
-}  // namespace
-
+// static
 bool AdaptorGenerator::GenerateAdaptor(
     const Interface& interface,
     const base::FilePath& output_file) {
   IndentedText text;
   text.AddLine(StringPrintf("// Automatic generation of interface for %s",
                             interface.name.c_str()));
-  string header_guard = GenerateHeaderGuard(output_file.value(),
-                                            interface.name);
+  string header_guard = GenerateHeaderGuard(output_file, interface.name);
   text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str()));
   text.AddLine(StringPrintf("#define %s", header_guard.c_str()));
   text.AddLine("#include <string>");
   text.AddLine("#include <vector>");
-  text.AddLine("");
+  text.AddBlankLine();
   text.AddLine("#include <base/macros.h>");
   text.AddLine("#include <dbus/object_path.h>");
+  text.AddLine("#include <chromeos/any.h>");
   text.AddLine("#include <chromeos/dbus/dbus_object.h>");
   text.AddLine("#include <chromeos/dbus/exported_object_manager.h>");
   text.AddLine("#include <chromeos/variant_dictionary.h>");
-  text.AddLine("");
+  text.AddBlankLine();
 
   vector<string> namespaces;
   string class_name;
@@ -55,7 +46,7 @@
   for (const auto& space : namespaces) {
     text.AddLine(StringPrintf("namespace %s {", space.c_str()));
   }
-  text.AddLine("");
+  text.AddBlankLine();
 
   string adaptor_name = StringPrintf("%sAdaptor", class_name.c_str());
   text.AddLine(StringPrintf("class %s {", adaptor_name.c_str()));
@@ -91,36 +82,14 @@
   text.PopOffset();
 
   text.AddLine("};");
-  text.AddLine("");
+  text.AddBlankLine();
 
   for (auto it = namespaces.rbegin(); it != namespaces.rend(); ++it) {
     text.AddLine(StringPrintf("}  // namespace %s", it->c_str()));
   }
   text.AddLine(StringPrintf("#endif  // %s", header_guard.c_str()));
 
-  string contents = text.GetContents();
-  int expected_write_return = contents.size();
-  if (base::WriteFile(output_file, contents.c_str(), contents.size()) !=
-      expected_write_return) {
-    LOG(ERROR) << "Failed to write file " << output_file.value();
-    return false;
-  }
-  return true;
-}
-
-// static
-string AdaptorGenerator::GenerateHeaderGuard(
-    const string& filename, const string& interface_name) {
-  string guard = StringPrintf("____chrome_dbus_binding___%s__%s",
-                              interface_name.c_str(), filename.c_str());
-  for (auto& c : guard) {
-    if (IsAsciiAlpha(c)) {
-      c = base::ToUpperASCII(c);
-    } else if (!IsAsciiDigit(c)) {
-      c = '_';
-    }
-  }
-  return guard;
+  return WriteTextToFile(output_file, text);
 }
 
 // static
@@ -214,27 +183,4 @@
   text->AddBlock(block);
 }
 
-// static
-bool AdaptorGenerator::GetNamespacesAndClassName(
-    const string& interface_name,
-    vector<string>* namespaces,
-    string* class_name) {
-  vector<string> split_namespaces;
-  base::SplitString(interface_name, '.', &split_namespaces);
-  if (split_namespaces.size() < 2) {
-    LOG(ERROR) << "Interface name must have both a domain and object part "
-               << "separated by '.'.  Got " << interface_name << " instead.";
-    return false;
-  }
-  *class_name = split_namespaces.back();
-  split_namespaces.pop_back();
-  namespaces->swap(split_namespaces);
-  return true;
-}
-
-// static
-bool AdaptorGenerator::IsIntegralType(const string& type) {
-  return type.find("::") == std::string::npos;
-}
-
 }  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/adaptor_generator.h b/chromeos-dbus-bindings/adaptor_generator.h
index 250557c..fe32409 100644
--- a/chromeos-dbus-bindings/adaptor_generator.h
+++ b/chromeos-dbus-bindings/adaptor_generator.h
@@ -10,6 +10,7 @@
 
 #include <base/macros.h>
 
+#include "chromeos-dbus-bindings/header_generator.h"
 #include "chromeos-dbus-bindings/indented_text.h"
 
 namespace base {
@@ -23,21 +24,14 @@
 class IndentedText;
 struct Interface;
 
-class AdaptorGenerator {
+class AdaptorGenerator : public HeaderGenerator {
  public:
-  AdaptorGenerator() = default;
-  virtual ~AdaptorGenerator() = default;
-
-  bool GenerateAdaptor(const Interface& interface,
-                       const base::FilePath& output_file);
+  static bool GenerateAdaptor(const Interface& interface,
+                              const base::FilePath& output_file);
 
  private:
   friend class AdaptorGeneratorTest;
 
-  // Create a unique header guard string to protect multiple includes of header.
-  static std::string GenerateHeaderGuard(const std::string& filename,
-                                         const std::string& interface_name);
-
   // Generates the constructor for the adaptor.
   static void AddConstructor(const Interface& interface,
                              const std::string& class_name,
@@ -47,14 +41,6 @@
   static void AddMethodInterface(const Interface& interface,
                                  IndentedText *text);
 
-  // Returns a vector of nesting namepsaces.
-  static bool GetNamespacesAndClassName(const std::string& interface_name,
-                                        std::vector<std::string>* namespaces,
-                                        std::string* class_name);
-
-  // Used to decide whether the argument should be a const reference.
-  static bool IsIntegralType(const std::string& type);
-
   DISALLOW_COPY_AND_ASSIGN(AdaptorGenerator);
 };
 
diff --git a/chromeos-dbus-bindings/adaptor_generator_unittest.cc b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
index edd1e3a..58e0863 100644
--- a/chromeos-dbus-bindings/adaptor_generator_unittest.cc
+++ b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
@@ -42,6 +42,7 @@
 
 #include <base/macros.h>
 #include <dbus/object_path.h>
+#include <chromeos/any.h>
 #include <chromeos/dbus/dbus_object.h>
 #include <chromeos/dbus/exported_object_manager.h>
 #include <chromeos/variant_dictionary.h>
@@ -126,7 +127,6 @@
   }
 
   base::ScopedTempDir temp_dir_;
-  AdaptorGenerator generator_;
 };
 
 TEST_F(AdaptorGeneratorTest, GenerateAdaptors) {
@@ -151,7 +151,7 @@
           {"", kMethod3Return0},
           {"", kMethod3Return1}});
   base::FilePath output_path = temp_dir_.path().Append("output.h");
-  EXPECT_TRUE(generator_.GenerateAdaptor(interface, output_path));
+  EXPECT_TRUE(AdaptorGenerator::GenerateAdaptor(interface, output_path));
   string contents;
   EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
   // The header guards contain the (temporary) filename, so we search for
diff --git a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
index 1d92c87..5b729d4 100644
--- a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
+++ b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
@@ -26,8 +26,10 @@
       'sources': [
         'adaptor_generator.cc',
         'dbus_signature.cc',
+        'header_generator.cc',
         'indented_text.cc',
         'method_name_generator.cc',
+        'proxy_generator.cc',
         'xml_interface_parser.cc',
       ],
       'variables': {
@@ -77,6 +79,7 @@
             'dbus_signature_unittest.cc',
             'indented_text_unittest.cc',
             'method_name_generator_unittest.cc',
+            'proxy_generator_unittest.cc',
             'xml_interface_parser_unittest.cc',
           ],
         },
diff --git a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
index 7da9faf..5bf35e1 100644
--- a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
+++ b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
@@ -11,6 +11,7 @@
 
 #include "chromeos-dbus-bindings/adaptor_generator.h"
 #include "chromeos-dbus-bindings/method_name_generator.h"
+#include "chromeos-dbus-bindings/proxy_generator.h"
 #include "chromeos-dbus-bindings/xml_interface_parser.h"
 
 namespace switches {
@@ -19,6 +20,7 @@
 static const char kInput[] = "input";
 static const char kMethodNames[] = "method-names";
 static const char kAdaptor[] = "adaptor";
+static const char kProxy[] = "proxy";
 static const char kHelpMessage[] = "\n"
     "Available Switches: \n"
     "  --input=<interface>\n"
@@ -26,7 +28,9 @@
     "  --method-names=<method name header filename>\n"
     "    The output header file with string constants for each method name.\n"
     "  --adaptor=<adaptor header filename>\n"
-    "    The output header file with DBus adaptor class.\n";
+    "    The output header file name containing the DBus adaptor class.\n"
+    "  --proxy=<proxy header filename>\n"
+    "    The output header file name containing the DBus proxy class.\n";
 
 }  // namespace switches
 
@@ -61,8 +65,7 @@
     std::string method_name_file =
         cl->GetSwitchValueASCII(switches::kMethodNames);
     VLOG(1) << "Outputting method names to " << method_name_file;
-    chromeos_dbus_bindings::MethodNameGenerator method_name_generator;
-    if (!method_name_generator.GenerateMethodNames(
+    if (!chromeos_dbus_bindings::MethodNameGenerator::GenerateMethodNames(
             parser.interface(),
             base::FilePath(method_name_file))) {
       LOG(ERROR) << "Failed to output method names.";
@@ -73,8 +76,7 @@
   if (cl->HasSwitch(switches::kAdaptor)) {
     std::string adaptor_file = cl->GetSwitchValueASCII(switches::kAdaptor);
     VLOG(1) << "Outputting adaptor to " << adaptor_file;
-    chromeos_dbus_bindings::AdaptorGenerator adaptor_generator;
-    if (!adaptor_generator.GenerateAdaptor(
+    if (!chromeos_dbus_bindings::AdaptorGenerator::GenerateAdaptor(
             parser.interface(),
             base::FilePath(adaptor_file))) {
       LOG(ERROR) << "Failed to output adaptor.";
@@ -82,5 +84,16 @@
      }
   }
 
+  if (cl->HasSwitch(switches::kProxy)) {
+    std::string proxy_file = cl->GetSwitchValueASCII(switches::kProxy);
+    LOG(INFO) << "Outputting proxy to " << proxy_file;
+    if (!chromeos_dbus_bindings::ProxyGenerator::GenerateProxy(
+            parser.interface(),
+            base::FilePath(proxy_file))) {
+      LOG(ERROR) << "Failed to output proxy.";
+      return 1;
+     }
+  }
+
   return 0;
 }
diff --git a/chromeos-dbus-bindings/header_generator.cc b/chromeos-dbus-bindings/header_generator.cc
new file mode 100644
index 0000000..c339c13
--- /dev/null
+++ b/chromeos-dbus-bindings/header_generator.cc
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos-dbus-bindings/header_generator.h"
+
+#include <string>
+
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "chromeos-dbus-bindings/indented_text.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_dbus_bindings {
+
+// static
+string HeaderGenerator::GenerateHeaderGuard(
+    const base::FilePath& output_file, const string& interface_name) {
+  string guard = base::StringPrintf("____chromeos_dbus_binding___%s__%s",
+                                    interface_name.c_str(),
+                                    output_file.value().c_str());
+  for (auto& c : guard) {
+    if (IsAsciiAlpha(c)) {
+      c = base::ToUpperASCII(c);
+    } else if (!IsAsciiDigit(c)) {
+      c = '_';
+    }
+  }
+  return guard;
+}
+
+// static
+bool HeaderGenerator::GetNamespacesAndClassName(
+    const string& interface_name,
+    vector<string>* namespaces,
+    string* class_name) {
+  vector<string> split_namespaces;
+  base::SplitString(interface_name, '.', &split_namespaces);
+  if (split_namespaces.size() < 2) {
+    LOG(ERROR) << "Interface name must have both a domain and object part "
+               << "separated by '.'.  Got " << interface_name << " instead.";
+    return false;
+  }
+  *class_name = split_namespaces.back();
+  split_namespaces.pop_back();
+  namespaces->swap(split_namespaces);
+  return true;
+}
+
+// static
+bool HeaderGenerator::IsIntegralType(const string& type) {
+  return type.find("::") == std::string::npos;
+}
+
+// static
+bool HeaderGenerator::WriteTextToFile(
+    const base::FilePath& output_file, const IndentedText &text) {
+  string contents = text.GetContents();
+  int expected_write_return = contents.size();
+  if (base::WriteFile(output_file, contents.c_str(), contents.size()) !=
+      expected_write_return) {
+    LOG(ERROR) << "Failed to write file " << output_file.value();
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/header_generator.h b/chromeos-dbus-bindings/header_generator.h
new file mode 100644
index 0000000..7be8d5b
--- /dev/null
+++ b/chromeos-dbus-bindings/header_generator.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_
+#define CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+namespace base {
+
+class FilePath;
+
+};
+
+namespace chromeos_dbus_bindings {
+
+struct Interface;
+class  IndentedText;
+
+class HeaderGenerator {
+ protected:
+  // Create a unique header guard string to protect multiple includes of header.
+  static std::string GenerateHeaderGuard(const base::FilePath& output_file,
+                                         const std::string& interface_name);
+
+  // Returns a vector of nesting namepsaces.
+  static bool GetNamespacesAndClassName(const std::string& interface_name,
+                                        std::vector<std::string>* namespaces,
+                                        std::string* class_name);
+
+  // Used to decide whether the argument should be a const reference.
+  static bool IsIntegralType(const std::string& type);
+
+  // Writes indented text to a file.
+  static bool WriteTextToFile(const base::FilePath& output_file,
+                              const IndentedText& text);
+
+  static const int kScopeOffset = 1;
+  static const int kBlockOffset = 2;
+  static const int kLineContinuationOffset = 4;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HeaderGenerator);
+};
+
+}  // namespace chromeos_dbus_bindings
+
+#endif  // CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_
diff --git a/chromeos-dbus-bindings/method_name_generator.cc b/chromeos-dbus-bindings/method_name_generator.cc
index b2d49f4..4b4154e 100644
--- a/chromeos-dbus-bindings/method_name_generator.cc
+++ b/chromeos-dbus-bindings/method_name_generator.cc
@@ -23,6 +23,7 @@
   return "k" + method_name + "Method";
 }
 
+// static
 bool MethodNameGenerator::GenerateMethodNames(
     const Interface& interface,
     const base::FilePath& output_file) {
diff --git a/chromeos-dbus-bindings/method_name_generator.h b/chromeos-dbus-bindings/method_name_generator.h
index af2576f..59ca890 100644
--- a/chromeos-dbus-bindings/method_name_generator.h
+++ b/chromeos-dbus-bindings/method_name_generator.h
@@ -21,11 +21,8 @@
 
 class MethodNameGenerator {
  public:
-  MethodNameGenerator() = default;
-  virtual ~MethodNameGenerator() = default;
-
-  virtual bool GenerateMethodNames(const Interface &interface,
-                                   const base::FilePath& output_file);
+  static bool GenerateMethodNames(const Interface &interface,
+                                  const base::FilePath& output_file);
   static std::string GenerateMethodNameConstant(const std::string& method_name);
 
  private:
diff --git a/chromeos-dbus-bindings/method_name_generator_unittest.cc b/chromeos-dbus-bindings/method_name_generator_unittest.cc
index e73b8ba..dc461d6 100644
--- a/chromeos-dbus-bindings/method_name_generator_unittest.cc
+++ b/chromeos-dbus-bindings/method_name_generator_unittest.cc
@@ -45,7 +45,6 @@
   }
 
   base::ScopedTempDir temp_dir_;
-  MethodNameGenerator generator_;
 };
 
 TEST_F(MethodNameGeneratorTest, GnerateMethodNames) {
@@ -54,7 +53,7 @@
   interface.methods.emplace_back(kMethodName1);
   interface.methods.emplace_back(kMethodName2);
   base::FilePath output_path = temp_dir_.path().Append("output.h");
-  EXPECT_TRUE(generator_.GenerateMethodNames(interface, output_path));
+  EXPECT_TRUE(MethodNameGenerator::GenerateMethodNames(interface, output_path));
   string contents;
   EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
   EXPECT_STREQ(kExpectedOutput, contents.c_str());
diff --git a/chromeos-dbus-bindings/proxy_generator.cc b/chromeos-dbus-bindings/proxy_generator.cc
new file mode 100644
index 0000000..34b44c0
--- /dev/null
+++ b/chromeos-dbus-bindings/proxy_generator.cc
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos-dbus-bindings/proxy_generator.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "chromeos-dbus-bindings/dbus_signature.h"
+#include "chromeos-dbus-bindings/indented_text.h"
+
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace chromeos_dbus_bindings {
+
+// static
+bool ProxyGenerator::GenerateProxy(
+    const Interface& interface,
+    const base::FilePath& output_file) {
+  IndentedText text;
+  text.AddLine(StringPrintf("// Automatic generation of interface for %s",
+                            interface.name.c_str()));
+  string header_guard = GenerateHeaderGuard(output_file, interface.name);
+  text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str()));
+  text.AddLine(StringPrintf("#define %s", header_guard.c_str()));
+  text.AddLine("#include <string>");
+  text.AddLine("#include <vector>");
+  text.AddBlankLine();
+  text.AddLine("#include <base/bind.h>");
+  text.AddLine("#include <base/callback.h>");
+  text.AddLine("#include <base/logging.h>");
+  text.AddLine("#include <base/macros.h>");
+  text.AddLine("#include <base/memory/ref_counted.h>");
+  text.AddLine("#include <chromeos/any.h>");
+  text.AddLine("#include <chromeos/dbus/dbus_method_invoker.h>");
+  text.AddLine("#include <chromeos/dbus/dbus_signal_handler.h>");
+  text.AddLine("#include <chromeos/errors/error.h>");
+  text.AddLine("#include <dbus/bus.h>");
+  text.AddLine("#include <dbus/message.h>");
+  text.AddLine("#include <dbus/object_path.h>");
+  text.AddLine("#include <dbus/object_proxy.h>");
+  text.AddBlankLine();
+
+  vector<string> namespaces;
+  string class_name;
+  CHECK(GetNamespacesAndClassName(interface.name, &namespaces, &class_name));
+  for (const auto& space : namespaces) {
+    text.AddLine(StringPrintf("namespace %s {", space.c_str()));
+  }
+  text.AddBlankLine();
+
+  string proxy_name = StringPrintf("%sProxy", class_name.c_str());
+  text.AddLine(StringPrintf("class %s {", proxy_name.c_str()));
+  text.AddLineWithOffset("public:", kScopeOffset);
+  text.PushOffset(kBlockOffset);
+  AddMethodInterface(interface, &text);
+  AddConstructor(interface, proxy_name, &text);
+  AddDestructor(proxy_name, &text);
+  AddSignalConnectedCallback(&text);
+  for (const auto& method : interface.methods) {
+    AddMethodProxy(method, interface.name, &text);
+  }
+
+  text.PopOffset();
+  text.AddBlankLine();
+  text.AddLineWithOffset("private:", kScopeOffset);
+
+  text.PushOffset(kBlockOffset);
+  text.AddLine("scoped_refptr<dbus::Bus> bus_;");
+  text.AddLine("std::string service_name_;");
+  text.AddLine("dbus::ObjectPath object_path_;");
+  text.AddLine("dbus::ObjectProxy* dbus_object_proxy_;");
+  text.AddBlankLine();
+
+  text.AddLine(StringPrintf(
+      "DISALLOW_COPY_AND_ASSIGN(%s);", proxy_name.c_str()));
+  text.PopOffset();
+  text.AddLine("};");
+
+  text.AddBlankLine();
+
+  for (auto it = namespaces.rbegin(); it != namespaces.rend(); ++it) {
+    text.AddLine(StringPrintf("}  // namespace %s", it->c_str()));
+  }
+  text.AddLine(StringPrintf("#endif  // %s", header_guard.c_str()));
+
+  return WriteTextToFile(output_file, text);
+}
+
+// static
+void ProxyGenerator::AddConstructor(const Interface& interface,
+                                      const string& class_name,
+                                      IndentedText* text) {
+  IndentedText block;
+  block.AddLine(StringPrintf("%s(", class_name.c_str()));
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("const scoped_refptr<dbus::Bus>& bus,");
+  block.AddLine("const std::string& service_name,");
+  block.AddLine("const std::string& object_path,");
+  block.AddLine("SignalReceiver* signal_receiver)");
+  block.AddLine(": bus_(bus),");
+  block.PushOffset(kBlockOffset);
+  block.AddLine("service_name_(service_name),");
+  block.AddLine("object_path_(object_path),");
+  block.AddLine("dbus_object_proxy_(");
+  block.AddLineWithOffset(
+      "bus_->GetObjectProxy(service_name_, object_path_)) {",
+      kLineContinuationOffset);
+  block.PopOffset();
+  block.PopOffset();
+  block.PushOffset(kBlockOffset);
+  for (const auto& signal : interface.signals) {
+    block.AddLine("chromeos::dbus_utils::ConnectToSignal(");
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine("dbus_object_proxy_,");
+    block.AddLine(StringPrintf("\"%s\",", interface.name.c_str()));
+    block.AddLine(StringPrintf("\"%s\",", signal.name.c_str()));
+    block.AddLine("base::Bind(");
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf(
+        "&SignalReceiver::%s,",
+        GetHandlerNameForSignal(signal.name).c_str()));
+    block.AddLine("base::Unretained(signal_receiver)),");
+    block.PopOffset();
+    block.AddLine("base::Bind(");
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf(
+        "&%s::OnDBusSignalConnected,", class_name.c_str()));
+    block.AddLine("base::Unretained(this)));");
+    block.PopOffset();
+    block.PopOffset();
+  }
+  block.PopOffset();
+  block.AddLine("}");
+
+  text->AddBlock(block);
+}
+
+// static
+void ProxyGenerator::AddDestructor(const string& class_name,
+                                   IndentedText* text) {
+  IndentedText block;
+  block.AddLine(StringPrintf("virtual ~%s() {", class_name.c_str()));
+  block.PushOffset(kBlockOffset);
+  block.AddLine("dbus_object_proxy_->Detach();");
+  block.AddLine(
+      "bus_->RemoveObjectProxy(service_name_, object_path_, base::Closure());");
+  block.PopOffset();
+  block.AddLine("}");
+  text->AddBlock(block);
+}
+
+// static
+void ProxyGenerator::AddSignalConnectedCallback(IndentedText* text) {
+  IndentedText block;
+  block.AddLine("void OnDBusSignalConnected(");
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("const std::string& interface,");
+  block.AddLine("const std::string& signal,");
+  block.AddLine("bool success) {");
+  block.PopOffset();
+  block.PushOffset(kBlockOffset);
+  block.AddLine("if (!success) {");
+  block.PushOffset(kBlockOffset);
+  block.AddLine("LOG(ERROR)");
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("<< \"Failed to connect to \" << interface << \".\" << signal");
+  block.AddLine("<< \" for \" << service_name_ << \" at \"");
+  block.AddLine("<< object_path_.value();");
+  block.PopOffset();
+  block.PopOffset();
+  block.AddLine("}");
+  block.PopOffset();
+  block.AddLine("}");
+  text->AddBlock(block);
+}
+
+// static
+void ProxyGenerator::AddMethodInterface(const Interface& interface,
+                                        IndentedText* text) {
+  IndentedText block;
+  block.AddLine("class SignalReceiver {");
+  block.AddLineWithOffset("public:", kScopeOffset);
+  block.PushOffset(kBlockOffset);
+  DbusSignature signature;
+  for (const auto& signal : interface.signals) {
+    if (signal.arguments.empty()) {
+      block.AddLine(StringPrintf("virtual void %s() {}",
+                                 GetHandlerNameForSignal(signal.name).c_str()));
+      continue;
+    }
+    block.AddLine(StringPrintf("virtual void %s(",
+                               GetHandlerNameForSignal(signal.name).c_str()));
+    block.PushOffset(kLineContinuationOffset);
+    string last_argument;
+    vector<string> argument_types;
+    for (const auto& argument : signal.arguments) {
+      if (!last_argument.empty()) {
+         block.AddLine(StringPrintf("%s,", last_argument.c_str()));
+      }
+      CHECK(signature.Parse(argument.type, &last_argument));
+      if (!IsIntegralType(last_argument)) {
+        last_argument = StringPrintf("const %s&", last_argument.c_str());
+      }
+    }
+    block.AddLine(StringPrintf("%s) {}", last_argument.c_str()));
+    block.PopOffset();
+  }
+  block.PopOffset();
+  block.AddLine("};");
+
+  text->AddBlock(block);
+}
+
+// static
+void ProxyGenerator::AddMethodProxy(const Interface::Method& method,
+                                    const string& interface_name,
+                                    IndentedText* text) {
+  IndentedText block;
+  string return_type("void");
+  bool is_void_method = true;
+  DbusSignature signature;
+  if (!method.output_arguments.empty()) {
+    if (method.output_arguments.size() > 1) {
+      LOG(WARNING) << "Method " << method.name << " has "
+                   << method.output_arguments.size()
+                   << " output arguments which is unsupported.";
+      return;
+    }
+    CHECK(signature.Parse(method.output_arguments[0].type, &return_type));
+    is_void_method = false;
+  }
+  block.AddLine(StringPrintf("virtual %s %s(",
+                             return_type.c_str(), method.name.c_str()));
+  block.PushOffset(kLineContinuationOffset);
+  vector<string> argument_names;
+  int argument_number = 0;
+  for (const auto& argument : method.input_arguments) {
+    string argument_type;
+    CHECK(signature.Parse(argument.type, &argument_type));
+    if (!IsIntegralType(argument_type)) {
+      argument_type = StringPrintf("const %s&", argument_type.c_str());
+    }
+    ++argument_number;
+    string argument_prefix(argument.name.empty() ?
+                           StringPrintf("argument%d", argument_number) :
+                           argument.name);
+    string argument_name(StringPrintf("%s_in", argument_prefix.c_str()));
+    argument_names.push_back(argument_name);
+    block.AddLine(StringPrintf(
+        "%s %s,", argument_type.c_str(), argument_name.c_str()));
+  }
+  block.AddLine("chromeos::ErrorPtr* error) {");
+  block.PopOffset();
+  block.PushOffset(kBlockOffset);
+
+  block.AddLine("auto response = chromeos::dbus_utils::CallMethodAndBlock(");
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("dbus_object_proxy_,");
+  block.AddLine(StringPrintf("\"%s\",", interface_name.c_str()));
+  block.AddLine(StringPrintf("\"%s\",", method.name.c_str()));
+  string last_argument = "error";
+  for (const auto& argument_name : argument_names) {
+    block.AddLine(StringPrintf("%s,", last_argument.c_str()));
+    last_argument = argument_name;
+  }
+  block.AddLine(StringPrintf("%s);", last_argument.c_str()));
+  block.PopOffset();
+
+  if (!is_void_method) {
+    block.AddLine(StringPrintf("%s result{};", return_type.c_str()));
+  }
+  block.AddLine("if (!response) {");
+  block.AddLineWithOffset(StringPrintf(
+      "return%s;", is_void_method ? "" : " result"), kBlockOffset);
+  block.AddLine("}");
+  block.AddLine("chromeos::dbus_utils::ExtractMethodCallResults(");
+  block.AddLineWithOffset(StringPrintf("response.get(), error%s);",
+                                       is_void_method ? "" : ", &result"),
+                          kLineContinuationOffset);
+  if (!is_void_method) {
+    block.AddLine("return result;");
+  }
+
+  block.PopOffset();
+  block.AddLine("}");
+
+  text->AddBlock(block);
+}
+
+// static
+string ProxyGenerator::GetHandlerNameForSignal(const string& signal) {
+  return StringPrintf("On%sSignal", signal.c_str());
+}
+
+}  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/proxy_generator.h b/chromeos-dbus-bindings/proxy_generator.h
new file mode 100644
index 0000000..66dd8ba
--- /dev/null
+++ b/chromeos-dbus-bindings/proxy_generator.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_
+#define CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "chromeos-dbus-bindings/header_generator.h"
+#include "chromeos-dbus-bindings/indented_text.h"
+#include "chromeos-dbus-bindings/interface.h"
+
+namespace base {
+
+class FilePath;
+
+}  // namespace base
+
+namespace chromeos_dbus_bindings {
+
+class IndentedText;
+struct Interface;
+
+class ProxyGenerator : public HeaderGenerator {
+ public:
+  static bool GenerateProxy(const Interface& interface,
+                            const base::FilePath& output_file);
+
+ private:
+  friend class ProxyGeneratorTest;
+
+  // Generates the constructor and destructor for the proxy.
+  static void AddConstructor(const Interface& interface,
+                             const std::string& class_name,
+                             IndentedText* text);
+  static void AddDestructor(const std::string& class_name,
+                            IndentedText* text);
+
+  // Generates a callback for signal receiver registration completion.
+  static void AddSignalConnectedCallback(IndentedText *text);
+
+  // Generates the method signatures for signal receivers.
+  static void AddMethodInterface(const Interface& interface,
+                                 IndentedText* text);
+
+  // Generates a native C++ method which calls a D-Bus method on the proxy.
+  static void AddMethodProxy(const Interface::Method& interface,
+                             const std::string& interface_name,
+                             IndentedText* text);
+
+  // Generates the signal handler name for a given signal name.
+  static std::string GetHandlerNameForSignal(const std::string& signal);
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyGenerator);
+};
+
+}  // namespace chromeos_dbus_bindings
+
+#endif  // CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_
diff --git a/chromeos-dbus-bindings/proxy_generator_unittest.cc b/chromeos-dbus-bindings/proxy_generator_unittest.cc
new file mode 100644
index 0000000..1149de9
--- /dev/null
+++ b/chromeos-dbus-bindings/proxy_generator_unittest.cc
@@ -0,0 +1,248 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos-dbus-bindings/proxy_generator.h"
+
+#include <string>
+#include <vector>
+
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "chromeos-dbus-bindings/interface.h"
+
+using std::string;
+using std::vector;
+using testing::Test;
+
+namespace chromeos_dbus_bindings {
+
+namespace {
+
+const char kInterfaceName[] = "org.chromium.TestInterface";
+const char kMethod1Name[] = "Elements";
+const char kMethod1Return[] = "s";
+const char kMethod1Argument1[] = "s";
+const char kMethod1ArgumentName1[] = "space_walk";
+const char kMethod1Argument2[] = "ao";
+const char kMethod1ArgumentName2[] = "ramblin_man";
+const char kMethod2Name[] = "ReturnToPatagonia";
+const char kMethod2Return[] = "x";
+const char kMethod3Name[] = "NiceWeatherForDucks";
+const char kMethod3Argument1[] = "b";
+const char kMethod4Name[] = "ExperimentNumberSix";
+const char kSignal1Name[] = "Closer";
+const char kSignal2Name[] = "TheCurseOfKaZar";
+const char kSignal2Argument1[] = "as";
+const char kSignal2Argument2[] = "y";
+const char kExpectedContent[] = R"literal_string(
+#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 <chromeos/any.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/dbus/dbus_signal_handler.h>
+#include <chromeos/errors/error.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+class TestInterfaceProxy {
+ public:
+  class SignalReceiver {
+   public:
+    virtual void OnCloserSignal() {}
+    virtual void OnTheCurseOfKaZarSignal(
+        const std::vector<std::string>&,
+        uint8_t) {}
+  };
+  TestInterfaceProxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      const std::string& service_name,
+      const std::string& object_path,
+      SignalReceiver* signal_receiver)
+      : bus_(bus),
+        service_name_(service_name),
+        object_path_(object_path),
+        dbus_object_proxy_(
+            bus_->GetObjectProxy(service_name_, object_path_)) {
+    chromeos::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "Closer",
+        base::Bind(
+            &SignalReceiver::OnCloserSignal,
+            base::Unretained(signal_receiver)),
+        base::Bind(
+            &TestInterfaceProxy::OnDBusSignalConnected,
+            base::Unretained(this)));
+    chromeos::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "TheCurseOfKaZar",
+        base::Bind(
+            &SignalReceiver::OnTheCurseOfKaZarSignal,
+            base::Unretained(signal_receiver)),
+        base::Bind(
+            &TestInterfaceProxy::OnDBusSignalConnected,
+            base::Unretained(this)));
+  }
+  virtual ~TestInterfaceProxy() {
+    dbus_object_proxy_->Detach();
+    bus_->RemoveObjectProxy(service_name_, object_path_, base::Closure());
+  }
+  void OnDBusSignalConnected(
+      const std::string& interface,
+      const std::string& signal,
+      bool success) {
+    if (!success) {
+      LOG(ERROR)
+          << "Failed to connect to " << interface << "." << signal
+          << " for " << service_name_ << " at "
+          << object_path_.value();
+    }
+  }
+  virtual std::string Elements(
+      const std::string& space_walk_in,
+      const std::vector<dbus::ObjectPath>& ramblin_man_in,
+      chromeos::ErrorPtr* error) {
+    auto response = chromeos::dbus_utils::CallMethodAndBlock(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "Elements",
+        error,
+        space_walk_in,
+        ramblin_man_in);
+    std::string result{};
+    if (!response) {
+      return result;
+    }
+    chromeos::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, &result);
+    return result;
+  }
+  virtual int64_t ReturnToPatagonia(
+      chromeos::ErrorPtr* error) {
+    auto response = chromeos::dbus_utils::CallMethodAndBlock(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "ReturnToPatagonia",
+        error);
+    int64_t result{};
+    if (!response) {
+      return result;
+    }
+    chromeos::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, &result);
+    return result;
+  }
+  virtual void NiceWeatherForDucks(
+      bool argument1_in,
+      chromeos::ErrorPtr* error) {
+    auto response = chromeos::dbus_utils::CallMethodAndBlock(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "NiceWeatherForDucks",
+        error,
+        argument1_in);
+    if (!response) {
+      return;
+    }
+    chromeos::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+  virtual void ExperimentNumberSix(
+      chromeos::ErrorPtr* error) {
+    auto response = chromeos::dbus_utils::CallMethodAndBlock(
+        dbus_object_proxy_,
+        "org.chromium.TestInterface",
+        "ExperimentNumberSix",
+        error);
+    if (!response) {
+      return;
+    }
+    chromeos::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  std::string service_name_;
+  dbus::ObjectPath object_path_;
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestInterfaceProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+)literal_string";
+
+}  // namespace
+
+class ProxyGeneratorTest : public Test {
+ public:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  }
+
+ protected:
+  base::FilePath CreateInputFile(const string& contents) {
+    base::FilePath path;
+    EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path));
+    EXPECT_EQ(contents.size(),
+              base::WriteFile(path, contents.c_str(), contents.size()));
+    return path;
+  }
+
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(ProxyGeneratorTest, GenerateAdaptors) {
+  Interface interface;
+  interface.name = kInterfaceName;
+  interface.methods.emplace_back(
+      kMethod1Name,
+      vector<Interface::Argument>{
+          {kMethod1ArgumentName1, kMethod1Argument1},
+          {kMethod1ArgumentName2, kMethod1Argument2}},
+      vector<Interface::Argument>{{"", kMethod1Return}});
+  interface.methods.emplace_back(
+      kMethod2Name,
+      vector<Interface::Argument>{},
+      vector<Interface::Argument>{{"", kMethod2Return}});
+  interface.methods.emplace_back(
+      kMethod3Name,
+      vector<Interface::Argument>{{"", kMethod3Argument1}},
+      vector<Interface::Argument>{});
+  interface.methods.emplace_back(kMethod4Name);
+  interface.signals.emplace_back(kSignal1Name);
+  interface.signals.emplace_back(
+      kSignal2Name,
+      vector<Interface::Argument>{
+          {"", kSignal2Argument1},
+          {"", kSignal2Argument2}});
+  base::FilePath output_path = temp_dir_.path().Append("output.h");
+  EXPECT_TRUE(ProxyGenerator::GenerateProxy(interface, 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.
+  EXPECT_NE(string::npos, contents.find(kExpectedContent))
+      << "Expected to find the following content...\n"
+      << kExpectedContent << "...within content...\n" << contents;
+}
+
+}  // namespace chromeos_dbus_bindings