AtomixDocumentTree support for filtering notifications by DocumentPath

Change-Id: I3f4f616bc4f2e488e5433e44f72bcd121b564b0d
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 1c43f4d..46b8415 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
@@ -17,10 +17,14 @@
 package org.onosproject.store.service;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -103,6 +107,46 @@
         return ImmutableList.copyOf(pathElements);
     }
 
+    /**
+     * Returns if the specified path belongs to a direct ancestor of the node pointed at by this path.
+     * <p>
+     * Example: {@code root.a} is a direct ancestor of {@code r.a.b.c}; while {@code r.a.x} is not.
+     *
+     * @param other other path
+     * @return {@code true} is yes; {@code false} otherwise.
+     */
+    public boolean isAncestorOf(DocumentPath other) {
+        return !other.equals(this) && other.toString().startsWith(toString());
+    }
+
+    /**
+     * Returns if the specified path is belongs to a subtree rooted this path.
+     * <p>
+     * Example: {@code root.a.b} and {@code root.a.b.c.d.e} are descendants of {@code r.a.b};
+     * while {@code r.a.x.c} is not.
+     *
+     * @param other other path
+     * @return {@code true} is yes; {@code false} otherwise.
+     */
+    public boolean isDescendentOf(DocumentPath other) {
+        return other.equals(this) || other.isAncestorOf(this);
+    }
+
+    /**
+     * 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
+     */
+    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)));
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(pathElements);
diff --git a/core/api/src/test/java/org/onosproject/store/service/DocumentPathTest.java b/core/api/src/test/java/org/onosproject/store/service/DocumentPathTest.java
new file mode 100644
index 0000000..2c0197b
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/store/service/DocumentPathTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.store.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link DocumentPath}.
+ */
+public class DocumentPathTest {
+
+    @Test
+    public void testConstruction() {
+        DocumentPath path = DocumentPath.from("root.a.b");
+        assertEquals(path.pathElements(), Arrays.asList("root", "a", "b"));
+        assertEquals(DocumentPath.from("root.a"), path.parent());
+    }
+
+    @Test
+    public void testAncestry() {
+        DocumentPath path1 = DocumentPath.from("root.a.b");
+        DocumentPath path2 = DocumentPath.from("root.a.d");
+        DocumentPath path3 = DocumentPath.from("root.a.b.c");
+        DocumentPath lca = DocumentPath.leastCommonAncestor(Arrays.asList(path1, path2, path3));
+        assertEquals(DocumentPath.from("root.a"), lca);
+        assertTrue(path1.isAncestorOf(path3));
+        assertFalse(path1.isAncestorOf(path2));
+        assertTrue(path3.isDescendentOf(path3));
+        assertTrue(path3.isDescendentOf(path1));
+        assertFalse(path3.isDescendentOf(path2));
+    }
+}