blob: 0a4906ee62aceaaae9c7dec407eb99ccf73a75a9 [file] [log] [blame]
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
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
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
21import org.apache.commons.collections.CollectionUtils;
22import org.apache.commons.lang.StringUtils;
23
Madan Jampaniad5b8c72016-09-12 15:05:01 -070024import java.util.Arrays;
Madan Jampani98094222016-09-15 21:12:46 -070025import java.util.Collection;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070026import java.util.Iterator;
27import java.util.List;
28import java.util.Objects;
29
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070030import static com.google.common.base.Preconditions.checkNotNull;
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070031
Madan Jampani5bdebd52016-09-07 16:18:12 -070032/**
33 * Unique key for nodes in the {@link DocumentTree}.
34 */
35public class DocumentPath implements Comparable<DocumentPath> {
36
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070037 /** Default path separator. */
38 public static final String DEFAULT_SEPARATOR = "|";
39
40 /** Default path separator regex. */
41 public static final String DEFAULT_SEPARATOR_RE = "\\|";
42
43 // TODO: Add means to set the path separator and separator ERE.
44 private static String pathSeparator = DEFAULT_SEPARATOR;
45 private static String pathSeparatorRE = DEFAULT_SEPARATOR_RE;
46
Madan Jampani5bdebd52016-09-07 16:18:12 -070047 private final List<String> pathElements = Lists.newArrayList();
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070048
49 /**
50 * Private utility constructor for internal generation of partial paths only.
51 *
Madan Jampani5bdebd52016-09-07 16:18:12 -070052 * @param pathElements list of path elements
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070053 */
Madan Jampani5bdebd52016-09-07 16:18:12 -070054 private DocumentPath(List<String> pathElements) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070055 checkNotNull(pathElements);
Madan Jampani5bdebd52016-09-07 16:18:12 -070056 this.pathElements.addAll(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070057 }
58
59 /**
Thomas Vachuskac3984c62016-09-07 17:51:45 -070060 * Constructs a new document path.
Madan Jampani5bdebd52016-09-07 16:18:12 -070061 * <p>
62 * New paths must contain at least one name and string names may NOT contain any period characters.
63 * If one field is {@code null} that field will be ignored.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070064 *
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070065 * @param nodeName the name of the last level of this path
Madan Jampani5bdebd52016-09-07 16:18:12 -070066 * @param parentPath the path representing the parent leading up to this
67 * node, in the case of the root this should be {@code null}
68 * @throws IllegalDocumentNameException if both parameters are null or name contains an illegal character ('.')
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070069 */
70 public DocumentPath(String nodeName, DocumentPath parentPath) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070071 checkNotNull(nodeName, "Node name cannot be null");
72 if (nodeName.contains(pathSeparator)) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070073 throw new IllegalDocumentNameException(
74 "Periods are not allowed in names.");
75 }
76 if (parentPath != null) {
Madan Jampani5bdebd52016-09-07 16:18:12 -070077 pathElements.addAll(parentPath.pathElements());
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070078 }
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070079 pathElements.add(nodeName);
Madan Jampani5bdebd52016-09-07 16:18:12 -070080 if (pathElements.isEmpty()) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -070081 throw new IllegalDocumentNameException("A document path must contain at" +
82 "least one non-null" +
83 "element.");
84 }
85 }
86
87 /**
Madan Jampaniad5b8c72016-09-12 15:05:01 -070088 * Creates a new {@code DocumentPath} from a period delimited path string.
89 *
90 * @param path path string
91 * @return {@code DocumentPath} instance
92 */
93 public static DocumentPath from(String path) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -070094 return new DocumentPath(Arrays.asList(path.split(pathSeparatorRE)));
Madan Jampaniad5b8c72016-09-12 15:05:01 -070095 }
96
97 /**
Sithara Punnassery589fac22016-10-03 11:51:53 -070098 * Returns the relative path to the given node.
99 *
100 * @return relative path to the given node.
101 */
102 public DocumentPath childPath() {
103 if (pathElements.size() <= 1) {
104 return null;
105 }
106 return new DocumentPath(this.pathElements.subList(pathElements.size() - 1, pathElements.size()));
107 }
108 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700109 * Returns a path for the parent of this node.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700110 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700111 * @return parent node path. If this path is for the root, returns {@code null}.
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700112 */
113 public DocumentPath parent() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700114 if (pathElements.size() <= 1) {
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700115 return null;
116 }
Madan Jampani5bdebd52016-09-07 16:18:12 -0700117 return new DocumentPath(this.pathElements.subList(0, pathElements.size() - 1));
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700118 }
119
120 /**
Madan Jampani5bdebd52016-09-07 16:18:12 -0700121 * Returns the list of path elements representing this path in correct
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700122 * order.
123 *
Madan Jampani5bdebd52016-09-07 16:18:12 -0700124 * @return a list of elements that make up this path
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700125 */
Madan Jampani5bdebd52016-09-07 16:18:12 -0700126 public List<String> pathElements() {
127 return ImmutableList.copyOf(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700128 }
129
Madan Jampani98094222016-09-15 21:12:46 -0700130 /**
131 * Returns if the specified path belongs to a direct ancestor of the node pointed at by this path.
132 * <p>
133 * Example: {@code root.a} is a direct ancestor of {@code r.a.b.c}; while {@code r.a.x} is not.
134 *
135 * @param other other path
136 * @return {@code true} is yes; {@code false} otherwise.
137 */
138 public boolean isAncestorOf(DocumentPath other) {
139 return !other.equals(this) && other.toString().startsWith(toString());
140 }
141
142 /**
143 * Returns if the specified path is belongs to a subtree rooted this path.
144 * <p>
145 * Example: {@code root.a.b} and {@code root.a.b.c.d.e} are descendants of {@code r.a.b};
146 * while {@code r.a.x.c} is not.
147 *
148 * @param other other path
149 * @return {@code true} is yes; {@code false} otherwise.
150 */
151 public boolean isDescendentOf(DocumentPath other) {
152 return other.equals(this) || other.isAncestorOf(this);
153 }
154
155 /**
156 * Returns the path that points to the least common ancestor of the specified
157 * collection of paths.
158 * @param paths collection of path
159 * @return path to least common ancestor
160 */
161 public static DocumentPath leastCommonAncestor(Collection<DocumentPath> paths) {
162 if (CollectionUtils.isEmpty(paths)) {
163 return null;
164 }
165 return DocumentPath.from(StringUtils.getCommonPrefix(paths.stream()
166 .map(DocumentPath::toString)
167 .toArray(String[]::new)));
168 }
169
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700170 @Override
171 public int hashCode() {
Madan Jampani5bdebd52016-09-07 16:18:12 -0700172 return Objects.hash(pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700173 }
174
175 @Override
176 public boolean equals(Object obj) {
177 if (obj instanceof DocumentPath) {
178 DocumentPath that = (DocumentPath) obj;
Madan Jampani5bdebd52016-09-07 16:18:12 -0700179 return this.pathElements.equals(that.pathElements);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700180 }
181 return false;
182 }
183
184 @Override
185 public String toString() {
186 StringBuilder stringBuilder = new StringBuilder();
Madan Jampani5bdebd52016-09-07 16:18:12 -0700187 Iterator<String> iter = pathElements.iterator();
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700188 while (iter.hasNext()) {
189 stringBuilder.append(iter.next());
190 if (iter.hasNext()) {
Thomas Vachuskae2bd1152017-03-23 13:42:32 -0700191 stringBuilder.append(pathSeparator);
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700192 }
193 }
194 return stringBuilder.toString();
195 }
196
197 @Override
Madan Jampani5bdebd52016-09-07 16:18:12 -0700198 public int compareTo(DocumentPath that) {
199 int shorterLength = this.pathElements.size() > that.pathElements.size()
200 ? that.pathElements.size() : this.pathElements.size();
201 for (int i = 0; i < shorterLength; i++) {
202 if (this.pathElements.get(i).compareTo(that.pathElements.get(i)) != 0) {
203 return this.pathElements.get(i).compareTo(that.pathElements.get(i));
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700204 }
205 }
Madan Jampani5bdebd52016-09-07 16:18:12 -0700206 if (this.pathElements.size() > that.pathElements.size()) {
207 return 1;
208 } else if (that.pathElements.size() > this.pathElements.size()) {
209 return -1;
210 } else {
211 return 0;
212 }
Aaron Kruglikovb789b5f2016-08-31 14:47:05 -0700213 }
214}