blob: 0198444c96198287a2c69b241e61458a52b32cfe [file] [log] [blame]
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.store.service;
18
Yuta HIGUCHI52e047f2017-09-11 16:43:21 -070019import com.google.common.collect.Comparators;
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070020import com.google.common.collect.ImmutableList;
21import com.google.common.collect.Lists;
22import org.apache.commons.collections.CollectionUtils;
23import org.apache.commons.lang.StringUtils;
24
Jordan Haltermanb0ac5902017-07-30 12:31:01 -070025import java.util.ArrayList;
Madan Jampaniad5b8c72016-09-12 15:05:01 -070026import java.util.Arrays;
Madan Jampani98094222016-09-15 21:12:46 -070027import java.util.Collection;
Yuta HIGUCHI52e047f2017-09-11 16:43:21 -070028import java.util.Comparator;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070029import java.util.Iterator;
30import java.util.List;
31import java.util.Objects;
32
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070033import static com.google.common.base.Preconditions.checkNotNull;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070034
Madan Jampani5bdebd52016-09-07 16:18:12 -070035/**
36 * Unique key for nodes in the {@link DocumentTree}.
37 */
38public class DocumentPath implements Comparable<DocumentPath> {
39
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070040 /** Default path separator. */
41 public static final String DEFAULT_SEPARATOR = "|";
42
43 /** Default path separator regex. */
44 public static final String DEFAULT_SEPARATOR_RE = "\\|";
45
46 // TODO: Add means to set the path separator and separator ERE.
47 private static String pathSeparator = DEFAULT_SEPARATOR;
48 private static String pathSeparatorRE = DEFAULT_SEPARATOR_RE;
49
Jordan Haltermancb1e02c2017-08-25 16:20:43 -070050 /** Root document tree path. */
51 public static final DocumentPath ROOT = DocumentPath.from("root");
52
Madan Jampani5bdebd52016-09-07 16:18:12 -070053 private final List<String> pathElements = Lists.newArrayList();
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070054
55 /**
56 * Private utility constructor for internal generation of partial paths only.
57 *
Madan Jampani5bdebd52016-09-07 16:18:12 -070058 * @param pathElements list of path elements
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070059 */
Madan Jampani5bdebd52016-09-07 16:18:12 -070060 private DocumentPath(List<String> pathElements) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070061 checkNotNull(pathElements);
Madan Jampani5bdebd52016-09-07 16:18:12 -070062 this.pathElements.addAll(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070063 }
64
65 /**
Thomas Vachuskac3984c62016-09-07 17:51:45 -070066 * Constructs a new document path.
Madan Jampani5bdebd52016-09-07 16:18:12 -070067 * <p>
68 * New paths must contain at least one name and string names may NOT contain any period characters.
69 * If one field is {@code null} that field will be ignored.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070070 *
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070071 * @param nodeName the name of the last level of this path
Madan Jampani5bdebd52016-09-07 16:18:12 -070072 * @param parentPath the path representing the parent leading up to this
73 * node, in the case of the root this should be {@code null}
74 * @throws IllegalDocumentNameException if both parameters are null or name contains an illegal character ('.')
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070075 */
76 public DocumentPath(String nodeName, DocumentPath parentPath) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070077 checkNotNull(nodeName, "Node name cannot be null");
78 if (nodeName.contains(pathSeparator)) {
Yuta HIGUCHI153d3582017-09-01 16:59:52 -070079 throw new IllegalDocumentNameException("'" + pathSeparator + "'" +
80 " are not allowed in names.");
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070081 }
82 if (parentPath != null) {
Madan Jampani5bdebd52016-09-07 16:18:12 -070083 pathElements.addAll(parentPath.pathElements());
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070084 }
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070085 pathElements.add(nodeName);
Madan Jampani5bdebd52016-09-07 16:18:12 -070086 if (pathElements.isEmpty()) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070087 throw new IllegalDocumentNameException("A document path must contain at" +
88 "least one non-null" +
89 "element.");
90 }
91 }
92
93 /**
Madan Jampaniad5b8c72016-09-12 15:05:01 -070094 * Creates a new {@code DocumentPath} from a period delimited path string.
95 *
96 * @param path path string
97 * @return {@code DocumentPath} instance
98 */
99 public static DocumentPath from(String path) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -0700100 return new DocumentPath(Arrays.asList(path.split(pathSeparatorRE)));
Madan Jampaniad5b8c72016-09-12 15:05:01 -0700101 }
102
103 /**
Jordan Haltermanb0ac5902017-07-30 12:31:01 -0700104 * Creates a new {@code DocumentPath} from a list of path elements.
105 *
106 * @param elements path elements
107 * @return {@code DocumentPath} instance
108 */
109 public static DocumentPath from(String... elements) {
110 return from(Arrays.asList(elements));
111 }
112
113 /**
114 * Creates a new {@code DocumentPath} from a list of path elements.
115 *
116 * @param elements path elements
117 * @return {@code DocumentPath} instance
118 */
119 public static DocumentPath from(List<String> elements) {
120 return new DocumentPath(elements);
121 }
122
123 /**
124 * Creates a new {@code DocumentPath} from a list of path elements.
125 *
126 * @param elements path elements
127 * @param child child element
128 * @return {@code DocumentPath} instance
129 */
130 public static DocumentPath from(List<String> elements, String child) {
131 elements = new ArrayList<>(elements);
132 elements.add(child);
133 return from(elements);
134 }
135
136 /**
Sithara Punnassery589fac22016-10-03 11:51:53 -0700137 * Returns the relative path to the given node.
138 *
139 * @return relative path to the given node.
140 */
141 public DocumentPath childPath() {
142 if (pathElements.size() <= 1) {
143 return null;
144 }
145 return new DocumentPath(this.pathElements.subList(pathElements.size() - 1, pathElements.size()));
146 }
147 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700148 * Returns a path for the parent of this node.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700149 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700150 * @return parent node path. If this path is for the root, returns {@code null}.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700151 */
152 public DocumentPath parent() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700153 if (pathElements.size() <= 1) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700154 return null;
155 }
Madan Jampani5bdebd52016-09-07 16:18:12 -0700156 return new DocumentPath(this.pathElements.subList(0, pathElements.size() - 1));
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700157 }
158
159 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700160 * Returns the list of path elements representing this path in correct
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700161 * order.
162 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700163 * @return a list of elements that make up this path
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700164 */
Madan Jampani5bdebd52016-09-07 16:18:12 -0700165 public List<String> pathElements() {
166 return ImmutableList.copyOf(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700167 }
168
Madan Jampani98094222016-09-15 21:12:46 -0700169 /**
170 * Returns if the specified path belongs to a direct ancestor of the node pointed at by this path.
171 * <p>
172 * Example: {@code root.a} is a direct ancestor of {@code r.a.b.c}; while {@code r.a.x} is not.
173 *
174 * @param other other path
175 * @return {@code true} is yes; {@code false} otherwise.
176 */
177 public boolean isAncestorOf(DocumentPath other) {
178 return !other.equals(this) && other.toString().startsWith(toString());
179 }
180
181 /**
182 * Returns if the specified path is belongs to a subtree rooted this path.
183 * <p>
184 * Example: {@code root.a.b} and {@code root.a.b.c.d.e} are descendants of {@code r.a.b};
185 * while {@code r.a.x.c} is not.
186 *
187 * @param other other path
188 * @return {@code true} is yes; {@code false} otherwise.
189 */
190 public boolean isDescendentOf(DocumentPath other) {
191 return other.equals(this) || other.isAncestorOf(this);
192 }
193
194 /**
195 * Returns the path that points to the least common ancestor of the specified
196 * collection of paths.
197 * @param paths collection of path
198 * @return path to least common ancestor
199 */
200 public static DocumentPath leastCommonAncestor(Collection<DocumentPath> paths) {
201 if (CollectionUtils.isEmpty(paths)) {
202 return null;
203 }
204 return DocumentPath.from(StringUtils.getCommonPrefix(paths.stream()
205 .map(DocumentPath::toString)
206 .toArray(String[]::new)));
207 }
208
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700209 @Override
210 public int hashCode() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700211 return Objects.hash(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700212 }
213
214 @Override
215 public boolean equals(Object obj) {
216 if (obj instanceof DocumentPath) {
217 DocumentPath that = (DocumentPath) obj;
Madan Jampani5bdebd52016-09-07 16:18:12 -0700218 return this.pathElements.equals(that.pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700219 }
220 return false;
221 }
222
223 @Override
224 public String toString() {
225 StringBuilder stringBuilder = new StringBuilder();
Madan Jampani5bdebd52016-09-07 16:18:12 -0700226 Iterator<String> iter = pathElements.iterator();
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700227 while (iter.hasNext()) {
228 stringBuilder.append(iter.next());
229 if (iter.hasNext()) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -0700230 stringBuilder.append(pathSeparator);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700231 }
232 }
233 return stringBuilder.toString();
234 }
235
236 @Override
Madan Jampani5bdebd52016-09-07 16:18:12 -0700237 public int compareTo(DocumentPath that) {
Yuta HIGUCHI52e047f2017-09-11 16:43:21 -0700238 return Comparators.lexicographical(Comparator.<String>naturalOrder())
239 .compare(this.pathElements, that.pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700240 }
241}