Baku - Derived SG suffixes

Adding derived syncgroup suffixes - syncgroup names must be unique even
if obtained from different app/db names, so it makes sense to in some
cases derive syncgroup names from app/db names to uniquely identify
syncgroups.

Change-Id: I1bb2ff4f0c9ea1171f281df167df033178c76ef2
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java
new file mode 100644
index 0000000..970d315
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java
@@ -0,0 +1,12 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.rx.syncbase;
+
+import java8.lang.FunctionalInterface;
+
+@FunctionalInterface
+public interface SgSuffixFormat<T> {
+    String get(final T parameters);
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java
new file mode 100644
index 0000000..38eac86
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java
@@ -0,0 +1,32 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.rx.syncbase;
+
+import com.google.common.escape.CharEscaperBuilder;
+import com.google.common.escape.Escaper;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class SgSuffixFormats {
+    public static final String SG_SUFFIX_DELIMITER = "/";
+    public static final Escaper SG_SUFFIX_COMPONENT_ESCAPER = new CharEscaperBuilder()
+            .addEscape('/', "\\/")
+            .addEscape('\\', "\\\\")
+            .toEscaper();
+
+    public static SgSuffixFormat<Object> simple(final String sgSuffix) {
+        return x -> sgSuffix;
+    }
+
+    public static SgSuffixFormat<UserSyncgroup.Parameters> discriminated(
+            final String customSuffix) {
+        return p -> SG_SUFFIX_COMPONENT_ESCAPER.escape(p.getDb().getRxApp().getName()) +
+                SG_SUFFIX_DELIMITER +
+                SG_SUFFIX_COMPONENT_ESCAPER.escape(p.getDb().getName()) +
+                SG_SUFFIX_DELIMITER +
+                customSuffix;
+    }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java
index f68e230..f942a40 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java
@@ -35,13 +35,15 @@
 // TODO(rosswang): Generalize this to other possible syncgroup strategies.
 @Accessors(prefix = "m")
 public abstract class UserSyncgroup extends RxSyncgroup {
-    public static final String DEFAULT_SYNCGROUP_SUFFIX = "user";
+    public static final SgSuffixFormat<Parameters> DEFAULT_SYNCGROUP_SUFFIX =
+            SgSuffixFormats.discriminated("user");
 
     public static class Builder {
         protected VContext mVContext;
         protected Observable<Blessings> mRxBlessings;
         protected SyncHostLevel mSyncHostLevel;
-        protected String mSgSuffix = DEFAULT_SYNCGROUP_SUFFIX;
+        protected SgSuffixFormat<? super Parameters> mSgSuffixFormat =
+                DEFAULT_SYNCGROUP_SUFFIX;
         protected RxDb mDb;
         protected Function<String, String> mDescriptionForUsername = u -> "User syncgroup for " + u;
         protected Function<AccessList, Permissions> mPermissionsForAcl;
@@ -68,22 +70,32 @@
             return this;
         }
 
-        public Builder sgSuffix(final String sgSuffix) {
-            mSgSuffix = sgSuffix;
+        public Builder sgSuffixFormat(final SgSuffixFormat sgSuffixFormat) {
+            mSgSuffixFormat = sgSuffixFormat;
             return this;
         }
 
+        public Builder sgSuffix(final String sgSuffix) {
+            return sgSuffixFormat(SgSuffixFormats.simple(sgSuffix));
+        }
+
+        public Builder discriminatedSgSuffix(final String customSuffix) {
+            return sgSuffixFormat(SgSuffixFormats.discriminated(customSuffix));
+        }
+
         public Builder db(final RxDb db) {
             mDb = db;
             return this;
         }
 
-        public Builder descriptionForUsername(final Function<String, String> descriptionForUsername) {
+        public Builder descriptionForUsername(
+                final Function<String, String> descriptionForUsername) {
             mDescriptionForUsername = descriptionForUsername;
             return this;
         }
 
-        public Builder permissionsForAcl(final Function<AccessList, Permissions> permissionsForAcl) {
+        public Builder permissionsForAcl(
+                final Function<AccessList, Permissions> permissionsForAcl) {
             mPermissionsForAcl = permissionsForAcl;
             return this;
         }
@@ -173,7 +185,7 @@
                 final Function<AccessList, Permissions> defaultPermissionsForAcl) {
             return new Parameters(mVContext, mRxBlessings,
                     mSyncHostLevel == null ? defaultSyncHost.get() : mSyncHostLevel,
-                    mSgSuffix, mDb, mDescriptionForUsername,
+                    mSgSuffixFormat, mDb, mDescriptionForUsername,
                     mPermissionsForAcl == null? defaultPermissionsForAcl : mPermissionsForAcl,
                     ImmutableList.copyOf(mPrefixes), mMemberInfo, mOnError);
         }
@@ -201,7 +213,7 @@
         VContext mVContext;
         Observable<Blessings> mRxBlessings;
         SyncHostLevel mSyncHostLevel;
-        String mSgSuffix;
+        SgSuffixFormat<? super Parameters> mSgSuffixFormat;
         RxDb mDb;
         Function<String, String> mDescriptionForUsername;
         Function<AccessList, Permissions> mPermissionsForAcl;
@@ -229,7 +241,8 @@
 
     private Observable<?> rxJoin(final ClientUser clientUser, final AccessList acl) {
         final String sgHost = mParams.getSyncHostLevel().getSyncgroupHostName(clientUser);
-        final String sgName = RxSyncbase.syncgroupName(sgHost, mParams.getSgSuffix());
+        final String sgName = RxSyncbase.syncgroupName(sgHost,
+                mParams.getSgSuffixFormat().get(mParams));
         final SyncgroupSpec spec = createSpec(clientUser, acl);
 
         return rxJoin(sgHost, sgName, spec);