Avoid conversion of DocumentPath to String

Change-Id: I7d21cc95fc948118fd42a412fce8cb8e773855f5
diff --git a/core/api/src/main/java/org/onosproject/store/service/DocumentPath.java b/core/api/src/main/java/org/onosproject/store/service/DocumentPath.java
index 0198444..ed5de9f 100644
--- a/core/api/src/main/java/org/onosproject/store/service/DocumentPath.java
+++ b/core/api/src/main/java/org/onosproject/store/service/DocumentPath.java
@@ -18,10 +18,7 @@
 
 import com.google.common.collect.Comparators;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang.StringUtils;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -29,7 +26,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
-
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -50,7 +46,7 @@
     /** Root document tree path. */
     public static final DocumentPath ROOT = DocumentPath.from("root");
 
-    private final List<String> pathElements = Lists.newArrayList();
+    private final List<String> pathElements;
 
     /**
      * Private utility constructor for internal generation of partial paths only.
@@ -59,13 +55,14 @@
      */
     private DocumentPath(List<String> pathElements) {
         checkNotNull(pathElements);
-        this.pathElements.addAll(pathElements);
+        this.pathElements = ImmutableList.copyOf(pathElements);
     }
 
     /**
      * Constructs a new document path.
      * <p>
-     * New paths must contain at least one name and string names may NOT contain any period characters.
+     * New paths must contain at least one name and string names MUST NOT contain
+     * any path separator characters.
      * If one field is {@code null} that field will be ignored.
      *
      * @param nodeName the name of the last level of this path
@@ -79,14 +76,14 @@
             throw new IllegalDocumentNameException("'" + pathSeparator + "'" +
                     " are not allowed in names.");
         }
+
         if (parentPath != null) {
-            pathElements.addAll(parentPath.pathElements());
-        }
-        pathElements.add(nodeName);
-        if (pathElements.isEmpty()) {
-            throw new IllegalDocumentNameException("A document path must contain at" +
-                                                   "least one non-null" +
-                                                   "element.");
+            pathElements = ImmutableList.<String>builder()
+                            .addAll(parentPath.pathElements())
+                            .add(nodeName)
+                            .build();
+        } else {
+            pathElements = ImmutableList.of(nodeName);
         }
     }
 
@@ -163,7 +160,7 @@
      * @return a list of elements that make up this path
      */
     public List<String> pathElements() {
-        return ImmutableList.copyOf(pathElements);
+        return pathElements;
     }
 
     /**
@@ -175,7 +172,8 @@
      * @return {@code true} is yes; {@code false} otherwise.
      */
     public boolean isAncestorOf(DocumentPath other) {
-        return !other.equals(this) && other.toString().startsWith(toString());
+        return this.pathElements.size() < other.pathElements.size() &&
+               this.pathElements.equals(other.pathElements.subList(0, this.pathElements.size()));
     }
 
     /**
@@ -194,16 +192,35 @@
     /**
      * Returns the path that points to the least common ancestor of the specified
      * collection of paths.
+     *
      * @param paths collection of path
-     * @return path to least common ancestor
+     * @return path to least common ancestor or null if there is nothing in common
      */
     public static DocumentPath leastCommonAncestor(Collection<DocumentPath> paths) {
         if (CollectionUtils.isEmpty(paths)) {
             return null;
         }
-        return DocumentPath.from(StringUtils.getCommonPrefix(paths.stream()
-                    .map(DocumentPath::toString)
-                    .toArray(String[]::new)));
+        DocumentPath first = paths.iterator().next();
+
+        int maxComps = paths.stream()
+            .map(DocumentPath::pathElements)
+            .mapToInt(List::size)
+            .min()
+            .orElse(-1); // paths.size() will never be 0 here
+
+        for (int i = 0; i < maxComps; ++i) {
+            final int fi = i;
+            String comp = first.pathElements().get(i);
+            boolean isAllCommon = paths.stream()
+                .map(DocumentPath::pathElements)
+                .map(l -> l.get(fi))
+                .allMatch(c -> comp.equals(c));
+            if (!isAllCommon) {
+                return (i == 0) ? null :
+                       DocumentPath.from(first.pathElements.subList(0, i));
+            }
+        }
+        return DocumentPath.from(first.pathElements.subList(0, maxComps));
     }
 
     @Override
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
index 2478600..04b94ff 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
@@ -27,6 +27,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.Funnels;
 import com.google.common.hash.Hashing;
 import org.onlab.util.HexString;
 import org.onosproject.cluster.PartitionId;
@@ -53,6 +55,10 @@
  * distributed primitives to a collection of other {@link DistributedPrimitiveCreator creators}.
  */
 public class FederatedDistributedPrimitiveCreator implements DistributedPrimitiveCreator {
+
+    private static final Funnel<Iterable<? extends CharSequence>> STR_LIST_FUNNEL =
+                Funnels.sequentialFunnel(Funnels.unencodedCharsFunnel());
+
     private final TreeMap<PartitionId, DistributedPrimitiveCreator> members;
     private final List<PartitionId> sortedMemberPartitionIds;
     private final int buckets;
@@ -148,7 +154,10 @@
         Map<PartitionId, AsyncDocumentTree<V>> trees =
                 Maps.transformValues(members, part -> part.<V>newAsyncDocumentTree(name, serializer, ordering));
         Hasher<DocumentPath> hasher = key -> {
-            int bucket = Math.abs(Hashing.murmur3_32().hashUnencodedChars(String.valueOf(key)).asInt()) % buckets;
+            int bucket = (key == null) ? 0 :
+                    Math.abs(Hashing.murmur3_32()
+                                  .hashObject(key.pathElements(), STR_LIST_FUNNEL)
+                                  .asInt()) % buckets;
             return sortedMemberPartitionIds.get(Hashing.consistentHash(bucket, sortedMemberPartitionIds.size()));
         };
         return new PartitionedAsyncDocumentTree<>(name, trees, hasher);