Skip to content

Commit 1200da1

Browse files
committed
copy-pasting JSONPointer and JSONPointerException from org.json, and using it, for compatibility
1 parent 12be013 commit 1200da1

File tree

8 files changed

+371
-11
lines changed

8 files changed

+371
-11
lines changed
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
package org.everit.json.schema;
2+
3+
import static java.lang.String.format;
4+
5+
import java.io.UnsupportedEncodingException;
6+
import java.net.URLDecoder;
7+
import java.net.URLEncoder;
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
11+
12+
import org.json.JSONArray;
13+
import org.json.JSONException;
14+
import org.json.JSONObject;
15+
16+
/*
17+
Copyright (c) 2002 JSON.org
18+
19+
Permission is hereby granted, free of charge, to any person obtaining a copy
20+
of this software and associated documentation files (the "Software"), to deal
21+
in the Software without restriction, including without limitation the rights
22+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23+
copies of the Software, and to permit persons to whom the Software is
24+
furnished to do so, subject to the following conditions:
25+
26+
The above copyright notice and this permission notice shall be included in all
27+
copies or substantial portions of the Software.
28+
29+
The Software shall be used for Good, not Evil.
30+
31+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37+
SOFTWARE.
38+
*/
39+
40+
/**
41+
* A JSON Pointer is a simple query language defined for JSON documents by
42+
* <a href="https://tools.ietf.org/html/rfc6901">RFC 6901</a>.
43+
* <p>
44+
* In a nutshell, JSONPointer allows the user to navigate into a JSON document
45+
* using strings, and retrieve targeted objects, like a simple form of XPATH.
46+
* Path segments are separated by the '/' char, which signifies the root of
47+
* the document when it appears as the first char of the string. Array
48+
* elements are navigated using ordinals, counting from 0. JSONPointer strings
49+
* may be extended to any arbitrary number of segments. If the navigation
50+
* is successful, the matched item is returned. A matched item may be a
51+
* JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building
52+
* fails, an appropriate exception is thrown. If the navigation fails to find
53+
* a match, a JSONPointerException is thrown.
54+
*
55+
* @author JSON.org
56+
* @version 2016-05-14
57+
*/
58+
public class JSONPointer {
59+
60+
// used for URL encoding and decoding
61+
private static final String ENCODING = "utf-8";
62+
63+
/**
64+
* This class allows the user to build a JSONPointer in steps, using
65+
* exactly one segment in each step.
66+
*/
67+
public static class Builder {
68+
69+
// Segments for the eventual JSONPointer string
70+
private final List<String> refTokens = new ArrayList<String>();
71+
72+
/**
73+
* Creates a {@code JSONPointer} instance using the tokens previously set using the
74+
* {@link #append(String)} method calls.
75+
*/
76+
public JSONPointer build() {
77+
return new JSONPointer(this.refTokens);
78+
}
79+
80+
/**
81+
* Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
82+
* <p>
83+
* Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
84+
* argument of this method MUST NOT be escaped. If you want to query the property called
85+
* {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
86+
* need to escape it as {@code "a~0b"}.
87+
*
88+
* @param token
89+
* the new token to be appended to the list
90+
* @return {@code this}
91+
* @throws NullPointerException
92+
* if {@code token} is null
93+
*/
94+
public Builder append(String token) {
95+
if (token == null) {
96+
throw new NullPointerException("token cannot be null");
97+
}
98+
this.refTokens.add(token);
99+
return this;
100+
}
101+
102+
/**
103+
* Adds an integer to the reference token list. Although not necessarily, mostly this token will
104+
* denote an array index.
105+
*
106+
* @param arrayIndex
107+
* the array index to be added to the token list
108+
* @return {@code this}
109+
*/
110+
public Builder append(int arrayIndex) {
111+
this.refTokens.add(String.valueOf(arrayIndex));
112+
return this;
113+
}
114+
}
115+
116+
/**
117+
* Static factory method for {@link Builder}. Example usage:
118+
* <p>
119+
* <pre><code>
120+
* JSONPointer pointer = JSONPointer.builder()
121+
* .append("obj")
122+
* .append("other~key").append("another/key")
123+
* .append("\"")
124+
* .append(0)
125+
* .build();
126+
* </code></pre>
127+
*
128+
* @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
129+
* {@link Builder#append(String)} calls.
130+
*/
131+
public static Builder builder() {
132+
return new Builder();
133+
}
134+
135+
// Segments for the JSONPointer string
136+
private final List<String> refTokens;
137+
138+
/**
139+
* Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
140+
* evaluate the same JSON Pointer on different JSON documents then it is recommended
141+
* to keep the {@code JSONPointer} instances due to performance considerations.
142+
*
143+
* @param pointer
144+
* the JSON String or URI Fragment representation of the JSON pointer.
145+
* @throws IllegalArgumentException
146+
* if {@code pointer} is not a valid JSON pointer
147+
*/
148+
public JSONPointer(final String pointer) {
149+
if (pointer == null) {
150+
throw new NullPointerException("pointer cannot be null");
151+
}
152+
if (pointer.isEmpty() || pointer.equals("#")) {
153+
this.refTokens = Collections.emptyList();
154+
return;
155+
}
156+
String refs;
157+
if (pointer.startsWith("#/")) {
158+
refs = pointer.substring(2);
159+
try {
160+
refs = URLDecoder.decode(refs, ENCODING);
161+
} catch (UnsupportedEncodingException e) {
162+
throw new RuntimeException(e);
163+
}
164+
} else if (pointer.startsWith("/")) {
165+
refs = pointer.substring(1);
166+
} else {
167+
throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
168+
}
169+
this.refTokens = new ArrayList<String>();
170+
int slashIdx = -1;
171+
int prevSlashIdx = 0;
172+
do {
173+
prevSlashIdx = slashIdx + 1;
174+
slashIdx = refs.indexOf('/', prevSlashIdx);
175+
if (prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
176+
// found 2 slashes in a row ( obj//next )
177+
// or single slash at the end of a string ( obj/test/ )
178+
this.refTokens.add("");
179+
} else if (slashIdx >= 0) {
180+
final String token = refs.substring(prevSlashIdx, slashIdx);
181+
this.refTokens.add(unescape(token));
182+
} else {
183+
// last item after separator, or no separator at all.
184+
final String token = refs.substring(prevSlashIdx);
185+
this.refTokens.add(unescape(token));
186+
}
187+
} while (slashIdx >= 0);
188+
// using split does not take into account consecutive separators or "ending nulls"
189+
//for (String token : refs.split("/")) {
190+
// this.refTokens.add(unescape(token));
191+
//}
192+
}
193+
194+
public JSONPointer(List<String> refTokens) {
195+
this.refTokens = new ArrayList<String>(refTokens);
196+
}
197+
198+
private String unescape(String token) {
199+
return token.replace("~1", "/").replace("~0", "~")
200+
.replace("\\\"", "\"")
201+
.replace("\\\\", "\\");
202+
}
203+
204+
/**
205+
* Evaluates this JSON Pointer on the given {@code document}. The {@code document}
206+
* is usually a {@link org.json.JSONObject} or a {@link org.json.JSONArray} instance, but the empty
207+
* JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
208+
* returned value will be {@code document} itself.
209+
*
210+
* @param document
211+
* the JSON document which should be the subject of querying.
212+
* @return the result of the evaluation
213+
* @throws JSONPointerException
214+
* if an error occurs during evaluation
215+
*/
216+
public Object queryFrom(Object document) throws JSONPointerException {
217+
if (this.refTokens.isEmpty()) {
218+
return document;
219+
}
220+
Object current = document;
221+
for (String token : this.refTokens) {
222+
if (current instanceof JSONObject) {
223+
current = ((JSONObject) current).opt(unescape(token));
224+
} else if (current instanceof JSONArray) {
225+
current = readByIndexToken(current, token);
226+
} else {
227+
throw new JSONPointerException(format(
228+
"value [%s] is not an array or object therefore its key %s cannot be resolved", current,
229+
token));
230+
}
231+
}
232+
return current;
233+
}
234+
235+
/**
236+
* Matches a JSONArray element by ordinal position
237+
*
238+
* @param current
239+
* the JSONArray to be evaluated
240+
* @param indexToken
241+
* the array index in string form
242+
* @return the matched object. If no matching item is found a
243+
* @throws JSONPointerException
244+
* is thrown if the index is out of bounds
245+
*/
246+
private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
247+
try {
248+
int index = Integer.parseInt(indexToken);
249+
JSONArray currentArr = (JSONArray) current;
250+
if (index >= currentArr.length()) {
251+
throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
252+
Integer.valueOf(currentArr.length())));
253+
}
254+
try {
255+
return currentArr.get(index);
256+
} catch (JSONException e) {
257+
throw new JSONPointerException("Error reading value at index position " + index, e);
258+
}
259+
} catch (NumberFormatException e) {
260+
throw new JSONPointerException(format("%s is not an array index", indexToken), e);
261+
}
262+
}
263+
264+
/**
265+
* Returns a string representing the JSONPointer path value using string
266+
* representation
267+
*/
268+
@Override
269+
public String toString() {
270+
StringBuilder rval = new StringBuilder("");
271+
for (String token : this.refTokens) {
272+
rval.append('/').append(escape(token));
273+
}
274+
return rval.toString();
275+
}
276+
277+
/**
278+
* Escapes path segment values to an unambiguous form.
279+
* The escape char to be inserted is '~'. The chars to be escaped
280+
* are ~, which maps to ~0, and /, which maps to ~1. Backslashes
281+
* and double quote chars are also escaped.
282+
*
283+
* @param token
284+
* the JSONPointer segment value to be escaped
285+
* @return the escaped value for the token
286+
*/
287+
private String escape(String token) {
288+
return token.replace("~", "~0")
289+
.replace("/", "~1")
290+
.replace("\\", "\\\\")
291+
.replace("\"", "\\\"");
292+
}
293+
294+
/**
295+
* Returns a string representing the JSONPointer path value using URI
296+
* fragment identifier representation
297+
*/
298+
public String toURIFragment() {
299+
try {
300+
StringBuilder rval = new StringBuilder("#");
301+
for (String token : this.refTokens) {
302+
rval.append('/').append(URLEncoder.encode(token, ENCODING));
303+
}
304+
return rval.toString();
305+
} catch (UnsupportedEncodingException e) {
306+
throw new RuntimeException(e);
307+
}
308+
}
309+
310+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.everit.json.schema;
2+
3+
/*
4+
Copyright (c) 2002 JSON.org
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
The Software shall be used for Good, not Evil.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
SOFTWARE.
25+
*/
26+
27+
import org.json.JSONException;
28+
29+
/**
30+
* The JSONPointerException is thrown by {@link JSONPointer} if an error occurs
31+
* during evaluating a pointer.
32+
*
33+
* @author JSON.org
34+
* @version 2016-05-13
35+
*/
36+
public class JSONPointerException extends JSONException {
37+
private static final long serialVersionUID = 8872944667561856751L;
38+
39+
public JSONPointerException(String message) {
40+
super(message);
41+
}
42+
43+
public JSONPointerException(String message, Throwable cause) {
44+
super(message, cause);
45+
}
46+
47+
}

core/src/main/java/org/everit/json/schema/internal/JsonPointerFormatValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.util.Optional;
66

77
import org.everit.json.schema.FormatValidator;
8-
import org.json.JSONPointer;
8+
import org.everit.json.schema.JSONPointer;
99

1010
public class JsonPointerFormatValidator implements FormatValidator {
1111

core/src/main/java/org/everit/json/schema/internal/RelativeJsonPointerFormatValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.util.Optional;
44

55
import org.everit.json.schema.FormatValidator;
6-
import org.json.JSONPointer;
6+
import org.everit.json.schema.JSONPointer;
77

88
public class RelativeJsonPointerFormatValidator implements FormatValidator {
99

core/src/main/java/org/everit/json/schema/loader/JsonPointerEvaluator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
import java.util.LinkedList;
1616
import java.util.function.Supplier;
1717

18+
import org.everit.json.schema.JSONPointerException;
1819
import org.everit.json.schema.SchemaException;
1920
import org.json.JSONException;
2021
import org.json.JSONObject;
21-
import org.json.JSONPointerException;
2222
import org.json.JSONTokener;
2323

2424
/**

0 commit comments

Comments
 (0)