@@ -14,10 +14,11 @@ public fun JsonPointer(path: String): JsonPointer = JsonPointer.compile(path)
1414 * [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901).
1515 */
1616public sealed class JsonPointer (
17- private val fullPath : String ,
18- private val pathOffset : Int ,
19- internal val next : JsonPointer ? = null ,
17+ internal open val next : JsonPointer ? = null ,
2018) {
19+ private var asString: String? = null
20+ private var hash: Int = 0
21+
2122 /* *
2223 * Creates a new [JsonPointer] that points to an [index] in the array.
2324 *
@@ -26,15 +27,10 @@ public sealed class JsonPointer(
2627 * val pointer = JsonPointer("/test").atIndex(0) // "/test/0"
2728 * ```
2829 */
29- public fun atIndex (index : Int ): JsonPointer =
30- JsonPointer (
31- buildString {
32- val pointer = this @JsonPointer.toString()
33- append(pointer)
34- append(SEPARATOR )
35- append(index)
36- },
37- )
30+ public fun atIndex (index : Int ): JsonPointer {
31+ require(index >= 0 ) { " negative index: $index " }
32+ return atProperty(index.toString())
33+ }
3834
3935 /* *
4036 * Creates a new [JsonPointer] that points to a [property] passed as a parameter.
@@ -44,28 +40,58 @@ public sealed class JsonPointer(
4440 * val pointer = JsonPointer.ROOT.atProperty("prop1").atProperty("prop2") // "/prop1/prop2"
4541 * ```
4642 */
47- public fun atProperty (property : String ): JsonPointer =
48- JsonPointer (
49- buildString {
50- val pointer = this @JsonPointer.toString()
51- append(pointer)
43+ public fun atProperty (property : String ): JsonPointer = insertLast(SegmentPointer (property))
44+
45+ override fun toString (): String {
46+ val str = asString
47+ if (str != null ) {
48+ return str
49+ }
50+ if (this !is SegmentPointer ) {
51+ return " "
52+ }
53+ return buildString {
54+ var node: JsonPointer = this @JsonPointer
55+ while (node is SegmentPointer ) {
5256 append(SEPARATOR )
53- for (ch in property) {
57+ append(escapeJsonPointer(node.propertyName))
58+ node = node.next
59+ }
60+ }.also {
61+ asString = it
62+ }
63+ }
64+
65+ internal fun insertLast (last : SegmentPointer ): JsonPointer {
66+ if (this !is SegmentPointer ) {
67+ return last
68+ }
69+ var parent: PointerParent ? = null
70+ var node: JsonPointer = this
71+ while (node is SegmentPointer ) {
72+ parent =
73+ PointerParent (
74+ parent,
75+ node.propertyName,
76+ )
77+ node = node.next
78+ }
79+ return buildPath(last, parent)
80+ }
81+
82+ private fun escapeJsonPointer (propertyName : String ): String {
83+ if (propertyName.contains(SEPARATOR ) || propertyName.contains(QUOTATION )) {
84+ return buildString(capacity = propertyName.length + 1 ) {
85+ for (ch in propertyName) {
5486 when (ch) {
55- QUOTATION -> append(QUOTATION ).append(QUOTATION_ESCAPE )
5687 SEPARATOR -> append(QUOTATION ).append(SEPARATOR_ESCAPE )
88+ QUOTATION -> append(QUOTATION ).append(QUOTATION_ESCAPE )
5789 else -> append(ch)
5890 }
5991 }
60- },
61- )
62-
63- override fun toString (): String {
64- return if (pathOffset <= 0 ) {
65- fullPath
66- } else {
67- fullPath.substring(pathOffset)
92+ }
6893 }
94+ return propertyName
6995 }
7096
7197 override fun equals (other : Any? ): Boolean {
@@ -74,13 +100,34 @@ public sealed class JsonPointer(
74100
75101 other as JsonPointer
76102
77- if (fullPath != other.fullPath) return false
78- return pathOffset == other.pathOffset
103+ var node = this
104+ var otherNode = other
105+ while (node is SegmentPointer && otherNode is SegmentPointer ) {
106+ if (node.propertyName != otherNode.propertyName) {
107+ return false
108+ }
109+ node = node.next
110+ otherNode = otherNode.next
111+ }
112+ return node is EmptyPointer && otherNode is EmptyPointer
79113 }
80114
81115 override fun hashCode (): Int {
82- var result = fullPath.hashCode()
83- result = 31 * result + pathOffset
116+ if (hash != 0 ) {
117+ return hash
118+ }
119+ var result = 31
120+ var node = this
121+ while (node is SegmentPointer ) {
122+ result = 31 * result + node.propertyName.hashCode()
123+ node = node.next
124+ }
125+ if (result == 0 ) {
126+ // just in case if for some reason the resulting has is zero
127+ // this way we won't recalculate it again
128+ result = 31
129+ }
130+ hash = result
84131 return result
85132 }
86133
@@ -118,42 +165,32 @@ public sealed class JsonPointer(
118165 }
119166 }
120167
121- @JvmStatic
122- private fun parseExpression (expr : String ): JsonPointer {
123- class PointerParent (
124- val parent : PointerParent ? ,
125- val startOffset : Int ,
126- val segment : String ,
127- )
168+ private class PointerParent (
169+ val parent : PointerParent ? ,
170+ val segment : String ,
171+ )
128172
129- fun buildPath (
130- start : Int ,
131- lastSegment : String ,
132- parent : PointerParent ? ,
133- ): JsonPointer {
134- var curr =
135- SegmentPointer (
136- expr,
137- start,
138- lastSegment,
139- EmptyPointer ,
140- )
141- var parentValue = parent
142- while (parentValue != null ) {
143- curr =
144- parentValue.run {
145- SegmentPointer (
146- expr,
147- startOffset,
148- segment,
149- curr,
150- )
151- }
152- parentValue = parentValue.parent
153- }
154- return curr
173+ private fun buildPath (
174+ lastSegment : SegmentPointer ,
175+ parent : PointerParent ? ,
176+ ): JsonPointer {
177+ var curr = lastSegment
178+ var parentValue = parent
179+ while (parentValue != null ) {
180+ curr =
181+ parentValue.run {
182+ SegmentPointer (
183+ segment,
184+ curr,
185+ )
186+ }
187+ parentValue = parentValue.parent
155188 }
189+ return curr
190+ }
156191
192+ @JvmStatic
193+ private fun parseExpression (expr : String ): JsonPointer {
157194 var parent: PointerParent ? = null
158195
159196 var offset = 1 // skip contextual slash
@@ -162,7 +199,7 @@ public sealed class JsonPointer(
162199 while (offset < end) {
163200 val currentChar = expr[offset]
164201 if (currentChar == SEPARATOR ) {
165- parent = PointerParent (parent, start, expr.substring(start + 1 , offset))
202+ parent = PointerParent (parent, expr.substring(start + 1 , offset))
166203 start = offset
167204 offset++
168205 continue
@@ -173,15 +210,15 @@ public sealed class JsonPointer(
173210 offset = builder.appendEscapedSegment(expr, start + 1 , offset)
174211 val segment = builder.toString()
175212 if (offset < 0 ) {
176- return buildPath(start, segment, parent)
213+ return buildPath(SegmentPointer ( segment) , parent)
177214 }
178- parent = PointerParent (parent, start, segment)
215+ parent = PointerParent (parent, segment)
179216 start = offset
180217 offset++
181218 continue
182219 }
183220 }
184- return buildPath(start, expr.substring(start + 1 ), parent)
221+ return buildPath(SegmentPointer ( expr.substring(start + 1 ) ), parent)
185222 }
186223 }
187224}
@@ -229,14 +266,12 @@ private fun StringBuilder.appendEscaped(ch: Char) {
229266 append(result)
230267}
231268
232- internal object EmptyPointer : JsonPointer(fullPath = " " , pathOffset = 0 )
269+ internal object EmptyPointer : JsonPointer()
233270
234271internal class SegmentPointer (
235- fullPath : String ,
236- pathOffset : Int ,
237272 segment : String ,
238- next : JsonPointer ? = null ,
239- ) : JsonPointer(fullPath, pathOffset, next) {
273+ override val next : JsonPointer = EmptyPointer ,
274+ ) : JsonPointer(next) {
240275 val propertyName: String = segment
241276 val index: Int = parseIndex(segment)
242277
0 commit comments