) {
115 | for (command in commands) {
116 | command.accept(visitor)
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonTest/kotlin/com/reidsync/kxjsonpatch/JsonDiffTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 flipkart.com zjsonpatch.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.reidsync.kxjsonpatch
17 |
18 | import com.reidsync.kxjsonpatch.utils.GsonObjectMapper
19 | import kotlinx.serialization.json.JsonArray
20 | import kotlinx.serialization.json.JsonElement
21 | import kotlinx.serialization.json.JsonObject
22 | import kotlinx.serialization.json.JsonPrimitive
23 | import kotlinx.serialization.json.jsonArray
24 | import kotlinx.serialization.json.jsonObject
25 | import resources.testdata.TestData_SAMPLE
26 | import kotlin.test.BeforeTest
27 | import kotlin.test.Test
28 | import kotlin.test.assertEquals
29 |
30 | /**
31 | * Unit test
32 | */
33 | class JsonDiffTest {
34 | var objectMapper = GsonObjectMapper()
35 | lateinit var jsonNode: JsonArray
36 |
37 | @BeforeTest
38 | fun setUp() {
39 | jsonNode = objectMapper.readTree(TestData_SAMPLE).jsonArray
40 | }
41 |
42 | @Test
43 | fun testSampleJsonDiff() {
44 | for (i in 0 until jsonNode.size) {
45 | val first: JsonElement = jsonNode.get(i).jsonObject.get("first")!!
46 | val second: JsonElement = jsonNode.get(i).jsonObject.get("second")!!
47 | println("Test # $i")
48 | println(first)
49 | println(second)
50 | val actualPatch: JsonElement = JsonDiff.asJson(first, second)
51 | println(actualPatch)
52 | val secondPrime: JsonElement = JsonPatch.apply(actualPatch, first)
53 | println(secondPrime)
54 | assertEquals(second, secondPrime)
55 | }
56 | }
57 |
58 | @Test
59 | fun testGeneratedJsonDiff() {
60 | for (i in 0..999) {
61 | val first: JsonElement = TestDataGenerator.generate((0..10).random())
62 | val second: JsonElement = TestDataGenerator.generate((0..10).random())
63 | val actualPatch: JsonElement = JsonDiff.asJson(first, second)
64 | println("Test # $i")
65 | println(first)
66 | println(second)
67 | println(actualPatch)
68 | val secondPrime: JsonElement = JsonPatch.apply(actualPatch, first)
69 | println(secondPrime)
70 | assertEquals(second, secondPrime)
71 | }
72 | }
73 |
74 | /**
75 | * REMOVE operation did result in JsonPatch generated with value field present.
76 | * That should not happen.
77 | */
78 | @Test
79 | fun testNoValueShouldBePresentInRemoveOperation() {
80 | val first = JsonObject(mapOf("key" to JsonPrimitive("value")))
81 | val second = JsonObject(emptyMap())
82 | val patch: JsonElement = JsonDiff.asJson(first, second)
83 | println(first)
84 | println(second)
85 | println(patch)
86 | val expectedPatch = JsonArray(
87 | content =
88 | listOf(
89 | JsonObject(
90 | mapOf(
91 | "op" to JsonPrimitive("remove"),
92 | "path" to JsonPrimitive("/key"),
93 | )
94 | )
95 | )
96 | )
97 | assertEquals(expectedPatch, patch)
98 | }
99 |
100 | @Test
101 | fun testValueShouldBePresentInOtherOperation() {
102 | val first = JsonObject(emptyMap())
103 | val second = JsonObject(mapOf("key" to JsonPrimitive("value")))
104 | val patch: JsonElement = JsonDiff.asJson(first, second)
105 | println(first)
106 | println(second)
107 | println(patch)
108 | val expectedPatch = JsonArray(
109 | content =
110 | listOf(
111 | JsonObject(
112 | mapOf(
113 | "op" to JsonPrimitive("add"),
114 | "path" to JsonPrimitive("/key"),
115 | "value" to JsonPrimitive("value"),
116 | )
117 | )
118 | )
119 | )
120 | assertEquals(expectedPatch, patch)
121 | }
122 |
123 | }
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonMain/kotlin/com/reidsync/kxjsonpatch/lcs/CommandVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.reidsync.kxjsonpatch.lcs
2 | /*
3 | * Licensed to the Apache Software Foundation (ASF) under one or more
4 | * contributor license agreements. See the NOTICE file distributed with
5 | * this work for additional information regarding copyright ownership.
6 | * The ASF licenses this file to You under the Apache License, Version 2.0
7 | * (the "License"); you may not use this file except in compliance with
8 | * the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | /**
20 | * This interface should be implemented by user object to walk
21 | * through [EditScript] objects.
22 | *
23 | *
24 | * Users should implement this interface in order to walk through
25 | * the [EditScript] object created by the comparison
26 | * of two sequences. This is a direct application of the visitor
27 | * design pattern. The [EditScript.visit]
28 | * method takes an object implementing this interface as an argument,
29 | * it will perform the loop over all commands in the script and the
30 | * proper methods of the user class will be called as the commands are
31 | * encountered.
32 | *
33 | *
34 | * The implementation of the user visitor class will depend on the
35 | * need. Here are two examples.
36 | *
37 | *
38 | * The first example is a visitor that build the longest common
39 | * subsequence:
40 | *
41 | * import org.apache.commons.collections4.comparators.sequence.CommandVisitor;
42 | *
43 | * import java.util.ArrayList;
44 | *
45 | * public class LongestCommonSubSequence implements CommandVisitor {
46 | *
47 | * public LongestCommonSubSequence() {
48 | * a = new ArrayList();
49 | * }
50 | *
51 | * public void visitInsertCommand(Object object) {
52 | * }
53 | *
54 | * public void visitKeepCommand(Object object) {
55 | * a.add(object);
56 | * }
57 | *
58 | * public void visitDeleteCommand(Object object) {
59 | * }
60 | *
61 | * public Object[] getSubSequence() {
62 | * return a.toArray();
63 | * }
64 | *
65 | * private ArrayList a;
66 | *
67 | * }
68 | *
69 | *
70 | *
71 | * The second example is a visitor that shows the commands and the way
72 | * they transform the first sequence into the second one:
73 | *
74 | * import org.apache.commons.collections4.comparators.sequence.CommandVisitor;
75 | *
76 | * import java.util.Arrays;
77 | * import java.util.ArrayList;
78 | * import java.util.Iterator;
79 | *
80 | * public class ShowVisitor implements CommandVisitor {
81 | *
82 | * public ShowVisitor(Object[] sequence1) {
83 | * v = new ArrayList();
84 | * v.addAll(Arrays.asList(sequence1));
85 | * index = 0;
86 | * }
87 | *
88 | * public void visitInsertCommand(Object object) {
89 | * v.insertElementAt(object, index++);
90 | * display("insert", object);
91 | * }
92 | *
93 | * public void visitKeepCommand(Object object) {
94 | * ++index;
95 | * display("keep ", object);
96 | * }
97 | *
98 | * public void visitDeleteCommand(Object object) {
99 | * v.remove(index);
100 | * display("delete", object);
101 | * }
102 | *
103 | * private void display(String commandName, Object object) {
104 | * System.out.println(commandName + " " + object + " ->" + this);
105 | * }
106 | *
107 | * public String toString() {
108 | * StringBuffer buffer = new StringBuffer();
109 | * for (Iterator iter = v.iterator(); iter.hasNext();) {
110 | * buffer.append(' ').append(iter.next());
111 | * }
112 | * return buffer.toString();
113 | * }
114 | *
115 | * private ArrayList v;
116 | * private int index;
117 | *
118 | * }
119 | *
120 | *
121 | * @since 4.0
122 | * @version $Id: CommandVisitor.java 1477760 2013-04-30 18:34:03Z tn $
123 | */
124 | interface CommandVisitor {
125 | /**
126 | * Method called when an insert command is encountered.
127 | *
128 | * @param object object to insert (this object comes from the second sequence)
129 | */
130 | fun visitInsertCommand(`object`: T)
131 |
132 | /**
133 | * Method called when a keep command is encountered.
134 | *
135 | * @param object object to keep (this object comes from the first sequence)
136 | */
137 | fun visitKeepCommand(`object`: T)
138 |
139 | /**
140 | * Method called when a delete command is encountered.
141 | *
142 | * @param object object to delete (this object comes from the first sequence)
143 | */
144 | fun visitDeleteCommand(`object`: T)
145 | }
--------------------------------------------------------------------------------
/.idea/codeStyles:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | xmlns:android
34 |
35 | ^$
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | xmlns:.*
45 |
46 | ^$
47 |
48 |
49 | BY_NAME
50 |
51 |
52 |
53 |
54 |
55 |
56 | .*:id
57 |
58 | http://schemas.android.com/apk/res/android
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | .*:name
68 |
69 | http://schemas.android.com/apk/res/android
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | name
79 |
80 | ^$
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | style
90 |
91 | ^$
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | .*
101 |
102 | ^$
103 |
104 |
105 | BY_NAME
106 |
107 |
108 |
109 |
110 |
111 |
112 | .*
113 |
114 | http://schemas.android.com/apk/res/android
115 |
116 |
117 | ANDROID_ATTRIBUTE_ORDER
118 |
119 |
120 |
121 |
122 |
123 |
124 | .*
125 |
126 | .*
127 |
128 |
129 | BY_NAME
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonMain/kotlin/com/reidsync/kxjsonpatch/JsonPatch.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 flipkart.com zjsonpatch.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.reidsync.kxjsonpatch
18 |
19 | import kotlinx.serialization.json.*
20 | import kotlin.jvm.JvmOverloads
21 | import kotlin.jvm.JvmStatic
22 |
23 | object JsonPatch {
24 | internal var op = Operations()
25 | internal var consts = Constants()
26 |
27 | private fun getPatchAttr(jsonNode: JsonObject, attr: String): JsonElement {
28 | val child = jsonNode.get(attr) ?: throw InvalidJsonPatchException("Invalid JSON Patch payload (missing '$attr' field)")
29 | return child
30 | }
31 |
32 | private fun getPatchAttrWithDefault(jsonNode: JsonObject, attr: String, defaultValue: JsonElement): JsonElement {
33 | val child = jsonNode.get(attr)
34 | if (child == null)
35 | return defaultValue
36 | else
37 | return child
38 | }
39 |
40 | @Throws(InvalidJsonPatchException::class)
41 | private fun process(patch: JsonElement, processor: JsonPatchApplyProcessor, flags: Set) {
42 |
43 | if (patch !is JsonArray)
44 | throw InvalidJsonPatchException("Invalid JSON Patch payload (not an array)")
45 | val operations = patch.jsonArray.iterator()
46 | while (operations.hasNext()) {
47 | val jsonNode_ = operations.next()
48 | if (jsonNode_ !is JsonObject) throw InvalidJsonPatchException("Invalid JSON Patch payload (not an object)")
49 | val jsonNode = jsonNode_.jsonObject
50 | val operation = op.opFromName(getPatchAttr(jsonNode.jsonObject, consts.OP).toString().replace("\"".toRegex(), ""))
51 | val path = getPath(getPatchAttr(jsonNode, consts.PATH))
52 |
53 | when (operation) {
54 | op.REMOVE -> {
55 | processor.edit { remove(path) }
56 | }
57 |
58 | op.ADD -> {
59 | val value: JsonElement
60 | if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
61 | value = getPatchAttr(jsonNode, consts.VALUE)
62 | else
63 | value = getPatchAttrWithDefault(jsonNode, consts.VALUE, JsonNull)
64 | processor.edit { add(path, value) }
65 | }
66 |
67 | op.REPLACE -> {
68 | val value: JsonElement
69 | if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
70 | value = getPatchAttr(jsonNode, consts.VALUE)
71 | else
72 | value = getPatchAttrWithDefault(jsonNode, consts.VALUE, JsonNull)
73 | processor.edit { replace(path, value) }
74 | }
75 |
76 | op.MOVE -> {
77 | val fromPath = getPath(getPatchAttr(jsonNode, consts.FROM))
78 | processor.edit { move(fromPath, path) }
79 | }
80 |
81 | op.COPY -> {
82 | val fromPath = getPath(getPatchAttr(jsonNode, consts.FROM))
83 | processor.edit { copy(fromPath, path) }
84 | }
85 |
86 | op.TEST -> {
87 | val value: JsonElement
88 | if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
89 | value = getPatchAttr(jsonNode, consts.VALUE)
90 | else
91 | value = getPatchAttrWithDefault(jsonNode, consts.VALUE, JsonNull)
92 | processor.edit { test(path, value) }
93 | }
94 | }
95 | }
96 | }
97 |
98 | @Throws(InvalidJsonPatchException::class)
99 | @JvmStatic
100 | @JvmOverloads
101 | fun validate(patch: JsonElement, flags: Set = CompatibilityFlags.defaults()) {
102 | process(patch, NoopProcessor.INSTANCE, flags)
103 | }
104 |
105 | @Throws(JsonPatchApplicationException::class)
106 | @JvmStatic
107 | @JvmOverloads
108 | fun apply(patch: JsonElement, source: JsonElement, flags: Set = CompatibilityFlags.defaults()): JsonElement {
109 | val processor = ApplyProcessor(source)
110 | process(patch, processor, flags)
111 | return processor.result()
112 | }
113 |
114 |
115 | private fun decodePath(path: String): String {
116 | return path.replace("~1".toRegex(), "/").replace("~0".toRegex(), "~") // see http://tools.ietf.org/html/rfc6901#section-4
117 | }
118 |
119 | private fun getPath(path: JsonElement): List {
120 | // List paths = Splitter.on('/').splitToList(path.toString().replaceAll("\"", ""));
121 | // return Lists.newArrayList(Iterables.transform(paths, DECODE_PATH_FUNCTION));
122 | val pathstr = path.toString().replace("\"", "")
123 | val paths = pathstr.split("/")
124 | return paths.map { decodePath(it) }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [kotlin-json-patch]
2 | [](https://dl.circleci.com/status-badge/redirect/circleci/FX4uQvXfdGbsC2LtLwAcHN/6WSq31hZUQd6ntmfk7zYqZ/tree/main)
3 | [](https://dl.circleci.com/status-badge/redirect/circleci/FX4uQvXfdGbsC2LtLwAcHN/6WSq31hZUQd6ntmfk7zYqZ/tree/main)
4 | [](http://kotlinlang.org/)
5 | [](https://www.apache.org/licenses/LICENSE-2.0.txt)
6 | [](https://central.sonatype.com/artifact/io.github.reidsync/kotlin-json-patch/overview)
7 |
8 | ![badge-support-kotlin-multiplatform]
9 | ![badge-support-android-native]
10 | ![badge-support-apple-silicon]
11 |
12 | ![badge-platform-android]
13 | ![badge-platform-ios]
14 | ![badge-platform-jvm]
15 | ![badge-platform-js]
16 |
17 | ## Kotlin JSON Patching Library
18 |
19 | ### This is an implementation of [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) written exclusively in Kotlin.
20 | It is based on the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) licensed library from Flipkart, [zjsonpatch](https://github.com/flipkart-incubator/zjsonpatch).
21 | This project is a fork of [KJsonPatch](https://github.com/beyondeye/kjsonpatch) (with the [latest commit referenced](https://github.com/beyondeye/kjsonpatch/commit/939455832a09de666d9578963676996b5e09b6be)).
22 |
23 | ## Changes
24 |
25 | This code has been modified from the original library in the following ways:
26 | * Ported from Java to Kotlin
27 | * Changed package names
28 | * Substituted Gson dependency with [`kotlinx.serialization.json`](https://kotlinlang.org/api/latest/kotlin.test/)
29 | * Added extensions for convenient usage of [`kotlinx.serialization.json`](https://kotlinlang.org/api/latest/kotlin.test/)
30 |
31 | ## Setup
32 | Add the dependency to your app module’s `build.gradle` file:
33 |
34 | ```kotlin
35 | repositories {
36 | mavenCentral()
37 | }
38 |
39 | dependencies {
40 | // e.g., implementation("io.github.reidsync:kotlin-json-patch:1.0.0")
41 | implementation("io.github.reidsync:kotlin-json-patch:${kotliln_json_patch_version}")
42 | }
43 | ```
44 | > _**Check the [kotlin-json-patch versions](https://central.sonatype.com/artifact/io.github.reidsync/kotlin-json-patch/versions)**_
45 | latest version : [](https://central.sonatype.com/artifact/io.github.reidsync/kotlin-json-patch/overview)
46 |
47 | You can add the dependency to `sourceSets.commonMain.dependecies` for your Kotlin Multiplatform project.
48 |
49 | ```kotlin
50 | kotlin {
51 | sourceSets {
52 | commonMain.dependencies {
53 | //put your multiplatform dependencies here
54 | implementation("io.github.reidsync:kotlin-json-patch:${kotliln_json_patch_version}")
55 | }
56 | }
57 | }
58 |
59 | ```
60 |
61 | ## API Usage
62 |
63 | > The variables `source`, `target`, and `patch` below must be asserted as valid `JsonElement` objects.
64 | ### Generating JSON Diff as a patch
65 | ```kotlin
66 | val diff: JsonArray = JsonDiff.asJson(source: JsonElement, target: JsonElement)
67 | ```
68 | or
69 | ```kotlin
70 | val diff: JsonElement = source.generatePatch(with: target)
71 | ```
72 | ### Applying JSON patch
73 | ```kotlin
74 | val result: JsonElement = JsonPatch.apply(patch: JsonElement, source: JsonElement)
75 | ```
76 | or
77 | ```kotlin
78 | val result: JsonElement = source.apply(patch: patch)
79 | ```
80 | This operation is performed on a clone of the source object.
81 |
82 | ##
83 | These changes mostly involve porting from Java to Kotlin to transform it into a pure Kotlin library that can be imported into Kotlin Multiplatform. If you have any specific preferences or further adjustments, feel free to let me know!
84 |
85 |
97 |
98 |
99 | [badge-platform-android]: https://img.shields.io/badge/-android-6EDB8D.svg?logo=android&&logoColor=white&style=flat
100 | [badge-platform-jvm]: https://img.shields.io/badge/-jvm-DB413D.svg?logo=jvm&logoColor=white&style=flat
101 | [badge-platform-js]: https://img.shields.io/badge/-js-F8DB5D.svg?logo=JavaScript&logoColor=white&style=flat
102 | [badge-platform-js-node]: https://img.shields.io/badge/-nodejs-68a063.svg?logo=nodedotjs&logoColor=white&style=flat
103 | [badge-platform-linux]: https://img.shields.io/badge/-linux-2D3F6C.svg?logo=linux&logoColor=white&style=flat
104 | [badge-platform-macos]: https://img.shields.io/badge/-macos-111111.svg?logo=macOS&logoColor=white&style=flat
105 | [badge-platform-ios]: https://img.shields.io/badge/-ios-CDCDCD.svg?logo=iOS&logoColor=white&style=flat
106 | [badge-platform-tvos]: https://img.shields.io/badge/-tvos-808080.svg?logo=AppleTV&logoColor=white&style=flat
107 | [badge-platform-watchos]: https://img.shields.io/badge/-watchos-C0C0C0.svg?logo=Apple&logoColor=white&style=flat
108 | [badge-platform-wasm]: https://img.shields.io/badge/-wasm-624FE8.svg?logo=webassembly&logoColor=white&style=flat
109 | [badge-platform-windows]: https://img.shields.io/badge/-windows-4D76CD.svg?logo=Windows&logoColor=whitestyle=flat
110 | [badge-support-android-native]: https://img.shields.io/badge/support-Android%20Native-6EDB8D.svg?style=flat?fontColor=white
111 | [badge-support-apple-silicon]: https://img.shields.io/badge/support-Apple%20Silicon-808080.svg?style=flat
112 | [badge-support-kotlin-multiplatform]: https://img.shields.io/badge/support-Kotlin%20Multiplatform-6A5ACD.svg?style=flat
113 | [badge-support-js-ir]: https://img.shields.io/badge/support-[js--IR]-AAC4E0.svg?style=flat
114 | [badge-support-linux-arm]: https://img.shields.io/badge/support-[LinuxArm]-2D3F6C.svg?style=flat
115 |
116 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,xcode,macos,kotlin,swift,swiftpackagemanager
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,xcode,macos,kotlin,swift,swiftpackagemanager
3 |
4 | ### Kotlin ###
5 | # Compiled class file
6 | *.class
7 |
8 | # Log file
9 | *.log
10 |
11 | # BlueJ files
12 | *.ctxt
13 |
14 | # Mobile Tools for Java (J2ME)
15 | .mtj.tmp/
16 |
17 | # Package Files #
18 | *.jar
19 | *.war
20 | *.nar
21 | *.ear
22 | *.zip
23 | *.tar.gz
24 | *.rar
25 |
26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
27 | hs_err_pid*
28 | replay_pid*
29 |
30 | ### macOS ###
31 | # General
32 | .DS_Store
33 | .AppleDouble
34 | .LSOverride
35 |
36 | # Icon must end with two \r
37 | Icon
38 |
39 |
40 | # Thumbnails
41 | ._*
42 |
43 | # Files that might appear in the root of a volume
44 | .DocumentRevisions-V100
45 | .fseventsd
46 | .Spotlight-V100
47 | .TemporaryItems
48 | .Trashes
49 | .VolumeIcon.icns
50 | .com.apple.timemachine.donotpresent
51 |
52 | # Directories potentially created on remote AFP share
53 | .AppleDB
54 | .AppleDesktop
55 | Network Trash Folder
56 | Temporary Items
57 | .apdisk
58 |
59 | ### macOS Patch ###
60 | # iCloud generated files
61 | *.icloud
62 |
63 | ### Swift ###
64 | # Xcode
65 | #
66 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
67 |
68 | ## User settings
69 | xcuserdata/
70 |
71 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
72 | *.xcscmblueprint
73 | *.xccheckout
74 |
75 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
76 | build/
77 | DerivedData/
78 | *.moved-aside
79 | *.pbxuser
80 | !default.pbxuser
81 | *.mode1v3
82 | !default.mode1v3
83 | *.mode2v3
84 | !default.mode2v3
85 | *.perspectivev3
86 | !default.perspectivev3
87 |
88 | ## Obj-C/Swift specific
89 | *.hmap
90 |
91 | ## App packaging
92 | *.ipa
93 | *.dSYM.zip
94 | *.dSYM
95 |
96 | ## Playgrounds
97 | timeline.xctimeline
98 | playground.xcworkspace
99 |
100 | # Swift Package Manager
101 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
102 | # Packages/
103 | # Package.pins
104 | # Package.resolved
105 | # *.xcodeproj
106 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
107 | # hence it is not needed unless you have added a package configuration file to your project
108 | # .swiftpm
109 |
110 | .build/
111 |
112 | # CocoaPods
113 | # We recommend against adding the Pods directory to your .gitignore. However
114 | # you should judge for yourself, the pros and cons are mentioned at:
115 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
116 | # Pods/
117 | # Add this line if you want to avoid checking in source code from the Xcode workspace
118 | # *.xcworkspace
119 |
120 | # Carthage
121 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
122 | # Carthage/Checkouts
123 |
124 | Carthage/Build/
125 |
126 | # Accio dependency management
127 | Dependencies/
128 | .accio/
129 |
130 | # fastlane
131 | # It is recommended to not store the screenshots in the git repo.
132 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
133 | # For more information about the recommended setup visit:
134 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
135 |
136 | fastlane/report.xml
137 | fastlane/Preview.html
138 | fastlane/screenshots/**/*.png
139 | fastlane/test_output
140 |
141 | # Code Injection
142 | # After new code Injection tools there's a generated folder /iOSInjectionProject
143 | # https://github.com/johnno1962/injectionforxcode
144 |
145 | iOSInjectionProject/
146 |
147 | ### SwiftPackageManager ###
148 | Packages
149 | xcuserdata
150 | *.xcodeproj
151 |
152 |
153 | ### Xcode ###
154 |
155 | ## Xcode 8 and earlier
156 |
157 | ### Xcode Patch ###
158 | *.xcodeproj/*
159 | !*.xcodeproj/project.pbxproj
160 | !*.xcodeproj/xcshareddata/
161 | !*.xcodeproj/project.xcworkspace/
162 | !*.xcworkspace/contents.xcworkspacedata
163 | /*.gcno
164 | **/xcshareddata/WorkspaceSettings.xcsettings
165 |
166 | ### AndroidStudio ###
167 | # Covers files to be ignored for android development using Android Studio.
168 |
169 | # Built application files
170 | *.apk
171 | *.ap_
172 | *.aab
173 |
174 | # Files for the ART/Dalvik VM
175 | *.dex
176 |
177 | # Java class files
178 |
179 | # Generated files
180 | bin/
181 | gen/
182 | out/
183 |
184 | # Gradle files
185 | .gradle
186 | .gradle/
187 |
188 | # Signing files
189 | .signing/
190 |
191 | # Local configuration file (sdk path, etc)
192 | local.properties
193 |
194 | # Proguard folder generated by Eclipse
195 | proguard/
196 |
197 | # Log Files
198 |
199 | # Android Studio
200 | /*/build/
201 | /*/local.properties
202 | /*/out
203 | /*/*/build
204 | /*/*/production
205 | captures/
206 | .navigation/
207 | *.ipr
208 | *~
209 | *.swp
210 |
211 | # Keystore files
212 | *.jks
213 | *.keystore
214 |
215 | # Google Services (e.g. APIs or Firebase)
216 | # google-services.json
217 |
218 | # Android Patch
219 | gen-external-apklibs
220 |
221 | # External native build folder generated in Android Studio 2.2 and later
222 | .externalNativeBuild
223 |
224 | # NDK
225 | obj/
226 |
227 | # IntelliJ IDEA
228 | *.iml
229 | *.iws
230 | /out/
231 |
232 | # User-specific configurations
233 | .idea/caches/
234 | .idea/libraries/
235 | .idea/shelf/
236 | .idea/workspace.xml
237 | .idea/tasks.xml
238 | .idea/.name
239 | .idea/compiler.xml
240 | .idea/copyright/profiles_settings.xml
241 | .idea/encodings.xml
242 | .idea/misc.xml
243 | .idea/modules.xml
244 | .idea/scopes/scope_settings.xml
245 | .idea/dictionaries
246 | .idea/vcs.xml
247 | .idea/jsLibraryMappings.xml
248 | .idea/datasources.xml
249 | .idea/dataSources.ids
250 | .idea/sqlDataSources.xml
251 | .idea/dynamic.xml
252 | .idea/uiDesigner.xml
253 | .idea/assetWizardSettings.xml
254 | .idea/gradle.xml
255 | .idea/jarRepositories.xml
256 | .idea/navEditor.xml
257 |
258 | # Legacy Eclipse project files
259 | .classpath
260 | .project
261 | .cproject
262 | .settings/
263 |
264 | # Mobile Tools for Java (J2ME)
265 |
266 | # Package Files #
267 |
268 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
269 |
270 | ## Plugin-specific files:
271 |
272 | # mpeltonen/sbt-idea plugin
273 | .idea_modules/
274 |
275 | # JIRA plugin
276 | atlassian-ide-plugin.xml
277 |
278 | # Mongo Explorer plugin
279 | .idea/mongoSettings.xml
280 |
281 | # Crashlytics plugin (for Android Studio and IntelliJ)
282 | com_crashlytics_export_strings.xml
283 | crashlytics.properties
284 | crashlytics-build.properties
285 | fabric.properties
286 |
287 | ### AndroidStudio Patch ###
288 |
289 | !/gradle/wrapper/gradle-wrapper.jar
290 |
291 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,xcode,macos,kotlin,swift,swiftpackagemanager
292 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | if ! command -v java >/dev/null 2>&1
134 | then
135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
136 |
137 | Please set the JAVA_HOME variable in your environment to match the
138 | location of your Java installation."
139 | fi
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 |
201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
203 |
204 | # Collect all arguments for the java command;
205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
206 | # shell script including quotes and variable substitutions, so put them in
207 | # double quotes to make sure that they get re-expanded; and
208 | # * put everything else in single quotes, so that it's not re-expanded.
209 |
210 | set -- \
211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
212 | -classpath "$CLASSPATH" \
213 | org.gradle.wrapper.GradleWrapperMain \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonMain/kotlin/com/reidsync/kxjsonpatch/JsonPatchEditingContextImpl.kt:
--------------------------------------------------------------------------------
1 | package com.reidsync.kxjsonpatch
2 |
3 | import kotlinx.serialization.json.*
4 |
5 | /*
6 | * Copyright 2023 Reid Byun.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | */
20 |
21 | class JsonPatchEditingContextImpl(var source: JsonElement): JsonPatchEditingContext {
22 | override fun remove(path: List) {
23 | source = editElement(source, path, action = { root ->
24 | if (path.isEmpty()) {
25 | throw JsonPatchApplicationException("[Remove Operation] path is empty")
26 | }
27 | else {
28 | var parentNode = root//getParentNode(root, searchPath)
29 | if (parentNode == null) {
30 | throw JsonPatchApplicationException("[Remove Operation] noSuchPath in source, path provided : " + path)
31 | }
32 | else {
33 | val fieldToRemove = path[path.size - 1].replace("\"".toRegex(), "")
34 | if (parentNode is JsonObject) {
35 | val copyp = parentNode.remove(fieldToRemove)
36 | println(parentNode)
37 | println(copyp)
38 | println(root)
39 | parentNode = copyp
40 | }
41 | else if (parentNode is JsonArray) {
42 | parentNode = parentNode.remove(arrayIndex(fieldToRemove, parentNode.size - 1))
43 | //return parentNode
44 | }
45 | else {
46 | throw JsonPatchApplicationException("[Remove Operation] noSuchPath in source, path provided : " + path)
47 | }
48 | }
49 | parentNode
50 | }}) ?: source
51 | }
52 |
53 | override fun replace(path: List, value: JsonElement) {
54 | source = editElement(source, path, action = { root ->
55 | if (path.isEmpty()) {
56 | throw JsonPatchApplicationException("[Replace Operation] path is empty")
57 | } else {
58 | var parentNode = getParentNode(source, path)
59 | if (parentNode == null) {
60 | throw JsonPatchApplicationException("[Replace Operation] noSuchPath in source, path provided : " + path)
61 | } else {
62 | val fieldToReplace = path[path.size - 1].replace("\"".toRegex(), "")
63 | if (fieldToReplace.isEmpty() && path.size == 1) {
64 | parentNode = value
65 | }
66 | else if (parentNode is JsonObject) {
67 | parentNode = parentNode.add(fieldToReplace, value)
68 | }
69 | else if (parentNode is JsonArray) {
70 | parentNode = parentNode.set(arrayIndex(fieldToReplace, parentNode.size - 1), value)
71 | }
72 | else {
73 | throw JsonPatchApplicationException("[Replace Operation] noSuchPath in source, path provided : " + path)
74 | }
75 | parentNode
76 | }
77 | }
78 | }) ?: source
79 | }
80 |
81 | override fun add(path: List, value: JsonElement) {
82 | source = editElement(source, path, action = { root ->
83 | if (path.isEmpty()) {
84 | throw JsonPatchApplicationException("[ADD Operation] path is empty , path : ")
85 | } else {
86 | var parentNode = root//getParentNode(root, searchPath)
87 | if (parentNode == null) {
88 | throw JsonPatchApplicationException("[ADD Operation] noSuchPath in source, path provided : " + path)
89 | } else {
90 | val fieldToReplace = path[path.size - 1].replace("\"".toRegex(), "")
91 | if (fieldToReplace == "" && path.size == 1)
92 | parentNode = value
93 | else if (!parentNode.isContainerNode()) {
94 | throw JsonPatchApplicationException("[ADD Operation] parent is not a container in source, path provided : $path | node : $parentNode")
95 | }
96 | else if (parentNode is JsonArray) {
97 | parentNode = addToArray(path, value, parentNode)
98 | }
99 | else {
100 | parentNode = addToObject(path, parentNode, value)
101 | }
102 | }
103 | parentNode
104 | }
105 | }) ?: source
106 | }
107 |
108 | override fun move(fromPath: List, toPath: List) {
109 | val parentNode = getParentNode(source, fromPath)
110 | val field = fromPath[fromPath.size - 1].replace("\"".toRegex(), "")
111 | val valueNode = if (parentNode!! is JsonArray) {
112 | parentNode.jsonArray[field.toInt()]
113 | }
114 | else {
115 | parentNode.jsonObject[field]
116 | }
117 |
118 | remove(fromPath)
119 | add(toPath, valueNode!!)
120 | }
121 |
122 | override fun copy(fromPath: List, toPath: List) {
123 | val parentNode = getParentNode(source, fromPath)
124 | val field = fromPath[fromPath.size - 1].replace("\"".toRegex(), "")
125 | val valueNode = if (parentNode!! is JsonArray) {
126 | parentNode.jsonArray[field.toInt()]
127 | }
128 | else {
129 | parentNode.jsonObject[field]
130 | }
131 | add(toPath, valueNode!!)
132 | }
133 |
134 | override fun test(path: List, value: JsonElement) {
135 | source = editElement(source, path, action = { root ->
136 | if (path.isEmpty()) {
137 | throw JsonPatchApplicationException("[TEST Operation] path is empty , path : ")
138 | } else {
139 | var parentNode = root
140 | if (parentNode == null) {
141 | throw JsonPatchApplicationException("[TEST Operation] noSuchPath in source, path provided : " + path)
142 | }
143 | else {
144 | val fieldToReplace = path[path.size - 1].replace("\"".toRegex(), "")
145 | if (fieldToReplace == "" && path.size == 1)
146 | parentNode = value
147 | else if (!parentNode.isContainerNode())
148 | throw JsonPatchApplicationException("[TEST Operation] parent is not a container in source, path provided : $path | node : $parentNode")
149 | else if (parentNode is JsonArray) {
150 | val target = parentNode
151 | val idxStr = path[path.size - 1]
152 |
153 | if ("-" == idxStr) {
154 | // see http://tools.ietf.org/html/rfc6902#section-4.1
155 | if (target.get(target.size - 1) != value) {
156 | throw JsonPatchApplicationException("[TEST Operation] value mismatch")
157 | }
158 | } else {
159 | val idx = arrayIndex(idxStr.replace("\"".toRegex(), ""), target.size)
160 | if (target.get(idx) != value) {
161 | throw JsonPatchApplicationException("[TEST Operation] value mismatch")
162 | }
163 | }
164 | } else {
165 | val target = parentNode as JsonObject
166 | val key = path[path.size - 1].replace("\"".toRegex(), "")
167 | if (target.get(key) != value) {
168 | throw JsonPatchApplicationException("[TEST Operation] value mismatch")
169 | }
170 | }
171 | parentNode
172 | }
173 | }
174 | }) ?: source
175 | }
176 |
177 | private fun getParentNode(source: JsonElement, fromPath: List): JsonElement? {
178 | val pathToParent = fromPath.subList(0, fromPath.size - 1) // would never by out of bound, lets see
179 | return getNode(source, pathToParent, 1)
180 | }
181 |
182 | private fun getNode(ret: JsonElement, path: List, pos_: Int): JsonElement? {
183 | var pos = pos_
184 | if (pos >= path.size) {
185 | return ret
186 | }
187 | val key = path[pos]
188 | if (ret is JsonArray) {
189 | val keyInt = (key.replace("\"".toRegex(), "")).toInt()
190 | return getNode(ret[keyInt], path, ++pos)
191 | } else if (ret is JsonObject) {
192 | if (ret.containsKey(key)) {
193 | return getNode(ret[key]!!, path, ++pos)
194 | }
195 | return null
196 | } else {
197 | return ret
198 | }
199 | }
200 |
201 | private fun editElement(source: JsonElement, fromPath: List, action: (JsonElement)-> JsonElement?): JsonElement? {
202 | val pathToParent = fromPath.subList(0, fromPath.size - 1) // would never by out of bound, lets see
203 | return findAndAction(source, pathToParent, 1, action)
204 | }
205 |
206 | private fun findAndAction(ret: JsonElement, path: List, pos_: Int, action: (JsonElement)-> JsonElement?): JsonElement? {
207 | var pos = pos_
208 | if (pos >= path.size) {
209 | // Result
210 | return action(ret)
211 | }
212 | val key = path[pos]
213 | if (ret is JsonArray) {
214 | val keyInt = (key.replace("\"".toRegex(), "")).toInt()
215 | return ret.set(keyInt, findAndAction(ret[keyInt], path, ++pos, action))
216 | }
217 | else if (ret is JsonObject) {
218 | if (ret.containsKey(key)) {
219 | return ret.set(key, findAndAction(ret[key]!!, path, ++pos, action))
220 | }
221 | return null
222 | } else {
223 | // Result
224 | return action(ret)
225 | }
226 | }
227 |
228 | private fun arrayIndex(s: String, max: Int): Int {
229 | val index = s.toInt()
230 | if (index < 0) {
231 | throw JsonPatchApplicationException("index Out of bound, index is negative")
232 | } else if (index > max) {
233 | throw JsonPatchApplicationException("index Out of bound, index is greater than " + max)
234 | }
235 | return index
236 | }
237 |
238 | private fun addToObject(path: List, node: JsonElement, value: JsonElement): JsonObject {
239 | val target = node as JsonObject
240 | val key = path[path.size - 1].replace("\"".toRegex(), "")
241 |
242 | return target.add(key, value)
243 | }
244 |
245 | private fun addToArray(path: List, value: JsonElement, parentNode: JsonElement): JsonElement {
246 | var target = parentNode as JsonArray
247 | val idxStr = path[path.size - 1]
248 |
249 | if ("-" == idxStr) {
250 | // see http://tools.ietf.org/html/rfc6902#section-4.1
251 | //target.add(value)
252 | target = target.add(value)
253 | } else {
254 | //val idx = arrayIndex(idxStr.replace("\"".toRegex(), ""), target.size())
255 | val idx = arrayIndex(idxStr.replace("\"".toRegex(), ""), target.size)
256 | target = target.insert(idx, value)
257 | }
258 |
259 | return target
260 | }
261 | }
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonTest/kotlin/resources/js-libs-samples.json.kt:
--------------------------------------------------------------------------------
1 | package resources.testdata
2 |
3 | const val TestData_JS_LIB_SAMPLES: String = """
4 | {
5 | "errors": [
6 | { "node": {"bar": [1, 2]},
7 | "op": [{"op": "add", "path": "/bar/8", "value": "5"}],
8 | "message": "Out of bounds (upper)" },
9 |
10 | { "node": {"bar": [1, 2]},
11 | "op": [{"op": "add", "path": "/bar/-1", "value": "5"}],
12 | "message": "Out of bounds (lower)" },
13 |
14 | { "node": ["foo", "sil"],
15 | "op": [{"op": "add", "path": "/bar", "value": 42}],
16 | "message": "Object operation on array target" },
17 |
18 | { "node": {"foo": 1, "baz": [{"qux": "hello"}]},
19 | "op": [{"op": "remove", "path": "/baz/1e0/qux"}],
20 | "message": "remove op shouldn't remove from array with bad number" },
21 |
22 | { "node": [1, 2, 3, 4],
23 | "op": [{"op": "remove", "path": "/1e0"}],
24 | "message": "remove op shouldn't remove from array with bad number" },
25 |
26 | { "node": [""],
27 | "op": [{"op": "replace", "path": "/1e0", "value": false}],
28 | "message": "replace op shouldn't replace in array with bad number" },
29 |
30 | { "node": {"baz": [1,2,3], "bar": 1},
31 | "op": [{"op": "copy", "from": "/baz/1e0", "path": "/boo"}],
32 | "message": "copy op shouldn't work with bad number" },
33 |
34 | { "node": {"foo": 1, "baz": [1,2,3,4]},
35 | "op": [{"op": "move", "from": "/baz/1e0", "path": "/foo"}],
36 | "message": "move op shouldn't work with bad number" },
37 |
38 | { "node": ["foo", "sil"],
39 | "op": [{"op": "add", "path": "/1e0", "value": "bar"}],
40 | "message": "add op shouldn't add to array with bad number" },
41 |
42 | { "node": [ 1 ],
43 | "op": [ { "op": "add", "path": "/-" } ],
44 | "message": "missing 'value' parameter" },
45 |
46 | { "node": [ 1 ],
47 | "op": [ { "op": "replace", "path": "/0" } ],
48 | "message": "missing 'value' parameter" },
49 |
50 | { "node": [ null ],
51 | "op": [ { "op": "test", "path": "/0" } ],
52 | "message": "missing 'value' parameter" },
53 |
54 | { "node": [ false ],
55 | "op": [ { "op": "test", "path": "/0" } ],
56 | "message": "missing 'value' parameter" },
57 |
58 | { "node": [ 1 ],
59 | "op": [ { "op": "copy", "path": "/-" } ],
60 | "message": "missing 'from' parameter" },
61 |
62 | { "node": { "foo": 1 },
63 | "op": [ { "op": "move", "path": "" } ],
64 | "message": "missing 'from' parameter" },
65 |
66 | { "node": { "foo": "bar" },
67 | "op": [ { "op": "add", "path": "/baz", "value": "qux",
68 | "op": "move", "from":"/foo" } ],
69 | "message": "patch has two 'op' members",
70 | "disabled": true },
71 |
72 | { "node": {"foo": 1},
73 | "op": [{"op": "spam", "path": "/foo", "value": 1}],
74 | "message": "Unrecognized op 'spam'" }
75 |
76 | ],
77 | "ops": [
78 | { "message": "replacing the root of the document is possible with add",
79 | "node": {"foo": "bar"},
80 | "op": [{"op": "add", "path": "", "value": {"baz": "qux"}}],
81 | "expected": {"baz":"qux"}},
82 |
83 | { "message": "replacing the root of the document is possible with add",
84 | "node": {"foo": "bar"},
85 | "op": [{"op": "add", "path": "", "value": ["baz", "qux"]}],
86 | "expected": ["baz", "qux"]},
87 |
88 | { "message": "empty list, empty docs",
89 | "node": {},
90 | "op": [],
91 | "expected": {} },
92 |
93 | { "message": "empty patch list",
94 | "node": {"foo": 1},
95 | "op": [],
96 | "expected": {"foo": 1} },
97 |
98 | { "message": "rearrangements OK?",
99 | "node": {"foo": 1, "bar": 2},
100 | "op": [],
101 | "expected": {"bar":2, "foo": 1} },
102 |
103 | { "message": "rearrangements OK? How about one level down ... array",
104 | "node": [{"foo": 1, "bar": 2}],
105 | "op": [],
106 | "expected": [{"bar":2, "foo": 1}] },
107 |
108 | { "message": "rearrangements OK? How about one level down...",
109 | "node": {"foo":{"foo": 1, "bar": 2}},
110 | "op": [],
111 | "expected": {"foo":{"bar":2, "foo": 1}} },
112 |
113 | { "message": "add replaces any existing field",
114 | "node": {"foo": null},
115 | "op": [{"op": "add", "path": "/foo", "value":1}],
116 | "expected": {"foo": 1} },
117 |
118 | { "message": "toplevel array",
119 | "node": [],
120 | "op": [{"op": "add", "path": "/0", "value": "foo"}],
121 | "expected": ["foo"] },
122 |
123 | { "message": "toplevel array, no change",
124 | "node": ["foo"],
125 | "op": [],
126 | "expected": ["foo"] },
127 |
128 | { "message": "toplevel object, numeric string",
129 | "node": {},
130 | "op": [{"op": "add", "path": "/foo", "value": "1"}],
131 | "expected": {"foo":"1"} },
132 |
133 | { "message": "toplevel object, integer",
134 | "node": {},
135 | "op": [{"op": "add", "path": "/foo", "value": 1}],
136 | "expected": {"foo":1} },
137 |
138 | { "message": "Toplevel scalar values OK?",
139 | "node": "foo",
140 | "op": [{"op": "replace", "path": "", "value": "bar"}],
141 | "expected": "bar",
142 | "disabled": true },
143 |
144 | { "message": "Add, / target",
145 | "node": {},
146 | "op": [ {"op": "add", "path": "/", "value":1 } ],
147 | "expected": {"":1} },
148 |
149 | { "message": "Add composite value at top level",
150 | "node": {"foo": 1},
151 | "op": [{"op": "add", "path": "/bar", "value": [1, 2]}],
152 | "expected": {"foo": 1, "bar": [1, 2]} },
153 |
154 | { "message": "Add into composite value",
155 | "node": {"foo": 1, "baz": [{"qux": "hello"}]},
156 | "op": [{"op": "add", "path": "/baz/0/foo", "value": "world"}],
157 | "expected": {"foo": 1, "baz": [{"qux": "hello", "foo": "world"}]} },
158 |
159 | { "node": {"foo": 1},
160 | "op": [{"op": "add", "path": "/bar", "value": true}],
161 | "expected": {"foo": 1, "bar": true} },
162 |
163 | { "node": {"foo": 1},
164 | "op": [{"op": "add", "path": "/bar", "value": false}],
165 | "expected": {"foo": 1, "bar": false} },
166 |
167 | { "node": {"foo": 1},
168 | "op": [{"op": "add", "path": "/bar", "value": null}],
169 | "expected": {"foo": 1, "bar": null} },
170 |
171 | { "message": "0 can be an array index or object element name",
172 | "node": {"foo": 1},
173 | "op": [{"op": "add", "path": "/0", "value": "bar"}],
174 | "expected": {"foo": 1, "0": "bar" } },
175 |
176 | { "node": ["foo"],
177 | "op": [{"op": "add", "path": "/1", "value": "bar"}],
178 | "expected": ["foo", "bar"] },
179 |
180 | { "node": ["foo", "sil"],
181 | "op": [{"op": "add", "path": "/1", "value": "bar"}],
182 | "expected": ["foo", "bar", "sil"] },
183 |
184 | { "node": ["foo", "sil"],
185 | "op": [{"op": "add", "path": "/0", "value": "bar"}],
186 | "expected": ["bar", "foo", "sil"] },
187 |
188 | { "node": ["foo", "sil"],
189 | "op": [{"op":"add", "path": "/2", "value": "bar"}],
190 | "expected": ["foo", "sil", "bar"] },
191 |
192 | { "node": ["foo", "sil"],
193 | "op": [{"op": "add", "path": "/1", "value": ["bar", "baz"]}],
194 | "expected": ["foo", ["bar", "baz"], "sil"],
195 | "message": "value in array add not flattened" },
196 |
197 | { "node": {"foo": 1, "bar": [1, 2, 3, 4]},
198 | "op": [{"op": "remove", "path": "/bar"}],
199 | "expected": {"foo": 1} },
200 |
201 | { "node": {"foo": 1, "baz": [{"qux": "hello"}]},
202 | "op": [{"op": "remove", "path": "/baz/0/qux"}],
203 | "expected": {"foo": 1, "baz": [{}]} },
204 |
205 | { "node": {"foo": 1, "baz": [{"qux": "hello"}]},
206 | "op": [{"op": "replace", "path": "/foo", "value": [1, 2, 3, 4]}],
207 | "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]} },
208 |
209 | { "node": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]},
210 | "op": [{"op": "replace", "path": "/baz/0/qux", "value": "world"}],
211 | "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "world"}]} },
212 |
213 | { "node": ["foo"],
214 | "op": [{"op": "replace", "path": "/0", "value": "bar"}],
215 | "expected": ["bar"] },
216 |
217 | { "node": [""],
218 | "op": [{"op": "replace", "path": "/0", "value": 0}],
219 | "expected": [0] },
220 |
221 | { "node": [""],
222 | "op": [{"op": "replace", "path": "/0", "value": true}],
223 | "expected": [true] },
224 |
225 | { "node": [""],
226 | "op": [{"op": "replace", "path": "/0", "value": false}],
227 | "expected": [false] },
228 |
229 | { "node": [""],
230 | "op": [{"op": "replace", "path": "/0", "value": null}],
231 | "expected": [null] },
232 |
233 | { "node": ["foo", "sil"],
234 | "op": [{"op": "replace", "path": "/1", "value": ["bar", "baz"]}],
235 | "expected": ["foo", ["bar", "baz"]],
236 | "message": "value in array replace not flattened" },
237 |
238 | { "message": "replace whole document",
239 | "node": {"foo": "bar"},
240 | "op": [{"op": "replace", "path": "", "value": {"baz": "qux"}}],
241 | "expected": {"baz": "qux"} },
242 |
243 | { "node": {"foo": null},
244 | "op": [{"op": "replace", "path": "/foo", "value": "truthy"}],
245 | "expected": {"foo": "truthy"},
246 | "message": "null value should be valid obj property to be replaced with something truthy" },
247 |
248 | { "node": {"foo": null},
249 | "op": [{"op": "remove", "path": "/foo"}],
250 | "expected": {},
251 | "message": "null value should be valid obj property to be removed" },
252 |
253 | { "node": {"foo": "bar"},
254 | "op": [{"op": "replace", "path": "/foo", "value": null}],
255 | "expected": {"foo": null},
256 | "message": "null value should still be valid obj property replace other value" },
257 |
258 | { "message": "Move to same location has no effect",
259 | "node": {"foo": 1},
260 | "op": [{"op": "move", "from": "/foo", "path": "/foo"}],
261 | "expected": {"foo": 1} },
262 |
263 | { "node": {"foo": 1, "baz": [{"qux": "hello"}]},
264 | "op": [{"op": "move", "from": "/foo", "path": "/bar"}],
265 | "expected": {"baz": [{"qux": "hello"}], "bar": 1} },
266 |
267 | { "node": {"baz": [{"qux": "hello"}], "bar": 1},
268 | "op": [{"op": "move", "from": "/baz/0/qux", "path": "/baz/1"}],
269 | "expected": {"baz": [{}, "hello"], "bar": 1} },
270 |
271 | { "message": "Adding to \"/-\" adds to the end of the array",
272 | "node": [ 1, 2 ],
273 | "op": [ { "op": "add", "path": "/-", "value": { "foo": [ "bar", "baz" ] } } ],
274 | "expected": [ 1, 2, { "foo": [ "bar", "baz" ] } ]},
275 |
276 | { "message": "Adding to \"/-\" adds to the end of the array, even n levels down",
277 | "node": [ 1, 2, [ 3, [ 4, 5 ] ] ],
278 | "op": [ { "op": "add", "path": "/2/1/-", "value": { "foo": [ "bar", "baz" ] } } ],
279 | "expected": [ 1, 2, [ 3, [ 4, 5, { "foo": [ "bar", "baz" ] } ] ] ]},
280 |
281 | { "message": "test remove on array",
282 | "node": [1, 2, 3, 4],
283 | "op": [{"op": "remove", "path": "/0"}],
284 | "expected": [2, 3, 4] },
285 |
286 | { "message": "test repeated removes",
287 | "node": [1, 2, 3, 4],
288 | "op": [{ "op": "remove", "path": "/1" },
289 | { "op": "remove", "path": "/2" }],
290 | "expected": [1, 3] }
291 | ]
292 | }
293 | """
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonMain/kotlin/com/reidsync/kxjsonpatch/lcs/SequencesComparator.kt:
--------------------------------------------------------------------------------
1 | package com.reidsync.kxjsonpatch.lcs
2 |
3 | import kotlin.jvm.JvmOverloads
4 |
5 | /*
6 | * Licensed to the Apache Software Foundation (ASF) under one or more
7 | * contributor license agreements. See the NOTICE file distributed with
8 | * this work for additional information regarding copyright ownership.
9 | * The ASF licenses this file to You under the Apache License, Version 2.0
10 | * (the "License"); you may not use this file except in compliance with
11 | * the License. You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 | /**
22 | * This class allows to compare two objects sequences.
23 | *
24 | *
25 | * The two sequences can hold any object type, as only the `equals`
26 | * method is used to compare the elements of the sequences. It is guaranteed
27 | * that the comparisons will always be done as `o1.equals(o2)` where
28 | * `o1` belongs to the first sequence and `o2` belongs to
29 | * the second sequence. This can be important if subclassing is used for some
30 | * elements in the first sequence and the `equals` method is
31 | * specialized.
32 | *
33 | *
34 | * Comparison can be seen from two points of view: either as giving the smallest
35 | * modification allowing to transform the first sequence into the second one, or
36 | * as giving the longest sequence which is a subsequence of both initial
37 | * sequences. The `equals` method is used to compare objects, so any
38 | * object can be put into sequences. Modifications include deleting, inserting
39 | * or keeping one object, starting from the beginning of the first sequence.
40 | *
41 | *
42 | * This class implements the comparison algorithm, which is the very efficient
43 | * algorithm from Eugene W. Myers
44 | * [
45 | * An O(ND) Difference Algorithm and Its Variations](http://www.cis.upenn.edu/~bcpierce/courses/dd/papers/diff.ps). This algorithm produces
46 | * the shortest possible
47 | * [edit script][EditScript]
48 | * containing all the
49 | * [commands][EditCommand]
50 | * needed to transform the first sequence into the second one.
51 | *
52 | * @see EditScript
53 | *
54 | * @see EditCommand
55 | *
56 | * @see CommandVisitor
57 | *
58 | *
59 | * @since 4.0
60 | * @version $Id: SequencesComparator.java 1540567 2013-11-10 22:19:29Z tn $
61 | */
62 |
63 | class SequencesComparator @JvmOverloads constructor(
64 | //class SequencesComparator constructor(
65 | sequence1: List,
66 | sequence2: List,
67 | equator: Equator = DefaultEquator.defaultEquator()
68 | ) {
69 | /** First sequence. */
70 | private val sequence1: List
71 |
72 | /** Second sequence. */
73 | private val sequence2: List
74 |
75 | /** The equator used for testing object equality. */
76 | private val equator: Equator
77 |
78 | /** Temporary variables. */
79 | private val vDown: IntArray
80 | private val vUp: IntArray
81 | /**
82 | * Simple constructor.
83 | *
84 | *
85 | * Creates a new instance of SequencesComparator with a custom [Equator].
86 | *
87 | *
88 | * It is *guaranteed* that the comparisons will always be done as
89 | * `Equator.equate(o1, o2)` where `o1` belongs to the first
90 | * sequence and `o2` belongs to the second sequence.
91 | *
92 | * @param sequence1 first sequence to be compared
93 | * @param sequence2 second sequence to be compared
94 | * @param equator the equator to use for testing object equality
95 | */
96 | /**
97 | * Simple constructor.
98 | *
99 | *
100 | * Creates a new instance of SequencesComparator using a [DefaultEquator].
101 | *
102 | *
103 | * It is *guaranteed* that the comparisons will always be done as
104 | * `o1.equals(o2)` where `o1` belongs to the first
105 | * sequence and `o2` belongs to the second sequence. This can be
106 | * important if subclassing is used for some elements in the first sequence
107 | * and the `equals` method is specialized.
108 | *
109 | * @param sequence1 first sequence to be compared
110 | * @param sequence2 second sequence to be compared
111 | */
112 | init {
113 | this.sequence1 = sequence1
114 | this.sequence2 = sequence2
115 | this.equator = equator
116 | val size = sequence1.size + sequence2.size + 2
117 | vDown = IntArray(size)
118 | vUp = IntArray(size)
119 | }
120 |
121 | /**
122 | * Get the [EditScript] object.
123 | *
124 | *
125 | * It is guaranteed that the objects embedded in the [ insert commands][InsertCommand] come from the second sequence and that the objects
126 | * embedded in either the [delete commands][DeleteCommand] or
127 | * [keep commands][KeepCommand] come from the first sequence. This can
128 | * be important if subclassing is used for some elements in the first
129 | * sequence and the `equals` method is specialized.
130 | *
131 | * @return the edit script resulting from the comparison of the two
132 | * sequences
133 | */
134 | fun getScript(): EditScript {
135 | val script = EditScript()
136 | buildScript(0, sequence1.size, 0, sequence2.size, script)
137 | return script
138 | }
139 |
140 | /**
141 | * Build a snake.
142 | *
143 | * @param start the value of the start of the snake
144 | * @param diag the value of the diagonal of the snake
145 | * @param end1 the value of the end of the first sequence to be compared
146 | * @param end2 the value of the end of the second sequence to be compared
147 | * @return the snake built
148 | */
149 | private fun buildSnake(start: Int, diag: Int, end1: Int, end2: Int): Snake {
150 | var end = start
151 | while (end - diag < end2 && end < end1 && equator.equate(
152 | sequence1[end],
153 | sequence2[end - diag]
154 | )
155 | ) {
156 | ++end
157 | }
158 | return Snake(start, end, diag)
159 | }
160 |
161 | /**
162 | * Get the middle snake corresponding to two subsequences of the
163 | * main sequences.
164 | *
165 | *
166 | * The snake is found using the MYERS Algorithm (this algorithms has
167 | * also been implemented in the GNU diff program). This algorithm is
168 | * explained in Eugene Myers article:
169 | * [
170 | * An O(ND) Difference Algorithm and Its Variations](http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps).
171 | *
172 | * @param start1 the begin of the first sequence to be compared
173 | * @param end1 the end of the first sequence to be compared
174 | * @param start2 the begin of the second sequence to be compared
175 | * @param end2 the end of the second sequence to be compared
176 | * @return the middle snake
177 | */
178 | private fun getMiddleSnake(start1: Int, end1: Int, start2: Int, end2: Int): Snake? {
179 | // Myers Algorithm
180 | // Initialisations
181 | val m = end1 - start1
182 | val n = end2 - start2
183 | if (m == 0 || n == 0) {
184 | return null
185 | }
186 | val delta = m - n
187 | val sum = n + m
188 | val offset = (if (sum % 2 == 0) sum else sum + 1) / 2
189 | vDown[1 + offset] = start1
190 | vUp[1 + offset] = end1 + 1
191 | for (d in 0..offset) {
192 | // Down
193 | run {
194 | var k = -d
195 | while (k <= d) {
196 |
197 | // First step
198 | val i = k + offset
199 | if (k == -d || k != d && vDown[i - 1] < vDown[i + 1]) {
200 | vDown[i] = vDown[i + 1]
201 | } else {
202 | vDown[i] = vDown[i - 1] + 1
203 | }
204 | var x = vDown[i]
205 | var y = x - start1 + start2 - k
206 | while (x < end1 && y < end2 && equator.equate(
207 | sequence1[x],
208 | sequence2[y]
209 | )
210 | ) {
211 | vDown[i] = ++x
212 | ++y
213 | }
214 | // Second step
215 | if (delta % 2 != 0 && delta - d <= k && k <= delta + d) {
216 | if (vUp[i - delta] <= vDown[i]) {
217 | return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2)
218 | }
219 | }
220 | k += 2
221 | }
222 | }
223 |
224 | // Up
225 | var k = delta - d
226 | while (k <= delta + d) {
227 |
228 | // First step
229 | val i = k + offset - delta
230 | if (k == delta - d
231 | || k != delta + d && vUp[i + 1] <= vUp[i - 1]
232 | ) {
233 | vUp[i] = vUp[i + 1] - 1
234 | } else {
235 | vUp[i] = vUp[i - 1]
236 | }
237 | var x = vUp[i] - 1
238 | var y = x - start1 + start2 - k
239 | while (x >= start1 && y >= start2 && equator.equate(sequence1[x], sequence2[y])) {
240 | vUp[i] = x--
241 | y--
242 | }
243 | // Second step
244 | if (delta % 2 == 0 && -d <= k && k <= d) {
245 | if (vUp[i] <= vDown[i + delta]) {
246 | return buildSnake(vUp[i], k + start1 - start2, end1, end2)
247 | }
248 | }
249 | k += 2
250 | }
251 | }
252 | throw RuntimeException("Internal Error")
253 | }
254 |
255 | /**
256 | * Build an edit script.
257 | *
258 | * @param start1 the begin of the first sequence to be compared
259 | * @param end1 the end of the first sequence to be compared
260 | * @param start2 the begin of the second sequence to be compared
261 | * @param end2 the end of the second sequence to be compared
262 | * @param script the edited script
263 | */
264 | private fun buildScript(
265 | start1: Int, end1: Int, start2: Int, end2: Int,
266 | script: EditScript
267 | ) {
268 | val middle = getMiddleSnake(start1, end1, start2, end2)
269 | if (middle == null || middle.start === end1 && middle.diag === end1 - end2 || middle.end === start1 && middle.diag === start1 - start2) {
270 | var i = start1
271 | var j = start2
272 | while (i < end1 || j < end2) {
273 | if (i < end1 && j < end2 && equator.equate(sequence1[i], sequence2[j])) {
274 | script.append(KeepCommand(sequence1[i]))
275 | ++i
276 | ++j
277 | } else {
278 | if (end1 - start1 > end2 - start2) {
279 | script.append(DeleteCommand(sequence1[i]))
280 | ++i
281 | } else {
282 | script.append(InsertCommand(sequence2[j]))
283 | ++j
284 | }
285 | }
286 | }
287 | } else {
288 | buildScript(
289 | start1, middle.start,
290 | start2, middle.start - middle.diag,
291 | script
292 | )
293 | for (i in middle.start until middle.end) {
294 | script.append(KeepCommand(sequence1[i]))
295 | }
296 | buildScript(
297 | middle.end, end1,
298 | middle.end - middle.diag, end2,
299 | script
300 | )
301 | }
302 | }
303 | /**
304 | * This class is a simple placeholder to hold the end part of a path
305 | * under construction in a [SequencesComparator].
306 | */
307 |
308 |
309 | private class Snake
310 | /**
311 | * Simple constructor. Creates a new instance of Snake with specified indices.
312 | *
313 | * @param start start index of the snake
314 | * @param end end index of the snake
315 | * @param diag diagonal number
316 | */(
317 | /** Start index. */
318 | val start: Int,
319 | /** End index. */
320 | val end: Int,
321 | /** Diagonal number. */
322 | val diag: Int
323 | ) {
324 | /**
325 | * Get the start index of the snake.
326 | *
327 | * @return start index of the snake
328 | */
329 | /**
330 | * Get the end index of the snake.
331 | *
332 | * @return end index of the snake
333 | */
334 | /**
335 | * Get the diagonal number of the snake.
336 | *
337 | * @return diagonal number of the snake
338 | */
339 |
340 | }
341 | }
--------------------------------------------------------------------------------
/kotlin-json-patch/src/commonMain/kotlin/com/reidsync/kxjsonpatch/JsonDiff.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 flipkart.com zjsonpatch.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.reidsync.kxjsonpatch
18 |
19 | import com.reidsync.kxjsonpatch.lcs.ListUtils
20 | import kotlinx.serialization.json.*
21 | import kotlin.jvm.JvmStatic
22 | import kotlin.math.min
23 |
24 | object JsonDiff {
25 | internal var op = Operations()
26 | internal var consts = Constants()
27 |
28 |
29 | @JvmStatic
30 | fun asJson(source: JsonElement, target: JsonElement): JsonArray {
31 | val diffs = ArrayList()
32 | val path = ArrayList()
33 | /*
34 | * generating diffs in the order of their occurrence
35 | */
36 | generateDiffs(diffs, path, source, target)
37 | /*
38 | * Merging remove & add to move operation
39 | */
40 | compactDiffs(diffs)
41 | /*
42 | * Introduce copy operation
43 | */
44 | introduceCopyOperation(source, target, diffs)
45 |
46 | return getJsonNodes(diffs)
47 | }
48 |
49 | private fun getMatchingValuePath(unchangedValues: Map>, value: JsonElement): List? {
50 | return unchangedValues[value]
51 | }
52 |
53 | private fun introduceCopyOperation(source: JsonElement, target: JsonElement, diffs: MutableList) {
54 | val unchangedValues = getUnchangedPart(source, target)
55 | for (i in diffs.indices) {
56 | val diff = diffs[i]
57 | if (op.ADD==diff.operation) {
58 | val matchingValuePath = getMatchingValuePath(unchangedValues, diff.value)
59 | if (matchingValuePath != null) {
60 | diffs[i] = Diff(op.COPY, matchingValuePath, diff.path)
61 | }
62 | }
63 | }
64 | }
65 |
66 | private fun getUnchangedPart(source: JsonElement, target: JsonElement): Map> {
67 | val unchangedValues = HashMap>()
68 | computeUnchangedValues(unchangedValues, listOf(), source, target)
69 | return unchangedValues
70 | }
71 |
72 | private fun computeUnchangedValues(unchangedValues: MutableMap>, path: List, source: JsonElement, target: JsonElement) {
73 | if (source == target) {
74 | unchangedValues.put(target, path)
75 | return
76 | }
77 |
78 | val firstType = NodeType.getNodeType(source)
79 | val secondType = NodeType.getNodeType(target)
80 |
81 | if (firstType == secondType) {
82 | when (firstType) {
83 | NodeType.OBJECT -> computeObject(unchangedValues, path, source.jsonObject, target.jsonObject)
84 | NodeType.ARRAY -> computeArray(unchangedValues, path, source.jsonArray, target.jsonArray)
85 | }/* nothing */
86 | }
87 | }
88 |
89 | private fun computeArray(unchangedValues: MutableMap>, path: List, source: JsonArray, target: JsonArray) {
90 | val size = min(source.size, target.size)
91 |
92 | for (i in 0..size - 1) {
93 | val currPath = getPath(path, i)
94 | computeUnchangedValues(unchangedValues, currPath, source.get(i), target.get(i))
95 | }
96 | }
97 |
98 | private fun computeObject(unchangedValues: MutableMap>, path: List, source: JsonObject, target: JsonObject) {
99 | //val firstFields = source.entrySet().iterator()
100 | val firstFields = source.iterator()
101 | while (firstFields.hasNext()) {
102 | val name = firstFields.next().key
103 | if (target.containsKey(name)) {
104 | val currPath = getPath(path, name)
105 | computeUnchangedValues(unchangedValues, currPath, source.get(name)!!, target.get(name)!!)
106 | }
107 | }
108 | }
109 |
110 | /**
111 | * This method merge 2 diffs ( remove then add, or vice versa ) with same value into one Move operation,
112 | * all the core logic resides here only
113 | */
114 | private fun compactDiffs(diffs: MutableList) {
115 | var i=-1
116 | while (++i <=diffs.size-1) {
117 | val diff1 = diffs[i]
118 |
119 | // if not remove OR add, move to next diff
120 | if (!(op.REMOVE==diff1.operation || op.ADD==diff1.operation)) {
121 | continue
122 | }
123 |
124 | for (j in i + 1..diffs.size - 1) {
125 | val diff2 = diffs[j]
126 | if (diff1.value != diff2.value) {
127 | continue
128 | }
129 |
130 | var moveDiff: Diff? = null
131 | if (op.REMOVE==diff1.operation && op.ADD==diff2.operation) {
132 | computeRelativePath(diff2.path, i + 1, j - 1, diffs)
133 | moveDiff = Diff(op.MOVE, diff1.path, diff2.path)
134 |
135 | } else if (op.ADD==diff1.operation && op.REMOVE==diff2.operation) {
136 | computeRelativePath(diff2.path, i, j - 1, diffs) // diff1's add should also be considered
137 | moveDiff = Diff(op.MOVE, diff2.path, diff1.path)
138 | }
139 | if (moveDiff != null) {
140 | diffs.removeAt(j)
141 | diffs[i] = moveDiff
142 | break
143 | }
144 | }
145 | }
146 | }
147 |
148 | //Note : only to be used for arrays
149 | //Finds the longest common Ancestor ending at Array
150 | private fun computeRelativePath(path: MutableList, startIdx: Int, endIdx: Int, diffs: List) {
151 | val counters = ArrayList()
152 |
153 | resetCounters(counters, path.size)
154 |
155 | for (i in startIdx..endIdx) {
156 | val diff = diffs[i]
157 | //Adjust relative path according to #ADD and #Remove
158 | if (op.ADD==diff.operation || op.REMOVE==diff.operation) {
159 | updatePath(path, diff, counters)
160 | }
161 | }
162 | updatePathWithCounters(counters, path)
163 | }
164 |
165 | private fun resetCounters(counters: MutableList, size: Int) {
166 | for (i in 0..size - 1) {
167 | counters.add(0)
168 | }
169 | }
170 |
171 | private fun updatePathWithCounters(counters: List, path: MutableList) {
172 | for (i in counters.indices) {
173 | val value = counters[i]
174 | if (value != 0) {
175 | val currValue = path[i].toString().toInt()
176 | path[i] = (currValue + value).toString()
177 | }
178 | }
179 | }
180 |
181 | private fun updatePath(path: List, pseudo: Diff, counters: MutableList) {
182 | //find longest common prefix of both the paths
183 |
184 | if (pseudo.path.size <= path.size) {
185 | var idx = -1
186 | for (i in 0..pseudo.path.size - 1 - 1) {
187 | if (pseudo.path[i] == path[i]) {
188 | idx = i
189 | } else {
190 | break
191 | }
192 | }
193 | if (idx == pseudo.path.size - 2) {
194 | if (pseudo.path[pseudo.path.size - 1] is Int) {
195 | updateCounters(pseudo, pseudo.path.size - 1, counters)
196 | }
197 | }
198 | }
199 | }
200 |
201 | private fun updateCounters(pseudo: Diff, idx: Int, counters: MutableList) {
202 | if (op.ADD==pseudo.operation) {
203 | counters[idx] = counters[idx] - 1
204 | } else {
205 | if (op.REMOVE==pseudo.operation) {
206 | counters[idx] = counters[idx] + 1
207 | }
208 | }
209 | }
210 |
211 | private fun getJsonNodes(diffs: List): JsonArray {
212 | var patch = JsonArray(emptyList())
213 | for (diff in diffs) {
214 | val jsonNode = getJsonNode(diff)
215 | patch = patch.add(jsonNode)
216 | }
217 | return patch
218 | }
219 |
220 | private fun getJsonNode(diff: Diff): JsonObject {
221 | var jsonNode = JsonObject(emptyMap())
222 | jsonNode = jsonNode.addProperty(consts.OP, op.nameFromOp(diff.operation))
223 | if (op.MOVE==diff.operation || op.COPY==diff.operation) {
224 | jsonNode = jsonNode.addProperty(consts.FROM, getArrayNodeRepresentation(diff.path)) //required {from} only in case of Move Operation
225 | jsonNode = jsonNode.addProperty(consts.PATH, getArrayNodeRepresentation(diff.toPath)) // destination Path
226 | } else {
227 | jsonNode = jsonNode.addProperty(consts.PATH, getArrayNodeRepresentation(diff.path))
228 | if (op.REMOVE != diff.operation) {
229 | jsonNode = jsonNode.add(consts.VALUE, diff.value)
230 | }
231 | }
232 | return jsonNode
233 | }
234 |
235 |
236 | private fun EncodePath(`object`: Any): String {
237 | val path = `object`.toString() // see http://tools.ietf.org/html/rfc6901#section-4
238 | return path.replace("~".toRegex(), "~0").replace("/".toRegex(), "~1")
239 | }
240 | //join path parts in argument 'path', inserting a '/' between joined elements, starting with '/' and transforming the element of the list with ENCODE_PATH_FUNCTION
241 | private fun getArrayNodeRepresentation(path: List): String {
242 | // return Joiner.on('/').appendTo(new StringBuilder().append('/'),
243 | // Iterables.transform(path, ENCODE_PATH_FUNCTION)).toString();
244 | val sb = StringBuilder()
245 | for (i in path.indices) {
246 | sb.append('/')
247 | sb.append(EncodePath(path[i]))
248 |
249 | }
250 | return sb.toString()
251 | }
252 |
253 |
254 |
255 | private fun generateDiffs(diffs: MutableList, path: List, source: JsonElement, target: JsonElement) {
256 | if (source != target) {
257 | val sourceType = NodeType.getNodeType(source)
258 | val targetType = NodeType.getNodeType(target)
259 |
260 | if (sourceType == NodeType.ARRAY && targetType == NodeType.ARRAY) {
261 | //both are arrays
262 | compareArray(diffs, path, source.jsonArray, target.jsonArray)
263 | } else if (sourceType == NodeType.OBJECT && targetType == NodeType.OBJECT) {
264 | //both are json
265 | compareObjects(diffs, path, source.jsonObject, target.jsonObject)
266 | } else {
267 | //can be replaced
268 |
269 | diffs.add(Diff.generateDiff(op.REPLACE, path, target))
270 | }
271 | }
272 | }
273 |
274 | private fun compareArray(diffs: MutableList, path: List, source: JsonArray, target: JsonArray) {
275 | val lcs = getLCS(source, target)
276 | var srcIdx = 0
277 | var targetIdx = 0
278 | var lcsIdx = 0
279 | val srcSize = source.size
280 | val targetSize = target.size
281 | val lcsSize = lcs.size
282 |
283 | var pos = 0
284 | while (lcsIdx < lcsSize) {
285 | val lcsNode = lcs[lcsIdx]
286 | val srcNode = source.get(srcIdx)
287 | val targetNode = target.get(targetIdx)
288 |
289 |
290 | if (lcsNode == srcNode && lcsNode == targetNode) { // Both are same as lcs node, nothing to do here
291 | srcIdx++
292 | targetIdx++
293 | lcsIdx++
294 | pos++
295 | } else {
296 | if (lcsNode == srcNode) { // src node is same as lcs, but not targetNode
297 | //addition
298 | val currPath = getPath(path, pos)
299 | diffs.add(Diff.generateDiff(op.ADD, currPath, targetNode))
300 | pos++
301 | targetIdx++
302 | } else if (lcsNode == targetNode) { //targetNode node is same as lcs, but not src
303 | //removal,
304 | val currPath = getPath(path, pos)
305 | diffs.add(Diff.generateDiff(op.REMOVE, currPath, srcNode))
306 | srcIdx++
307 | } else {
308 | val currPath = getPath(path, pos)
309 | //both are unequal to lcs node
310 | generateDiffs(diffs, currPath, srcNode, targetNode)
311 | srcIdx++
312 | targetIdx++
313 | pos++
314 | }
315 | }
316 | }
317 |
318 | while (srcIdx < srcSize && targetIdx < targetSize) {
319 | val srcNode = source.get(srcIdx)
320 | val targetNode = target.get(targetIdx)
321 | val currPath = getPath(path, pos)
322 | generateDiffs(diffs, currPath, srcNode, targetNode)
323 | srcIdx++
324 | targetIdx++
325 | pos++
326 | }
327 | pos = addRemaining(diffs, path, target, pos, targetIdx, targetSize)
328 | removeRemaining(diffs, path, pos, srcIdx, srcSize, source)
329 | }
330 |
331 | private fun removeRemaining(diffs: MutableList, path: List, pos: Int, srcIdx_: Int, srcSize: Int, source_: JsonElement): Int {
332 | var srcIdx = srcIdx_
333 | val source = source_.jsonArray
334 | while (srcIdx < srcSize) {
335 | val currPath = getPath(path, pos)
336 | diffs.add(Diff.generateDiff(op.REMOVE, currPath, source.get(srcIdx)))
337 | srcIdx++
338 | }
339 | return pos
340 | }
341 |
342 | private fun addRemaining(diffs: MutableList, path: List, target_: JsonElement, pos_: Int, targetIdx_: Int, targetSize: Int): Int {
343 | var pos = pos_
344 | var targetIdx = targetIdx_
345 | val target = target_.jsonArray
346 | while (targetIdx < targetSize) {
347 | val jsonNode = target.get(targetIdx)
348 | val currPath = getPath(path, pos)
349 | diffs.add(Diff.generateDiff(op.ADD, currPath, jsonNode.deepCopy()))
350 | pos++
351 | targetIdx++
352 | }
353 | return pos
354 | }
355 |
356 | private fun compareObjects(diffs: MutableList, path: List, source: JsonObject, target: JsonObject) {
357 | val keysFromSrc = source.iterator()
358 | while (keysFromSrc.hasNext()) {
359 | val key = keysFromSrc.next().key
360 | if (!target.containsKey(key)) {
361 | //remove case
362 | val currPath = getPath(path, key)
363 | diffs.add(Diff.generateDiff(op.REMOVE, currPath, source.get(key)!!))
364 | continue
365 | }
366 | val currPath = getPath(path, key)
367 | generateDiffs(diffs, currPath, source.get(key)!!, target.get(key)!!)
368 | }
369 | val keysFromTarget = target.iterator()
370 | while (keysFromTarget.hasNext()) {
371 | val key = keysFromTarget.next().key
372 | if (!source.containsKey(key)) {
373 | //add case
374 | val currPath = getPath(path, key)
375 | diffs.add(Diff.generateDiff(op.ADD, currPath, target.get(key)!!))
376 | }
377 | }
378 | }
379 |
380 | private fun getPath(path: List, key: Any): List {
381 | val toReturn = ArrayList()
382 | toReturn.addAll(path)
383 | toReturn.add(key)
384 | return toReturn
385 | }
386 |
387 | private fun getLCS(first_: JsonElement, second_: JsonElement): List {
388 | if (first_ !is JsonArray) throw IllegalArgumentException("LCS can only work on JSON arrays")
389 | if (second_ !is JsonArray) throw IllegalArgumentException("LCS can only work on JSON arrays")
390 | val first = first_ as JsonArray
391 | val second = second_ as JsonArray
392 | return ListUtils.longestCommonSubsequence(first.toList(),second.toList())
393 | }
394 | }
395 |
396 |
397 |
--------------------------------------------------------------------------------