[incfs] Optimize path::join for appending

Append path strings to the first argument if it's an rvalue

Bug: 183435580
Test: atest libincfs-test service.incremental_test
Change-Id: I52c4a1f0e4ad3547aeccac96a3393323e3be9adb
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
index 338659d..bf4e9616 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,19 +44,20 @@
                                         PathCharsLess());
 }
 
-static void preparePathComponent(std::string_view& path, bool trimFront) {
-    if (trimFront) {
-        while (!path.empty() && path.front() == '/') {
-            path.remove_prefix(1);
-        }
+static void preparePathComponent(std::string_view& path, bool trimAll) {
+    // need to check for double front slash as a single one has a separate meaning in front
+    while (!path.empty() && path.front() == '/' &&
+           (trimAll || (path.size() > 1 && path[1] == '/'))) {
+        path.remove_prefix(1);
     }
-    while (!path.empty() && path.back() == '/') {
+    // for the back we don't care about double-vs-single slash difference
+    while (path.size() > !trimAll && path.back() == '/') {
         path.remove_suffix(1);
     }
 }
 
 void details::append_next_path(std::string& target, std::string_view path) {
-    preparePathComponent(path, true);
+    preparePathComponent(path, !target.empty());
     if (path.empty()) {
         return;
     }
diff --git a/services/incremental/path.h b/services/incremental/path.h
index 3e5fd21..e12e1d0 100644
--- a/services/incremental/path.h
+++ b/services/incremental/path.h
@@ -89,15 +89,25 @@
 bool startsWith(std::string_view path, std::string_view prefix);
 
 template <class... Paths>
-std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
-    std::string result;
+std::string join(std::string&& first, std::string_view second, Paths&&... paths) {
+    std::string& result = first;
     {
         using std::size;
         result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
     }
-    result.assign(first);
-    (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
-    return result;
+    (details::append_next_path(result, second), ...,
+     details::append_next_path(result, std::forward<Paths>(paths)));
+    return std::move(result);
+}
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+    return path::join(std::string(), first, second, std::forward<Paths>(paths)...);
+}
+
+template <class... Paths>
+std::string join(const char* first, std::string_view second, Paths&&... paths) {
+    return path::join(std::string_view(first), second, std::forward<Paths>(paths)...);
 }
 
 } // namespace android::incremental::path
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
index cbe479e1..4a8ae5b 100644
--- a/services/incremental/test/path_test.cpp
+++ b/services/incremental/test/path_test.cpp
@@ -40,4 +40,24 @@
     EXPECT_TRUE(!PathLess()("/a/b", "/a"));
 }
 
+TEST(Path, Join) {
+    EXPECT_STREQ("", path::join("", "").c_str());
+
+    EXPECT_STREQ("/", path::join("", "/").c_str());
+    EXPECT_STREQ("/", path::join("/", "").c_str());
+    EXPECT_STREQ("/", path::join("/", "/").c_str());
+    EXPECT_STREQ("/", path::join("/"s, "/").c_str());
+    EXPECT_STREQ("/", path::join("/"sv, "/").c_str());
+    EXPECT_STREQ("/", path::join("/", "/", "/", "/", "/", "/", "/", "/", "/", "/").c_str());
+
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b/"s, "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b/", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b/", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("", "", "/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join(""s, "", "/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b", "", "", "/", "", "/", "/", "/c", "d").c_str());
+}
+
 } // namespace android::incremental::path