• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023 the original author or authors.
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 package com.networknt.schema;
17 
18 import java.util.Objects;
19 
20 /**
21  * Represents a path to a JSON node.
22  */
23 public class JsonNodePath implements Comparable<JsonNodePath> {
24     private final PathType type;
25     private final JsonNodePath parent;
26 
27     private final String pathSegment;
28     private final int pathSegmentIndex;
29 
30     private volatile String value = null; // computed lazily
31 
JsonNodePath(PathType type)32     public JsonNodePath(PathType type) {
33         this.type = type;
34         this.parent = null;
35         this.pathSegment = null;
36         this.pathSegmentIndex = -1;
37     }
38 
JsonNodePath(JsonNodePath parent, String pathSegment)39     private JsonNodePath(JsonNodePath parent, String pathSegment) {
40         this.parent = parent;
41         this.type = parent.type;
42         this.pathSegment = pathSegment;
43         this.pathSegmentIndex = -1;
44     }
45 
JsonNodePath(JsonNodePath parent, int pathSegmentIndex)46     private JsonNodePath(JsonNodePath parent, int pathSegmentIndex) {
47         this.parent = parent;
48         this.type = parent.type;
49         this.pathSegment = null;
50         this.pathSegmentIndex = pathSegmentIndex;
51     }
52 
53     /**
54      * Returns the parent path, or null if this path does not have a parent.
55      *
56      * @return the parent
57      */
getParent()58     public JsonNodePath getParent() {
59         return this.parent;
60     }
61 
62     /**
63      * Append the child token to the path.
64      *
65      * @param token the child token
66      * @return the path
67      */
append(String token)68     public JsonNodePath append(String token) {
69         return new JsonNodePath(this, token);
70     }
71 
72     /**
73      * Append the index to the path.
74      *
75      * @param index the index
76      * @return the path
77      */
append(int index)78     public JsonNodePath append(int index) {
79         return new JsonNodePath(this, index);
80     }
81 
82     /**
83      * Gets the {@link PathType}.
84      *
85      * @return the path type
86      */
getPathType()87     public PathType getPathType() {
88         return this.type;
89     }
90 
91     /**
92      * Gets the name element given an index.
93      * <p>
94      * The index parameter is the index of the name element to return. The element
95      * that is closest to the root has index 0. The element that is farthest from
96      * the root has index count -1.
97      *
98      * @param index to return
99      * @return the name element
100      */
getName(int index)101     public String getName(int index) {
102         Object element = getElement(index);
103         if (element != null) {
104             return element.toString();
105         }
106         return null;
107     }
108 
109     /**
110      * Gets the element given an index.
111      * <p>
112      * The index parameter is the index of the element to return. The element that
113      * is closest to the root has index 0. The element that is farthest from the
114      * root has index count -1.
115      *
116      * @param index to return
117      * @return the element either a String or Integer
118      */
getElement(int index)119     public Object getElement(int index) {
120         if (index == -1) {
121             if (this.pathSegmentIndex != -1) {
122                 return Integer.valueOf(this.pathSegmentIndex);
123             } else {
124                 return this.pathSegment;
125             }
126         }
127         int nameCount = getNameCount();
128         if (nameCount - 1 == index) {
129             return this.getElement(-1);
130         }
131         int count = nameCount - index - 1;
132         if (count < 0) {
133             throw new IllegalArgumentException("");
134         }
135         JsonNodePath current = this;
136         for (int x = 0; x < count; x++) {
137             current = current.parent;
138         }
139         return current.getElement(-1);
140     }
141 
142     /**
143      * Gets the number of name elements in the path.
144      *
145      * @return the number of elements in the path or 0 if this is the root element
146      */
getNameCount()147     public int getNameCount() {
148         int current = this.pathSegmentIndex == -1 && this.pathSegment == null ? 0 : 1;
149         int parent = this.parent != null ? this.parent.getNameCount() : 0;
150         return current + parent;
151     }
152 
153     /**
154      * Tests if this path starts with the other path.
155      *
156      * @param other the other path
157      * @return true if the path starts with the other path
158      */
startsWith(JsonNodePath other)159     public boolean startsWith(JsonNodePath other) {
160         int count = getNameCount();
161         int otherCount = other.getNameCount();
162 
163         if (otherCount > count) {
164             return false;
165         } else if (otherCount == count) {
166             return this.equals(other);
167         } else {
168             JsonNodePath compare = this;
169             int x = count - otherCount;
170             while (x > 0) {
171                 compare = compare.getParent();
172                 x--;
173             }
174             return other.equals(compare);
175         }
176     }
177 
178     /**
179      * Tests if this path contains a string segment that is an exact match.
180      * <p>
181      * This will not match if the segment is a number.
182      *
183      * @param segment the segment to test
184      * @return true if the string segment is found
185      */
contains(String segment)186     public boolean contains(String segment) {
187         boolean result = segment.equals(this.pathSegment);
188         if (result) {
189             return true;
190         }
191         JsonNodePath path = this.getParent();
192         while (path != null) {
193             if (segment.equals(path.pathSegment)) {
194                 return true;
195             }
196             path = path.getParent();
197         }
198         return false;
199     }
200 
201     @Override
toString()202     public String toString() {
203         if (this.value == null) {
204             String parentValue = this.parent == null ? type.getRoot() : this.parent.toString();
205             if (pathSegmentIndex != -1) {
206                 this.value = this.type.append(parentValue, pathSegmentIndex);
207             } else if (pathSegment != null) {
208                 this.value = this.type.append(parentValue, pathSegment);
209             } else {
210                 this.value = parentValue;
211             }
212         }
213         return this.value;
214     }
215 
216     @Override
hashCode()217     public int hashCode() {
218         return Objects.hash(parent, pathSegment, pathSegmentIndex, type);
219     }
220 
221     @Override
equals(Object obj)222     public boolean equals(Object obj) {
223         if (this == obj)
224             return true;
225         if (obj == null)
226             return false;
227         if (getClass() != obj.getClass())
228             return false;
229         JsonNodePath other = (JsonNodePath) obj;
230         return Objects.equals(parent, other.parent) && Objects.equals(pathSegment, other.pathSegment)
231                 && pathSegmentIndex == other.pathSegmentIndex && type == other.type;
232     }
233 
234     @Override
compareTo(JsonNodePath other)235     public int compareTo(JsonNodePath other) {
236         if (this.parent != null && other.parent == null) {
237             return 1;
238         } else if (this.parent == null && other.parent != null) {
239             return -1;
240         } else if (this.parent != null && other.parent != null) {
241             int result = this.parent.compareTo(other.parent);
242             if (result != 0) {
243                 return result;
244             }
245         }
246         String thisValue = this.getName(-1);
247         String otherValue = other.getName(-1);
248         if (thisValue == null && otherValue == null) {
249             return 0;
250         } else if (thisValue != null && otherValue == null) {
251             return 1;
252         } else if (thisValue == null && otherValue != null) {
253             return -1;
254         } else {
255             return thisValue.compareTo(otherValue);
256         }
257     }
258 }
259