Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 15:14:12 GMT
  2. Package org.json

File JSONPointer.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

24
67
12
2
362
186
30
0.45
5.58
6
2.5

Classes

Class Line # Actions
JSONPointer 54 60 26
0.00%
JSONPointer.Builder 64 7 4
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

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