chromeos-dbus-bindings: Generate Interface

Generate an interface class that contains pure-virtual methods
for each method supported by the interface.  Also generate a
controller that registers these methods with an exported DBus
interface.

BUG=chromium:404505
TEST=New unit test

Change-Id: Ib9eacdd822982e6e992d10fc21a4eec804489b45
Reviewed-on: https://chromium-review.googlesource.com/218749
Reviewed-by: Mike Frysinger <vapier@chromium.org>
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
new file mode 100644
index 0000000..7057bc9
--- /dev/null
+++ b/chromeos-dbus-bindings/adaptor_generator.cc
@@ -0,0 +1,226 @@
+// 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/adaptor_generator.h"
+
+#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"
+#include "chromeos-dbus-bindings/indented_text.h"
+#include "chromeos-dbus-bindings/interface.h"
+
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace chromeos_dbus_bindings {
+
+namespace {
+const int kScopeOffset = 1;
+const int kBlockOffset = 2;
+const int kLineContinuationOffset = 4;
+}  // namespace
+
+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);
+  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.AddLine("#include <base/macros.h>");
+  text.AddLine("#include <dbus/object_path.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("");
+
+  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.AddLine("");
+
+  string adaptor_name = StringPrintf("%sAdaptor", class_name.c_str());
+  text.AddLine(StringPrintf("class %s {", adaptor_name.c_str()));
+  text.AddLineWithOffset("public:", kScopeOffset);
+  string method_interface = StringPrintf("%sAdaptorMethodInterface",
+                                         class_name.c_str());
+  text.PushOffset(kBlockOffset);
+  AddMethodInterface(interface, method_interface, &text);
+  AddConstructor(interface, adaptor_name, method_interface, &text);
+  text.AddLine(StringPrintf("virtual ~%s() = default;", adaptor_name.c_str()));
+  text.AddLine("virtual void OnRegisterComplete(bool success) {}");
+  text.PopOffset();
+
+  text.AddLineWithOffset("private:", kScopeOffset);
+
+  text.PushOffset(kBlockOffset);
+  text.AddLine(StringPrintf("%s* interface_;  // Owned by caller.",
+                            method_interface.c_str()));
+  text.AddLine("chromeos::dbus_utils::DBusObject dbus_object_;");
+  text.AddLine(StringPrintf(
+      "DISALLOW_COPY_AND_ASSIGN(%s);", adaptor_name.c_str()));
+  text.PopOffset();
+
+  text.AddLine("};");
+  text.AddLine("");
+
+  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;
+}
+
+// static
+void AdaptorGenerator::AddConstructor(const Interface& interface,
+                                      const string& class_name,
+                                      const string& method_interface,
+                                      IndentedText *text) {
+  IndentedText block;
+  block.AddLine(StringPrintf("%s(", class_name.c_str()));
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("chromeos::dbus_utils::ExportedObjectManager* object_manager,");
+  block.AddLine("const std::string& object_path,");
+  block.AddLine(StringPrintf("%s* interface)  // Owned by caller.",
+                             method_interface.c_str()));
+  block.AddLine(": interface_(interface),");
+  block.PushOffset(kBlockOffset);
+  block.AddLine("dbus_object_(");
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLine("object_manager,");
+  block.AddLine("object_manager->GetBus(),");
+  block.AddLine("dbus::ObjectPath(object_path)) {");
+  block.PopOffset();
+  block.PopOffset();
+  block.PopOffset();
+  block.PushOffset(kBlockOffset);
+  block.AddLine("auto* itf =");
+  block.AddLineWithOffset(StringPrintf(
+      "dbus_object_.AddOrGetInterface(\"%s\");", interface.name.c_str()),
+      kLineContinuationOffset);
+  for (const auto& method : interface.methods) {
+    block.AddLine("itf->AddMethodHandler(");
+    block.PushOffset(kLineContinuationOffset);
+    block.AddLine(StringPrintf("\"%s\",", method.name.c_str()));
+    block.AddLine("base::Unretained(interface_),");
+    block.AddLine(StringPrintf("&%s::%s);",
+                              method_interface.c_str(),
+                              method.name.c_str()));
+    block.PopOffset();
+  }
+  block.AddLine("dbus_object_.RegisterAsync(base::Bind(");
+  block.AddLineWithOffset(
+      StringPrintf("&%s::OnRegisterComplete, base::Unretained(this)));",
+                   class_name.c_str()),
+      kLineContinuationOffset);
+  block.PopOffset();
+  block.AddLine("}");
+  text->AddBlock(block);
+}
+
+// static
+void AdaptorGenerator::AddMethodInterface(const Interface& interface,
+                                          const string& class_name,
+                                          IndentedText *text) {
+  IndentedText block;
+  block.AddLine(StringPrintf("class %s {", class_name.c_str()));
+  block.AddLineWithOffset("public:", kScopeOffset);
+  DbusSignature signature;
+  block.PushOffset(kBlockOffset);
+  for (const auto& method : interface.methods) {
+    string return_type("void");
+    if (!method.output_arguments.empty()) {
+      CHECK_EQ(1UL, method.output_arguments.size())
+          << "Method " << method.name << " has "
+          << method.output_arguments.size()
+          << " output arguments which is invalid.";
+      CHECK(signature.Parse(method.output_arguments[0].type, &return_type));
+    }
+    block.AddLine(StringPrintf("virtual %s %s(",
+                               return_type.c_str(), method.name.c_str()));
+    block.PushOffset(kLineContinuationOffset);
+    string last_argument = "chromeos::ErrorPtr* /* error */";
+    for (const auto& argument : method.input_arguments) {
+      block.AddLine(last_argument + ",");
+      CHECK(signature.Parse(argument.type, &last_argument));
+      if (!IsIntegralType(last_argument)) {
+        last_argument = StringPrintf("const %s&", last_argument.c_str());
+      }
+      if (!argument.name.empty()) {
+        last_argument.append(StringPrintf(" /* %s */", argument.name.c_str()));
+      }
+    }
+    block.AddLine(last_argument + ") = 0;");
+    block.PopOffset();
+  }
+  block.PopOffset();
+  block.AddLine("};");
+
+  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
new file mode 100644
index 0000000..a9413a1
--- /dev/null
+++ b/chromeos-dbus-bindings/adaptor_generator.h
@@ -0,0 +1,65 @@
+// 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_ADAPTOR_GENERATOR_H_
+#define CHROMEOS_DBUS_BINDINGS_ADAPTOR_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "chromeos-dbus-bindings/indented_text.h"
+
+namespace base {
+
+class FilePath;
+
+}  // namespace base
+
+namespace chromeos_dbus_bindings {
+
+class IndentedText;
+struct Interface;
+
+class AdaptorGenerator {
+ public:
+  AdaptorGenerator() = default;
+  virtual ~AdaptorGenerator() = default;
+
+  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,
+                             const std::string& method_interface,
+                             IndentedText *text);
+
+  // Generates the method interface class.
+  static void AddMethodInterface(const Interface& interface,
+                                 const std::string& class_name,
+                                 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);
+};
+
+}  // namespace chromeos_dbus_bindings
+
+#endif  // CHROMEOS_DBUS_BINDINGS_ADAPTOR_GENERATOR_H_
diff --git a/chromeos-dbus-bindings/adaptor_generator_unittest.cc b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
new file mode 100644
index 0000000..4ba313a
--- /dev/null
+++ b/chromeos-dbus-bindings/adaptor_generator_unittest.cc
@@ -0,0 +1,146 @@
+// 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/adaptor_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 kMethod0Name[] = "Kaneda";
+const char kMethod0Return[] = "s";
+const char kMethod0Argument0[] = "s";
+const char kMethod0ArgumentName0[] = "iwata";
+const char kMethod0Argument1[] = "ao";
+const char kMethod0ArgumentName1[] = "clarke";
+const char kMethod1Name[] = "Tetsuo";
+const char kMethod1Argument1[] = "i";
+const char kMethod1Return[] = "x";
+const char kMethod2Name[] = "Kei";
+const char kInterfaceName[] = "org.chromium.TestInterface";
+const char kExpectedContent[] = R"literal_string(
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <chromeos/dbus/exported_object_manager.h>
+#include <chromeos/variant_dictionary.h>
+
+namespace org {
+namespace chromium {
+
+class TestInterfaceAdaptor {
+ public:
+  class TestInterfaceAdaptorMethodInterface {
+   public:
+    virtual std::string Kaneda(
+        chromeos::ErrorPtr* /* error */,
+        const std::string& /* iwata */,
+        const std::vector<dbus::ObjectPath>& /* clarke */) = 0;
+    virtual int64_t Tetsuo(
+        chromeos::ErrorPtr* /* error */,
+        int32_t) = 0;
+    virtual void Kei(
+        chromeos::ErrorPtr* /* error */) = 0;
+  };
+  TestInterfaceAdaptor(
+      chromeos::dbus_utils::ExportedObjectManager* object_manager,
+      const std::string& object_path,
+      TestInterfaceAdaptorMethodInterface* interface)  // Owned by caller.
+      : interface_(interface),
+        dbus_object_(
+            object_manager,
+            object_manager->GetBus(),
+            dbus::ObjectPath(object_path)) {
+    auto* itf =
+        dbus_object_.AddOrGetInterface("org.chromium.TestInterface");
+    itf->AddMethodHandler(
+        "Kaneda",
+        base::Unretained(interface_),
+        &TestInterfaceAdaptorMethodInterface::Kaneda);
+    itf->AddMethodHandler(
+        "Tetsuo",
+        base::Unretained(interface_),
+        &TestInterfaceAdaptorMethodInterface::Tetsuo);
+    itf->AddMethodHandler(
+        "Kei",
+        base::Unretained(interface_),
+        &TestInterfaceAdaptorMethodInterface::Kei);
+    dbus_object_.RegisterAsync(base::Bind(
+        &TestInterfaceAdaptor::OnRegisterComplete, base::Unretained(this)));
+  }
+  virtual ~TestInterfaceAdaptor() = default;
+  virtual void OnRegisterComplete(bool success) {}
+ private:
+  TestInterfaceAdaptorMethodInterface* interface_;  // Owned by caller.
+  chromeos::dbus_utils::DBusObject dbus_object_;
+  DISALLOW_COPY_AND_ASSIGN(TestInterfaceAdaptor);
+};
+
+}  // namespace chromium
+}  // namespace org
+)literal_string";
+
+}  // namespace
+class AdaptorGeneratorTest : 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_;
+  AdaptorGenerator generator_;
+};
+
+TEST_F(AdaptorGeneratorTest, GenerateAdaptors) {
+  Interface interface;
+  interface.name = kInterfaceName;
+  interface.methods.emplace_back(
+      kMethod0Name,
+      vector<Interface::Argument>{
+          Interface::Argument(kMethod0ArgumentName0, kMethod0Argument0),
+          Interface::Argument(kMethod0ArgumentName1, kMethod0Argument1) },
+      vector<Interface::Argument>{ Interface::Argument("", kMethod0Return) });
+  interface.methods.emplace_back(
+      kMethod1Name,
+      vector<Interface::Argument>{ Interface::Argument("", kMethod1Argument1) },
+      vector<Interface::Argument>{ Interface::Argument("", kMethod1Return) });
+  interface.methods.emplace_back(kMethod2Name);
+  base::FilePath output_path = temp_dir_.path().Append("output.h");
+  EXPECT_TRUE(generator_.GenerateAdaptor(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
diff --git a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
index 91c445a..1d92c87 100644
--- a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
+++ b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp
@@ -24,7 +24,9 @@
       'target_name': 'libchromeos-dbus-bindings',
       'type': 'static_library',
       'sources': [
+        'adaptor_generator.cc',
         'dbus_signature.cc',
+        'indented_text.cc',
         'method_name_generator.cc',
         'xml_interface_parser.cc',
       ],
@@ -71,7 +73,9 @@
           'includes': ['../common-mk/common_test.gypi'],
           'sources': [
             'testrunner.cc',
+            'adaptor_generator_unittest.cc',
             'dbus_signature_unittest.cc',
+            'indented_text_unittest.cc',
             'method_name_generator_unittest.cc',
             'xml_interface_parser_unittest.cc',
           ],
diff --git a/chromeos-dbus-bindings/dbus_signature.cc b/chromeos-dbus-bindings/dbus_signature.cc
index f3f62d8..922ad16 100644
--- a/chromeos-dbus-bindings/dbus_signature.cc
+++ b/chromeos-dbus-bindings/dbus_signature.cc
@@ -18,14 +18,14 @@
 const char DbusSignature::kArrayTypename[] = "std::vector";
 const char DbusSignature::kBooleanTypename[] = "bool";
 const char DbusSignature::kByteTypename[] = "uint8_t";
-const char DbusSignature::kDefaultObjectPathTypename[] = "std::string";
+const char DbusSignature::kDefaultObjectPathTypename[] = "dbus::ObjectPath";
 const char DbusSignature::kDictTypename[] = "std::map";
 const char DbusSignature::kDoubleTypename[] = "double";
 const char DbusSignature::kSigned16Typename[] = "int16_t";
 const char DbusSignature::kSigned32Typename[] = "int32_t";
 const char DbusSignature::kSigned64Typename[] = "int64_t";
 const char DbusSignature::kStringTypename[] = "std::string";
-const char DbusSignature::kUnixFdTypename[] = "int";
+const char DbusSignature::kUnixFdTypename[] = "dbus::FileDescriptor";
 const char DbusSignature::kUnsigned16Typename[] = "uint16_t";
 const char DbusSignature::kUnsigned32Typename[] = "uint32_t";
 const char DbusSignature::kUnsigned64Typename[] = "uint64_t";
diff --git a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
index f473e94..f02593c 100644
--- a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
+++ b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
@@ -8,6 +8,7 @@
 #include <base/files/file_path.h>
 #include <base/logging.h>
 
+#include "chromeos-dbus-bindings/adaptor_generator.h"
 #include "chromeos-dbus-bindings/method_name_generator.h"
 #include "chromeos-dbus-bindings/xml_interface_parser.h"
 
@@ -16,12 +17,15 @@
 static const char kHelp[] = "help";
 static const char kInput[] = "input";
 static const char kMethodNames[] = "method-names";
+static const char kAdaptor[] = "adaptor";
 static const char kHelpMessage[] = "\n"
     "Available Switches: \n"
     "  --input=<interface>\n"
     "    The input XML interface file (mandatory).\n"
     "  --method-names=<method name header filename>\n"
-    "    The output header file with string constants for each method name.\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";
 
 }  // namespace switches
 
@@ -48,7 +52,7 @@
     return 1;
   }
 
-  if (!cl->HasSwitch(switches::kMethodNames)) {
+  if (cl->HasSwitch(switches::kMethodNames)) {
     std::string method_name_file =
         cl->GetSwitchValueASCII(switches::kMethodNames);
     LOG(INFO) << "Outputting method names to " << method_name_file;
@@ -61,5 +65,17 @@
      }
   }
 
+  if (cl->HasSwitch(switches::kAdaptor)) {
+    std::string adaptor_file = cl->GetSwitchValueASCII(switches::kAdaptor);
+    LOG(INFO) << "Outputting adaptor to " << adaptor_file;
+    chromeos_dbus_bindings::AdaptorGenerator adaptor_generator;
+    if (!adaptor_generator.GenerateAdaptor(
+            parser.interface(),
+            base::FilePath(adaptor_file))) {
+      LOG(ERROR) << "Failed to output adaptor.";
+      return 1;
+     }
+  }
+
   return 0;
 }
