blob: 25caabececc039f39936e958ef4c7e7e4954e7de [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;
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070021import org.apache.commons.collections.CollectionUtils;
Jordan Haltermanb0ac5902017-07-30 12:31:01 -070022import java.util.ArrayList;
Madan Jampaniad5b8c72016-09-12 15:05:01 -070023import java.util.Arrays;
Madan Jampani98094222016-09-15 21:12:46 -070024import java.util.Collection;
Yuta HIGUCHI52e047f2017-09-11 16:43:21 -070025import java.util.Comparator;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070026import java.util.Iterator;
27import java.util.List;
28import java.util.Objects;
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070029import static com.google.common.base.Preconditions.checkNotNull;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070030
Madan Jampani5bdebd52016-09-07 16:18:12 -070031/**
32 * Unique key for nodes in the {@link DocumentTree}.
33 */
34public class DocumentPath implements Comparable<DocumentPath> {
35
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070036 /** Default path separator. */
37 public static final String DEFAULT_SEPARATOR = "|";
38
39 /** Default path separator regex. */
40 public static final String DEFAULT_SEPARATOR_RE = "\\|";
41
42 // TODO: Add means to set the path separator and separator ERE.
43 private static String pathSeparator = DEFAULT_SEPARATOR;
44 private static String pathSeparatorRE = DEFAULT_SEPARATOR_RE;
45
Jordan Haltermancb1e02c2017-08-25 16:20:43 -070046 /** Root document tree path. */
47 public static final DocumentPath ROOT = DocumentPath.from("root");
48
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -070049 private final List<String> pathElements;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070050
51 /**
52 * Private utility constructor for internal generation of partial paths only.
53 *
Madan Jampani5bdebd52016-09-07 16:18:12 -070054 * @param pathElements list of path elements
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070055 */
Madan Jampani5bdebd52016-09-07 16:18:12 -070056 private DocumentPath(List<String> pathElements) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070057 checkNotNull(pathElements);
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -070058 this.pathElements = ImmutableList.copyOf(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070059 }
60
61 /**
Thomas Vachuskac3984c62016-09-07 17:51:45 -070062 * Constructs a new document path.
Madan Jampani5bdebd52016-09-07 16:18:12 -070063 * <p>
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -070064 * New paths must contain at least one name and string names MUST NOT contain
65 * any path separator characters.
Madan Jampani5bdebd52016-09-07 16:18:12 -070066 * If one field is {@code null} that field will be ignored.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070067 *
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070068 * @param nodeName the name of the last level of this path
Madan Jampani5bdebd52016-09-07 16:18:12 -070069 * @param parentPath the path representing the parent leading up to this
70 * node, in the case of the root this should be {@code null}
71 * @throws IllegalDocumentNameException if both parameters are null or name contains an illegal character ('.')
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070072 */
73 public DocumentPath(String nodeName, DocumentPath parentPath) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070074 checkNotNull(nodeName, "Node name cannot be null");
75 if (nodeName.contains(pathSeparator)) {
Yuta HIGUCHI153d3582017-09-01 16:59:52 -070076 throw new IllegalDocumentNameException("'" + pathSeparator + "'" +
77 " are not allowed in names.");
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070078 }
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -070079
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070080 if (parentPath != null) {
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -070081 pathElements = ImmutableList.<String>builder()
82 .addAll(parentPath.pathElements())
83 .add(nodeName)
84 .build();
85 } else {
86 pathElements = ImmutableList.of(nodeName);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070087 }
88 }
89
90 /**
Madan Jampaniad5b8c72016-09-12 15:05:01 -070091 * Creates a new {@code DocumentPath} from a period delimited path string.
92 *
93 * @param path path string
94 * @return {@code DocumentPath} instance
95 */
96 public static DocumentPath from(String path) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070097 return new DocumentPath(Arrays.asList(path.split(pathSeparatorRE)));
Madan Jampaniad5b8c72016-09-12 15:05:01 -070098 }
99
100 /**
Jordan Haltermanb0ac5902017-07-30 12:31:01 -0700101 * Creates a new {@code DocumentPath} from a list of path elements.
102 *
103 * @param elements path elements
104 * @return {@code DocumentPath} instance
105 */
106 public static DocumentPath from(String... elements) {
107 return from(Arrays.asList(elements));
108 }
109
110 /**
111 * Creates a new {@code DocumentPath} from a list of path elements.
112 *
113 * @param elements path elements
114 * @return {@code DocumentPath} instance
115 */
116 public static DocumentPath from(List<String> elements) {
117 return new DocumentPath(elements);
118 }
119
120 /**
121 * Creates a new {@code DocumentPath} from a list of path elements.
122 *
123 * @param elements path elements
124 * @param child child element
125 * @return {@code DocumentPath} instance
126 */
127 public static DocumentPath from(List<String> elements, String child) {
Yuta HIGUCHI2d1b5902018-02-15 14:49:23 -0800128 List<String> concat = new ArrayList<>(elements.size() + 1);
129 concat.addAll(elements);
130 concat.add(child);
131 return from(concat);
132 }
133
134 /**
135 * Creates a new {@code DocumentPath} from a list of path elements.
136 *
137 * @param elements path elements
138 * @param childElms child element
139 * @return {@code DocumentPath} instance
140 */
141 public static DocumentPath from(List<String> elements, String... childElms) {
142 List<String> concat = new ArrayList<>(elements.size() + childElms.length);
143 concat.addAll(elements);
144 concat.addAll(Arrays.asList(childElms));
145 return from(concat);
146 }
147
148 /**
149 * Creates a new DocumentPath element appending {@code childElm} to
150 * this path.
151 *
152 * @param childElms to append
153 * @return this + childElm
154 */
155 public DocumentPath append(List<String> childElms) {
156 List<String> concat = new ArrayList<>(pathElements.size() + childElms.size());
157 concat.addAll(pathElements);
158 concat.addAll(childElms);
159 return from(concat);
Jordan Haltermanb0ac5902017-07-30 12:31:01 -0700160 }
161
162 /**
Sithara Punnassery589fac22016-10-03 11:51:53 -0700163 * Returns the relative path to the given node.
164 *
165 * @return relative path to the given node.
166 */
167 public DocumentPath childPath() {
168 if (pathElements.size() <= 1) {
169 return null;
170 }
171 return new DocumentPath(this.pathElements.subList(pathElements.size() - 1, pathElements.size()));
172 }
173 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700174 * Returns a path for the parent of this node.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700175 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700176 * @return parent node path. If this path is for the root, returns {@code null}.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700177 */
178 public DocumentPath parent() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700179 if (pathElements.size() <= 1) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700180 return null;
181 }
Madan Jampani5bdebd52016-09-07 16:18:12 -0700182 return new DocumentPath(this.pathElements.subList(0, pathElements.size() - 1));
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700183 }
184
185 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700186 * Returns the list of path elements representing this path in correct
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700187 * order.
188 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700189 * @return a list of elements that make up this path
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700190 */
Madan Jampani5bdebd52016-09-07 16:18:12 -0700191 public List<String> pathElements() {
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -0700192 return pathElements;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700193 }
194
Madan Jampani98094222016-09-15 21:12:46 -0700195 /**
196 * Returns if the specified path belongs to a direct ancestor of the node pointed at by this path.
197 * <p>
198 * Example: {@code root.a} is a direct ancestor of {@code r.a.b.c}; while {@code r.a.x} is not.
199 *
200 * @param other other path
201 * @return {@code true} is yes; {@code false} otherwise.
202 */
203 public boolean isAncestorOf(DocumentPath other) {
Yuta HIGUCHIb2a20d12018-02-14 10:25:55 -0800204 return other != null &&
205 this.pathElements.size() < other.pathElements.size() &&
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -0700206 this.pathElements.equals(other.pathElements.subList(0, this.pathElements.size()));
Madan Jampani98094222016-09-15 21:12:46 -0700207 }
208
209 /**
210 * Returns if the specified path is belongs to a subtree rooted this path.
211 * <p>
212 * Example: {@code root.a.b} and {@code root.a.b.c.d.e} are descendants of {@code r.a.b};
213 * while {@code r.a.x.c} is not.
214 *
215 * @param other other path
216 * @return {@code true} is yes; {@code false} otherwise.
217 */
218 public boolean isDescendentOf(DocumentPath other) {
Yuta HIGUCHIb2a20d12018-02-14 10:25:55 -0800219 return other != null &&
220 (other.equals(this) || other.isAncestorOf(this));
Madan Jampani98094222016-09-15 21:12:46 -0700221 }
222
223 /**
224 * Returns the path that points to the least common ancestor of the specified
225 * collection of paths.
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -0700226 *
Madan Jampani98094222016-09-15 21:12:46 -0700227 * @param paths collection of path
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -0700228 * @return path to least common ancestor or null if there is nothing in common
Madan Jampani98094222016-09-15 21:12:46 -0700229 */
230 public static DocumentPath leastCommonAncestor(Collection<DocumentPath> paths) {
231 if (CollectionUtils.isEmpty(paths)) {
232 return null;
233 }
Yuta HIGUCHI796a78d2017-11-03 14:09:59 -0700234 DocumentPath first = paths.iterator().next();
235
236 int maxComps = paths.stream()
237 .map(DocumentPath::pathElements)
238 .mapToInt(List::size)
239 .min()
240 .orElse(-1); // paths.size() will never be 0 here
241
242 for (int i = 0; i < maxComps; ++i) {
243 final int fi = i;
244 String comp = first.pathElements().get(i);
245 boolean isAllCommon = paths.stream()
246 .map(DocumentPath::pathElements)
247 .map(l -> l.get(fi))
248 .allMatch(c -> comp.equals(c));
249 if (!isAllCommon) {
250 return (i == 0) ? null :
251 DocumentPath.from(first.pathElements.subList(0, i));
252 }
253 }
254 return DocumentPath.from(first.pathElements.subList(0, maxComps));
Madan Jampani98094222016-09-15 21:12:46 -0700255 }
256
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700257 @Override
258 public int hashCode() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700259 return Objects.hash(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700260 }
261
262 @Override
263 public boolean equals(Object obj) {
264 if (obj instanceof DocumentPath) {
265 DocumentPath that = (DocumentPath) obj;
Madan Jampani5bdebd52016-09-07 16:18:12 -0700266 return this.pathElements.equals(that.pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700267 }
268 return false;
269 }
270
271 @Override
272 public String toString() {
273 StringBuilder stringBuilder = new StringBuilder();
Madan Jampani5bdebd52016-09-07 16:18:12 -0700274 Iterator<String> iter = pathElements.iterator();
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700275 while (iter.hasNext()) {
276 stringBuilder.append(iter.next());
277 if (iter.hasNext()) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -0700278 stringBuilder.append(pathSeparator);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700279 }
280 }
281 return stringBuilder.toString();
282 }
283
284 @Override
Madan Jampani5bdebd52016-09-07 16:18:12 -0700285 public int compareTo(DocumentPath that) {
Yuta HIGUCHI52e047f2017-09-11 16:43:21 -0700286 return Comparators.lexicographical(Comparator.<String>naturalOrder())
287 .compare(this.pathElements, that.pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700288 }
289}