@@ -14,10 +14,11 @@ public fun JsonPointer(path: String): JsonPointer = JsonPointer.compile(path)
14
14
* [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901).
15
15
*/
16
16
public 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 ,
20
18
) {
19
+ private var asString: String? = null
20
+ private var hash: Int = 0
21
+
21
22
/* *
22
23
* Creates a new [JsonPointer] that points to an [index] in the array.
23
24
*
@@ -26,15 +27,10 @@ public sealed class JsonPointer(
26
27
* val pointer = JsonPointer("/test").atIndex(0) // "/test/0"
27
28
* ```
28
29
*/
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
+ }
38
34
39
35
/* *
40
36
* Creates a new [JsonPointer] that points to a [property] passed as a parameter.
@@ -44,28 +40,58 @@ public sealed class JsonPointer(
44
40
* val pointer = JsonPointer.ROOT.atProperty("prop1").atProperty("prop2") // "/prop1/prop2"
45
41
* ```
46
42
*/
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 ) {
52
56
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) {
54
86
when (ch) {
55
- QUOTATION -> append(QUOTATION ).append(QUOTATION_ESCAPE )
56
87
SEPARATOR -> append(QUOTATION ).append(SEPARATOR_ESCAPE )
88
+ QUOTATION -> append(QUOTATION ).append(QUOTATION_ESCAPE )
57
89
else -> append(ch)
58
90
}
59
91
}
60
- },
61
- )
62
-
63
- override fun toString (): String {
64
- return if (pathOffset <= 0 ) {
65
- fullPath
66
- } else {
67
- fullPath.substring(pathOffset)
92
+ }
68
93
}
94
+ return propertyName
69
95
}
70
96
71
97
override fun equals (other : Any? ): Boolean {
@@ -74,13 +100,34 @@ public sealed class JsonPointer(
74
100
75
101
other as JsonPointer
76
102
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
79
113
}
80
114
81
115
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
84
131
return result
85
132
}
86
133
@@ -118,42 +165,32 @@ public sealed class JsonPointer(
118
165
}
119
166
}
120
167
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
+ )
128
172
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
155
188
}
189
+ return curr
190
+ }
156
191
192
+ @JvmStatic
193
+ private fun parseExpression (expr : String ): JsonPointer {
157
194
var parent: PointerParent ? = null
158
195
159
196
var offset = 1 // skip contextual slash
@@ -162,7 +199,7 @@ public sealed class JsonPointer(
162
199
while (offset < end) {
163
200
val currentChar = expr[offset]
164
201
if (currentChar == SEPARATOR ) {
165
- parent = PointerParent (parent, start, expr.substring(start + 1 , offset))
202
+ parent = PointerParent (parent, expr.substring(start + 1 , offset))
166
203
start = offset
167
204
offset++
168
205
continue
@@ -173,15 +210,15 @@ public sealed class JsonPointer(
173
210
offset = builder.appendEscapedSegment(expr, start + 1 , offset)
174
211
val segment = builder.toString()
175
212
if (offset < 0 ) {
176
- return buildPath(start, segment, parent)
213
+ return buildPath(SegmentPointer ( segment) , parent)
177
214
}
178
- parent = PointerParent (parent, start, segment)
215
+ parent = PointerParent (parent, segment)
179
216
start = offset
180
217
offset++
181
218
continue
182
219
}
183
220
}
184
- return buildPath(start, expr.substring(start + 1 ), parent)
221
+ return buildPath(SegmentPointer ( expr.substring(start + 1 ) ), parent)
185
222
}
186
223
}
187
224
}
@@ -229,14 +266,12 @@ private fun StringBuilder.appendEscaped(ch: Char) {
229
266
append(result)
230
267
}
231
268
232
- internal object EmptyPointer : JsonPointer(fullPath = " " , pathOffset = 0 )
269
+ internal object EmptyPointer : JsonPointer()
233
270
234
271
internal class SegmentPointer (
235
- fullPath : String ,
236
- pathOffset : Int ,
237
272
segment : String ,
238
- next : JsonPointer ? = null ,
239
- ) : JsonPointer(fullPath, pathOffset, next) {
273
+ override val next : JsonPointer = EmptyPointer ,
274
+ ) : JsonPointer(next) {
240
275
val propertyName: String = segment
241
276
val index: Int = parseIndex(segment)
242
277
0 commit comments