diff --git a/chromeos-dbus-bindings/indented_text.cc b/chromeos-dbus-bindings/indented_text.cc
new file mode 100644
index 0000000..9a8a7f7
--- /dev/null
+++ b/chromeos-dbus-bindings/indented_text.cc
@@ -0,0 +1,64 @@
+// 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/indented_text.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+using std::string;
+using std::vector;
+
+namespace chromeos_dbus_bindings {
+
+IndentedText::IndentedText() : offset_(0) {}
+
+void IndentedText::AddBlock(const IndentedText& block) {
+  AddBlockWithOffset(block, 0);
+}
+
+void IndentedText::AddBlockWithOffset(const IndentedText& block, size_t shift) {
+  for (const auto& member : block.contents_) {
+    AddLineWithOffset(member.first, member.second + shift);
+  }
+}
+
+void IndentedText::AddLine(const std::string& line) {
+  AddLineWithOffset(line, 0);
+}
+
+void IndentedText::AddLineWithOffset(const std::string& line, size_t shift) {
+  contents_.emplace_back(line, shift + offset_);
+}
+
+string IndentedText::GetContents() const {
+  string output;
+  for (const auto& member : contents_) {
+    string indent(member.second, ' ');
+    output.append(indent + member.first + "\n");
+  }
+  return output;
+}
+
+void IndentedText::PushOffset(size_t shift) {
+  offset_ += shift;
+  offset_history_.push_back(shift);
+}
+
+void IndentedText::PopOffset() {
+  CHECK(!offset_history_.empty());
+  offset_ -= offset_history_.back();
+  offset_history_.pop_back();
+}
+
+void IndentedText::Reset() {
+  offset_ = 0;
+  offset_history_.clear();
+  contents_.clear();
+}
+
+}  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/indented_text.h b/chromeos-dbus-bindings/indented_text.h
new file mode 100644
index 0000000..059e85d
--- /dev/null
+++ b/chromeos-dbus-bindings/indented_text.h
@@ -0,0 +1,54 @@
+// 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_INDENTED_TEXT_H_
+#define CHROMEOS_DBUS_BINDINGS_INDENTED_TEXT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+
+namespace chromeos_dbus_bindings {
+
+class IndentedText {
+ public:
+  IndentedText();
+  virtual ~IndentedText() = default;
+
+  // Insert a block of indented text.
+  void AddBlock(const IndentedText& block);
+  void AddBlockWithOffset(const IndentedText& block, size_t shift);
+
+  // Add a line at the current indentation.
+  void AddLine(const std::string& line);
+  void AddLineWithOffset(const std::string& line, size_t shift);
+
+  // Return a string representing the indented text.
+  std::string GetContents() const;
+
+  // Add or remove an offset to the current stack of indentation offsets.
+  void PushOffset(size_t shift);
+  void PopOffset();
+
+  // Reset to initial state.
+  void Reset();
+
+
+ private:
+  using IndentedLine = std::pair<std::string, size_t>;
+
+  friend class IndentedTextTest;
+
+  size_t offset_;
+  std::vector<size_t> offset_history_;
+  std::vector<IndentedLine> contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(IndentedText);
+};
+
+}  // namespace chromeos_dbus_bindings
+
+#endif  // CHROMEOS_DBUS_BINDINGS_INDENTED_TEXT_H_
diff --git a/chromeos-dbus-bindings/indented_text_unittest.cc b/chromeos-dbus-bindings/indented_text_unittest.cc
new file mode 100644
index 0000000..ef60082
--- /dev/null
+++ b/chromeos-dbus-bindings/indented_text_unittest.cc
@@ -0,0 +1,128 @@
+// 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/indented_text.h"
+
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using std::string;
+using std::vector;
+using testing::ElementsAre;
+using testing::Test;
+
+namespace chromeos_dbus_bindings {
+
+class IndentedTextTest : public Test {
+ protected:
+  size_t GetOffset() const { return text_.offset_; }
+  const vector<size_t>& GetHistory() const { return text_.offset_history_; }
+  IndentedText text_;
+};
+
+TEST_F(IndentedTextTest, Constructor) {
+  EXPECT_EQ("", text_.GetContents());
+  EXPECT_EQ(0, GetOffset());
+  EXPECT_TRUE(GetHistory().empty());
+}
+
+TEST_F(IndentedTextTest, AddLine) {
+  const char kTestString0[] = "test";
+  text_.AddLine(kTestString0);
+  EXPECT_EQ(string(kTestString0) + "\n", text_.GetContents());
+  EXPECT_EQ(0, GetOffset());
+  EXPECT_TRUE(GetHistory().empty());
+
+  const char kTestString1[] = "me";
+  text_.AddLine(kTestString1);
+  EXPECT_EQ(string(kTestString0) + "\n" + kTestString1 + "\n",
+            text_.GetContents());
+  EXPECT_EQ(0, GetOffset());
+  EXPECT_TRUE(GetHistory().empty());
+}
+
+TEST_F(IndentedTextTest, AddLineWithOffset) {
+  const char kTestString[] = "test";
+  const int kShift = 4;
+  text_.AddLineWithOffset(kTestString, kShift);
+  EXPECT_EQ(string(kShift, ' ') + kTestString + "\n", text_.GetContents());
+}
+
+TEST_F(IndentedTextTest, AddBlock) {
+  IndentedText block0;
+  const char kTestString[] = "test";
+  block0.AddLineWithOffset(kTestString, 10);
+  block0.AddLineWithOffset(kTestString, 20);
+  IndentedText block1;
+  block1.AddLineWithOffset(kTestString, 5);
+  block1.AddLineWithOffset(kTestString, 15);
+  text_.AddBlock(block0);
+  text_.AddBlock(block1);
+  EXPECT_EQ(block0.GetContents() + block1.GetContents(), text_.GetContents());
+}
+
+TEST_F(IndentedTextTest, AddBlockWithOffset) {
+  const char kTestString[] = "test";
+  IndentedText block;
+  const int kOffset0 = 0;
+  block.AddLineWithOffset(kTestString, kOffset0);
+  const int kOffset1 = 4;
+  block.AddLineWithOffset(kTestString, kOffset1);
+  const int kOffset2 = 20;
+  text_.AddBlockWithOffset(block, kOffset2);
+  EXPECT_EQ(string(kOffset2 + kOffset0, ' ') + kTestString + "\n" +
+            string(kOffset2 + kOffset1, ' ') + kTestString + "\n",
+            text_.GetContents());
+}
+
+TEST_F(IndentedTextTest, PushPop) {
+  const char kTestString[] = "test";
+  text_.AddLine(kTestString);
+
+  const int kShift0 = 2;
+  text_.PushOffset(kShift0);
+  EXPECT_EQ(2, GetOffset());
+  EXPECT_THAT(GetHistory(), ElementsAre(kShift0));
+  text_.AddLine(kTestString);
+
+  const int kShift1 = 4;
+  text_.PushOffset(kShift1);
+  EXPECT_EQ(kShift0 + kShift1, GetOffset());
+  EXPECT_THAT(GetHistory(), ElementsAre(kShift0, kShift1));
+  text_.AddLine(kTestString);
+
+  text_.PopOffset();
+  text_.AddLine(kTestString);
+  EXPECT_EQ(2, GetOffset());
+  EXPECT_THAT(GetHistory(), ElementsAre(kShift0));
+
+  text_.PopOffset();
+  text_.AddLine(kTestString);
+  EXPECT_EQ(0, GetOffset());
+  EXPECT_TRUE(GetHistory().empty());
+
+  EXPECT_EQ(string(kTestString) + "\n" +
+            string(kShift0, ' ') + kTestString + "\n" +
+            string(kShift0 + kShift1, ' ') + kTestString + "\n" +
+            string(kShift0, ' ') + kTestString + "\n" +
+            string(kTestString) + "\n",
+            text_.GetContents());
+}
+
+TEST_F(IndentedTextTest, Reset) {
+  text_.PushOffset(10);
+  text_.AddLine("test");
+  EXPECT_NE("", text_.GetContents());
+  EXPECT_NE(0, GetOffset());
+  EXPECT_FALSE(GetHistory().empty());
+  text_.Reset();
+  EXPECT_EQ("", text_.GetContents());
+  EXPECT_EQ(0, GetOffset());
+  EXPECT_TRUE(GetHistory().empty());
+}
+
+}  // namespace chromeos_dbus_bindings
diff --git a/chromeos-dbus-bindings/method_name_generator.cc b/chromeos-dbus-bindings/method_name_generator.cc
index 7ffebea..b2d49f4 100644
--- a/chromeos-dbus-bindings/method_name_generator.cc
+++ b/chromeos-dbus-bindings/method_name_generator.cc
@@ -9,6 +9,7 @@
 #include <base/file_util.h>
 #include <base/files/file_path.h>
 #include <base/logging.h>
