Split should have a matching Join, if only for convenient debugging output.

Change-Id: I68275a6410af706875f53540db4ef0242f414470
diff --git a/src/heap.cc b/src/heap.cc
index d9027d1..3a3ee5a 100644
--- a/src/heap.cc
+++ b/src/heap.cc
@@ -125,15 +125,10 @@
 
   arg_vector.push_back(strdup("--base=0x60000000"));
 
-  arg_vector.push_back(NULL);
-
-  std::string command_line;
-  for (size_t i = 0; i < arg_vector.size() - 1; i++) {
-    command_line += arg_vector[i];
-    command_line += " ";
-  }
+  std::string command_line(Join(arg_vector, ' '));
   LOG(INFO) << command_line;
 
+  arg_vector.push_back(NULL);
   char** argv = &arg_vector[0];
 
   // fork and exec dex2oat
diff --git a/src/utils.cc b/src/utils.cc
index 78f524f..201834c 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -635,22 +635,41 @@
   return IsValidClassName(s, kDescriptor, '/');
 }
 
-void Split(const std::string& s, char delim, std::vector<std::string>& result) {
+void Split(const std::string& s, char separator, std::vector<std::string>& result) {
   const char* p = s.data();
   const char* end = p + s.size();
   while (p != end) {
-    if (*p == delim) {
+    if (*p == separator) {
       ++p;
     } else {
       const char* start = p;
-      while (++p != end && *p != delim) {
-        // Skip to the next occurrence of the delimiter.
+      while (++p != end && *p != separator) {
+        // Skip to the next occurrence of the separator.
       }
       result.push_back(std::string(start, p - start));
     }
   }
 }
 
+template <typename StringT>
+std::string Join(std::vector<StringT>& strings, char separator) {
+  if (strings.empty()) {
+    return "";
+  }
+
+  std::string result(strings[0]);
+  for (size_t i = 1; i < strings.size(); ++i) {
+    result += separator;
+    result += strings[i];
+  }
+  return result;
+}
+
+// Explicit instantiations.
+template std::string Join<std::string>(std::vector<std::string>& strings, char separator);
+template std::string Join<const char*>(std::vector<const char*>& strings, char separator);
+template std::string Join<char*>(std::vector<char*>& strings, char separator);
+
 void SetThreadName(const char* threadName) {
   ANNOTATE_THREAD_NAME(threadName); // For tsan.
 
diff --git a/src/utils.h b/src/utils.h
index 7807299..74a9802 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -256,9 +256,12 @@
   return ns * 1000 * 1000;
 }
 
-// Splits a string using the given delimiter character into a vector of
+// Splits a string using the given separator character into a vector of
 // strings. Empty strings will be omitted.
-void Split(const std::string& s, char delim, std::vector<std::string>& result);
+void Split(const std::string& s, char separator, std::vector<std::string>& result);
+
+// Joins a vector of strings into a single string, using the given separator.
+template <typename StringT> std::string Join(std::vector<StringT>& strings, char separator);
 
 // Returns the calling thread's tid. (The C libraries don't expose this.)
 pid_t GetTid();
diff --git a/src/utils_test.cc b/src/utils_test.cc
index 7f9f4b9..31fccc5 100644
--- a/src/utils_test.cc
+++ b/src/utils_test.cc
@@ -256,4 +256,42 @@
   EXPECT_EQ(expected, actual);
 }
 
+TEST_F(UtilsTest, Join) {
+  std::vector<std::string> strings;
+
+  strings.clear();
+  EXPECT_EQ("", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("foo");
+  EXPECT_EQ("foo", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("");
+  strings.push_back("foo");
+  EXPECT_EQ(":foo", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("foo");
+  strings.push_back("");
+  EXPECT_EQ("foo:", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("");
+  strings.push_back("foo");
+  strings.push_back("");
+  EXPECT_EQ(":foo:", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("foo");
+  strings.push_back("bar");
+  EXPECT_EQ("foo:bar", Join(strings, ':'));
+
+  strings.clear();
+  strings.push_back("foo");
+  strings.push_back("bar");
+  strings.push_back("baz");
+  EXPECT_EQ("foo:bar:baz", Join(strings, ':'));
+}
+
 }  // namespace art