|
11 | 11 | * It may be used in future for filtering of streaming JSON content
|
12 | 12 | * as well (not implemented yet for 2.3).
|
13 | 13 | *<p>
|
14 |
| - * Instances are fully immutable and can be shared, cached. |
| 14 | + * Instances are fully immutable and can be cached, shared between threads. |
15 | 15 | *
|
16 | 16 | * @author Tatu Saloranta
|
17 | 17 | *
|
@@ -137,6 +137,86 @@ public static JsonPointer compile(String input) throws IllegalArgumentException
|
137 | 137 | */
|
138 | 138 | public static JsonPointer valueOf(String input) { return compile(input); }
|
139 | 139 |
|
| 140 | + /** |
| 141 | + * Factory method that will construct a pointer instance that describes |
| 142 | + * path to location given {@link JsonStreamContext} points to. |
| 143 | + * |
| 144 | + * @param context Context to build pointer expression fot |
| 145 | + * @param includeRoot Whether to include number offset for virtual "root context" |
| 146 | + * or not. |
| 147 | + * |
| 148 | + * @since 2.9 |
| 149 | + */ |
| 150 | + public static JsonPointer forPath(JsonStreamContext context, |
| 151 | + boolean includeRoot) |
| 152 | + { |
| 153 | + // First things first: last segment may be for START_ARRAY/START_OBJECT, |
| 154 | + // in which case it does not yet point to anything, and should be skipped |
| 155 | + if (context == null) { |
| 156 | + return EMPTY; |
| 157 | + } |
| 158 | + if (!context.hasPathSegment()) { |
| 159 | + // one special case; do not prune root if we need it |
| 160 | + if (!(includeRoot && context.inRoot() && context.hasCurrentIndex())) { |
| 161 | + context = context.getParent(); |
| 162 | + } |
| 163 | + } |
| 164 | + JsonPointer tail = null; |
| 165 | + |
| 166 | + for (; context != null; context = context.getParent()) { |
| 167 | + if (context.inObject()) { |
| 168 | + String seg = context.getCurrentName(); |
| 169 | + if (seg == null) { // is this legal? |
| 170 | + seg = ""; |
| 171 | + } |
| 172 | + tail = new JsonPointer(_fullPath(tail, seg), seg, tail); |
| 173 | + } else if (context.inArray() || includeRoot) { |
| 174 | + int ix = context.getCurrentIndex(); |
| 175 | + String ixStr = String.valueOf(ix); |
| 176 | + tail = new JsonPointer(_fullPath(tail, ixStr), ixStr, ix, tail); |
| 177 | + } |
| 178 | + // NOTE: this effectively drops ROOT node(s); should have 1 such node, |
| 179 | + // as the last one, but we don't have to care (probably some paths have |
| 180 | + // no root, for example) |
| 181 | + } |
| 182 | + if (tail == null) { |
| 183 | + return EMPTY; |
| 184 | + } |
| 185 | + return tail; |
| 186 | + } |
| 187 | + |
| 188 | + private static String _fullPath(JsonPointer tail, String segment) |
| 189 | + { |
| 190 | + if (tail == null) { |
| 191 | + StringBuilder sb = new StringBuilder(segment.length()+1); |
| 192 | + sb.append('/'); |
| 193 | + _appendEscaped(sb, segment); |
| 194 | + return sb.toString(); |
| 195 | + } |
| 196 | + String tailDesc = tail._asString; |
| 197 | + StringBuilder sb = new StringBuilder(segment.length() + 1 + tailDesc.length()); |
| 198 | + sb.append('/'); |
| 199 | + _appendEscaped(sb, segment); |
| 200 | + sb.append(tailDesc); |
| 201 | + return sb.toString(); |
| 202 | + } |
| 203 | + |
| 204 | + private static void _appendEscaped(StringBuilder sb, String segment) |
| 205 | + { |
| 206 | + for (int i = 0, end = segment.length(); i < end; ++i) { |
| 207 | + char c = segment.charAt(i); |
| 208 | + if (c == '/') { |
| 209 | + sb.append("~1"); |
| 210 | + continue; |
| 211 | + } |
| 212 | + if (c == '~') { |
| 213 | + sb.append("~0"); |
| 214 | + continue; |
| 215 | + } |
| 216 | + sb.append(c); |
| 217 | + } |
| 218 | + } |
| 219 | + |
140 | 220 | /* Factory method that composes a pointer instance, given a set
|
141 | 221 | * of 'raw' segments: raw meaning that no processing will be done,
|
142 | 222 | * no escaping may is present.
|
@@ -189,13 +269,32 @@ public JsonPointer last() {
|
189 | 269 | return current;
|
190 | 270 | }
|
191 | 271 |
|
| 272 | + /** |
| 273 | + * Mutant factory method that will return |
| 274 | + *<ul> |
| 275 | + * <li>`tail` if `this` instance is "empty" pointer, OR |
| 276 | + * </li> |
| 277 | + * <li>`this` instance if `tail` is "empty" pointer, OR |
| 278 | + * </li> |
| 279 | + * <li>Newly constructed {@link JsonPointer} instance that starts with all segments |
| 280 | + * of `this`, followed by all segments of `tail`. |
| 281 | + * </li> |
| 282 | + *</ul> |
| 283 | + * |
| 284 | + * @param tail {@link JsonPointer} instance to append to this one, to create a new pointer instance |
| 285 | + * |
| 286 | + * @return Either `this` instance, `tail`, or a newly created combination, as per description above. |
| 287 | + */ |
192 | 288 | public JsonPointer append(JsonPointer tail) {
|
193 | 289 | if (this == EMPTY) {
|
194 | 290 | return tail;
|
195 | 291 | }
|
196 | 292 | if (tail == EMPTY) {
|
197 | 293 | return this;
|
198 | 294 | }
|
| 295 | + // 21-Mar-2017, tatu: Not superbly efficient; could probably improve by not concatenating, |
| 296 | + // re-decoding -- by stitching together segments -- but for now should be fine. |
| 297 | + |
199 | 298 | String currentJsonPointer = _asString;
|
200 | 299 | if (currentJsonPointer.endsWith("/")) {
|
201 | 300 | //removes final slash
|
|
0 commit comments