+#include <base/strings/stringprintf.h>
 
 #include "chromeos-dbus-bindings/interface.h"
 
@@ -17,19 +18,20 @@
 namespace chromeos_dbus_bindings {
 
 // static
-const char MethodNameGenerator::kLineTerminator[] = "\";\n";
-const char MethodNameGenerator::kNamePrefix[] = "const char k";
-const char MethodNameGenerator::kNameSeparator[] = "Method[] = \"";
+string MethodNameGenerator::GenerateMethodNameConstant(
+    const string& method_name) {
+  return "k" + method_name + "Method";
+}
 
 bool MethodNameGenerator::GenerateMethodNames(
     const Interface& interface,
     const base::FilePath& output_file) {
   string contents;
   for (const auto& method : interface.methods) {
-    const string& method_name = method.name;
     contents.append(
-        kNamePrefix + method_name + kNameSeparator + method_name +
-        kLineTerminator);
+        base::StringPrintf("const char %s[] = \"%s\";\n",
+                           GenerateMethodNameConstant(method.name).c_str(),
+                           method.name.c_str()));
   }
 
   int expected_write_return = contents.size();
diff --git a/chromeos-dbus-bindings/method_name_generator.h b/chromeos-dbus-bindings/method_name_generator.h
index 4bd0e8c..af2576f 100644
--- a/chromeos-dbus-bindings/method_name_generator.h
+++ b/chromeos-dbus-bindings/method_name_generator.h
@@ -5,6 +5,8 @@
 #ifndef CHROMEOS_DBUS_BINDINGS_METHOD_NAME_GENERATOR_H_
 #define CHROMEOS_DBUS_BINDINGS_METHOD_NAME_GENERATOR_H_
 
+#include <string>
+
 #include <base/macros.h>
 
 namespace base {
@@ -24,15 +26,11 @@
 
   virtual bool GenerateMethodNames(const Interface &interface,
                                    const base::FilePath& output_file);
+  static std::string GenerateMethodNameConstant(const std::string& method_name);
 
  private:
   friend class MethodNameGeneratorTest;
 
-  // Strings used.
-  static const char kLineTerminator[];
-  static const char kNamePrefix[];
-  static const char kNameSeparator[];
-
   DISALLOW_COPY_AND_ASSIGN(MethodNameGenerator);
 };