1
1
package com .fasterxml .jackson .core ;
2
2
3
+ import java .io .*;
4
+
3
5
import com .fasterxml .jackson .core .io .NumberInput ;
4
- import java .io .Externalizable ;
5
- import java .io .IOException ;
6
- import java .io .ObjectInput ;
7
- import java .io .ObjectOutput ;
8
- import java .io .ObjectStreamException ;
9
- import java .io .Serializable ;
10
6
11
7
/**
12
8
* Implementation of
17
13
* It may be used in future for filtering of streaming JSON content
18
14
* as well (not implemented yet for 2.3).
19
15
*<p>
16
+ * Note that the implementation was largely rewritten for Jackson 2.14 to
17
+ * reduce memory usage by sharing backing "full path" representation for
18
+ * nested instances.
19
+ *<p>
20
20
* Instances are fully immutable and can be cached, shared between threads.
21
21
*
22
22
* @author Tatu Saloranta
@@ -68,6 +68,8 @@ public class JsonPointer implements Serializable
68
68
*<p>
69
69
* NOTE: starting with 2.14, there is no accompanying
70
70
* {@link #_asStringOffset} that MUST be considered with this String;
71
+ * this {@code String} may contain preceding path, as it is now full path
72
+ * of parent pointer, except for the outermost pointer instance.
71
73
*/
72
74
protected final String _asString ;
73
75
@@ -81,6 +83,10 @@ public class JsonPointer implements Serializable
81
83
protected final int _matchingElementIndex ;
82
84
83
85
/**
86
+ * Lazily-calculated hash code: need to retain hash code now that we can no
87
+ * longer rely on {@link #_asString} being the exact full representation (it
88
+ * is often "more", including parent path).
89
+ *
84
90
* @since 2.14
85
91
*/
86
92
protected int _hashCode ;
@@ -209,44 +215,69 @@ public static JsonPointer forPath(JsonStreamContext context,
209
215
context = context .getParent ();
210
216
}
211
217
}
212
- JsonPointer tail = null ;
218
+
219
+ PointerSegment next = null ;
220
+ int approxLength = 0 ;
213
221
214
222
for (; context != null ; context = context .getParent ()) {
215
223
if (context .inObject ()) {
216
- String seg = context .getCurrentName ();
217
- if (seg == null ) { // is this legal?
218
- seg = "" ;
224
+ String propName = context .getCurrentName ();
225
+ if (propName == null ) { // is this legal?
226
+ propName = "" ;
219
227
}
220
- tail = new JsonPointer (_fullPath (tail , seg ), 0 , seg , tail );
228
+ approxLength += 2 + propName .length ();
229
+ next = new PointerSegment (next , propName , -1 );
230
+ // tail = new JsonPointer(_fullPath(tail, seg), 0, seg, tail);
221
231
} else if (context .inArray () || includeRoot ) {
222
232
int ix = context .getCurrentIndex ();
223
- String ixStr = String .valueOf (ix );
224
- tail = new JsonPointer (_fullPath (tail , ixStr ), 0 , ixStr , ix , tail );
233
+ approxLength += 6 ;
234
+ next = new PointerSegment (next , null , ix );
235
+ // tail = new JsonPointer(_fullPath(tail, ixStr), 0, ixStr, ix, tail);
225
236
}
226
237
// NOTE: this effectively drops ROOT node(s); should have 1 such node,
227
238
// as the last one, but we don't have to care (probably some paths have
228
239
// no root, for example)
229
240
}
230
- if (tail == null ) {
241
+ if (next == null ) {
231
242
return EMPTY ;
232
243
}
233
- return tail ;
234
- }
235
244
236
- private static String _fullPath (JsonPointer tail , String segment )
237
- {
238
- if (tail == null ) {
239
- StringBuilder sb = new StringBuilder (segment .length ()+1 );
240
- sb .append ('/' );
241
- _appendEscaped (sb , segment );
242
- return sb .toString ();
245
+ // And here the fun starts! We have the head, need to traverse
246
+ // to compose full path String
247
+ // final PointerSegment head = next;
248
+ StringBuilder pathBuilder = new StringBuilder (approxLength );
249
+ PointerSegment last = null ;
250
+
251
+ for (; next != null ; next = next .next ) {
252
+ // Let's find the last segment as well, for reverse traversal
253
+ last = next ;
254
+ next .pathOffset = pathBuilder .length ();
255
+ pathBuilder .append ('/' );
256
+ if (next .property != null ) {
257
+ _appendEscaped (pathBuilder , next .property );
258
+ } else {
259
+ pathBuilder .append (next .index );
260
+ }
243
261
}
244
- String tailDesc = tail ._asString ;
245
- StringBuilder sb = new StringBuilder (segment .length () + 1 + tailDesc .length ());
246
- sb .append ('/' );
247
- _appendEscaped (sb , segment );
248
- sb .append (tailDesc );
249
- return sb .toString ();
262
+ final String fullPath = pathBuilder .toString ();
263
+
264
+ // and then iteratively construct JsonPointer chain in reverse direction
265
+ // (from innermost back to outermost)
266
+ PointerSegment currSegment = last ;
267
+ JsonPointer currPtr = EMPTY ;
268
+
269
+ for (; currSegment != null ; currSegment = currSegment .prev ) {
270
+ if (currSegment .property != null ) {
271
+ currPtr = new JsonPointer (fullPath , currSegment .pathOffset ,
272
+ currSegment .property , currPtr );
273
+ } else {
274
+ int index = currSegment .index ;
275
+ currPtr = new JsonPointer (fullPath , currSegment .pathOffset ,
276
+ String .valueOf (index ), index , currPtr );
277
+ }
278
+ }
279
+
280
+ return currPtr ;
250
281
}
251
282
252
283
private static void _appendEscaped (StringBuilder sb , String segment )
@@ -788,6 +819,32 @@ private static class PointerParent {
788
819
}
789
820
}
790
821
822
+ /**
823
+ * Helper class used to contain a single segment when constructing JsonPointer
824
+ * from context.
825
+ */
826
+ private static class PointerSegment {
827
+ public final PointerSegment next ;
828
+ public final String property ;
829
+ public final int index ;
830
+
831
+ // Offset within external buffer, updated when constructing
832
+ public int pathOffset ;
833
+
834
+ // And we actually need 2-way traversal, it turns out so:
835
+ public PointerSegment prev ;
836
+
837
+ public PointerSegment (PointerSegment next , String pn , int ix ) {
838
+ this .next = next ;
839
+ property = pn ;
840
+ index = ix ;
841
+ // Ok not the cleanest thing but...
842
+ if (next != null ) {
843
+ next .prev = this ;
844
+ }
845
+ }
846
+ }
847
+
791
848
/*
792
849
/**********************************************************
793
850
/* Support for JDK serialization (2.14+)
0 commit comments