From 23f3ea00411548f4bd0e5d26c7d4263da0ea2957 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Wed, 14 Aug 2024 18:53:40 +0530 Subject: [PATCH] chore (deps) : Directly rely on upstream zjsonpatch repository instead of fabric8io fork Add a new module zjsonpatch that would directly include `com.flipkart.zjsonpatch:zjsonpatch` dependency excluding `org.apache.commons:commons-collections4`. Port required method `ListUtils.longestCommonSubsequence` and related classes inside zjsonpatch module Signed-off-by: Rohan Kumar --- .../testing/KubernetesResourceDiff.java | 4 +- .../mock/crud/KubernetesCrudPersistence.java | 2 +- .../client/server/mock/crud/PatchHandler.java | 2 +- .../mockwebserver/crud/CrudDispatcher.java | 2 +- kubernetes-client/pom.xml | 1 - .../client/dsl/internal/PatchUtils.java | 2 +- .../features/src/main/resources/feature.xml | 2 +- pom.xml | 20 +- uberjar/pom.xml | 8 +- zjsonpatch/pom.xml | 97 ++++++ .../commons/collections4/DefaultEquator.java | 80 +++++ .../apache/commons/collections4/Equator.java | 27 ++ .../commons/collections4/ListUtils.java | 95 ++++++ .../collections4/sequence/CommandVisitor.java | 46 +++ .../collections4/sequence/DeleteCommand.java | 44 +++ .../collections4/sequence/EditCommand.java | 57 ++++ .../collections4/sequence/EditScript.java | 113 +++++++ .../collections4/sequence/InsertCommand.java | 46 +++ .../collections4/sequence/KeepCommand.java | 46 +++ .../sequence/SequencesComparator.java | 308 ++++++++++++++++++ .../io/fabric8/zjsonpatch/JsonDiffTest.java | 58 ++++ .../commons/collections4/ListUtilsTest.java | 53 +++ 22 files changed, 1100 insertions(+), 13 deletions(-) create mode 100644 zjsonpatch/pom.xml create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/DefaultEquator.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/Equator.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/ListUtils.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/CommandVisitor.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/DeleteCommand.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditCommand.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditScript.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/InsertCommand.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/KeepCommand.java create mode 100644 zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/SequencesComparator.java create mode 100644 zjsonpatch/src/test/java/io/fabric8/zjsonpatch/JsonDiffTest.java create mode 100644 zjsonpatch/src/test/java/org/apache/commons/collections4/ListUtilsTest.java diff --git a/java-generator/it/src/main/java/io/fabric8/java/generator/testing/KubernetesResourceDiff.java b/java-generator/it/src/main/java/io/fabric8/java/generator/testing/KubernetesResourceDiff.java index 05bbadd7bc3..b64fdba34a2 100644 --- a/java-generator/it/src/main/java/io/fabric8/java/generator/testing/KubernetesResourceDiff.java +++ b/java-generator/it/src/main/java/io/fabric8/java/generator/testing/KubernetesResourceDiff.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import com.flipkart.zjsonpatch.JsonDiff; +import com.flipkart.zjsonpatch.JsonPatch; import com.github.difflib.text.DiffRow; import com.github.difflib.text.DiffRowGenerator; -import io.fabric8.zjsonpatch.JsonDiff; -import io.fabric8.zjsonpatch.JsonPatch; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudPersistence.java b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudPersistence.java index 6136377c863..e5e787119f7 100644 --- a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudPersistence.java +++ b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudPersistence.java @@ -19,11 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.flipkart.zjsonpatch.JsonDiff; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.client.server.mock.Resetable; import io.fabric8.kubernetes.client.utils.Serialization; import io.fabric8.mockwebserver.crud.AttributeSet; -import io.fabric8.zjsonpatch.JsonDiff; import java.util.Map; import java.util.Optional; diff --git a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/PatchHandler.java b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/PatchHandler.java index d6518043375..373ecebb661 100644 --- a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/PatchHandler.java +++ b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/PatchHandler.java @@ -17,12 +17,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.flipkart.zjsonpatch.JsonPatch; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.client.dsl.base.PatchType; import io.fabric8.kubernetes.client.utils.Serialization; import io.fabric8.kubernetes.client.utils.Utils; import io.fabric8.mockwebserver.crud.AttributeSet; -import io.fabric8.zjsonpatch.JsonPatch; import okhttp3.MediaType; import okhttp3.mockwebserver.MockResponse; diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java index 9e8ef71e6c6..12d2b180462 100644 --- a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java @@ -16,9 +16,9 @@ package io.fabric8.mockwebserver.crud; import com.fasterxml.jackson.databind.JsonNode; +import com.flipkart.zjsonpatch.JsonPatch; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockServerException; -import io.fabric8.zjsonpatch.JsonPatch; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.RecordedRequest; diff --git a/kubernetes-client/pom.xml b/kubernetes-client/pom.xml index 20a2b613cf8..0b75bf940ef 100644 --- a/kubernetes-client/pom.xml +++ b/kubernetes-client/pom.xml @@ -74,7 +74,6 @@ io.fabric8 zjsonpatch - ${zjsonpatch.version} diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PatchUtils.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PatchUtils.java index 19a765070de..261f0e03dce 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PatchUtils.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/PatchUtils.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.flipkart.zjsonpatch.JsonDiff; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; -import io.fabric8.zjsonpatch.JsonDiff; import java.util.ArrayList; import java.util.Iterator; diff --git a/platforms/karaf/features/src/main/resources/feature.xml b/platforms/karaf/features/src/main/resources/feature.xml index f53b7c076ef..a9ebdc9cd39 100644 --- a/platforms/karaf/features/src/main/resources/feature.xml +++ b/platforms/karaf/features/src/main/resources/feature.xml @@ -37,7 +37,7 @@ mvn:org.ow2.asm/asm-tree/${asm.bundle.version} mvn:org.ow2.asm/asm-util/${asm.bundle.version} mvn:io.fabric8/kubernetes-model-common/${project.version} - mvn:io.fabric8/zjsonpatch/${zjsonpatch.version} + mvn:io.fabric8/zjsonpatch/${project.version} mvn:io.fabric8/kubernetes-model-core/${project.version} mvn:io.fabric8/kubernetes-model-rbac/${project.version} diff --git a/pom.xml b/pom.xml index 93a49fa4f9d..f59804d3f9d 100644 --- a/pom.xml +++ b/pom.xml @@ -100,7 +100,7 @@ 3.9.8 3.13.1 4.5.9 - 0.3.0 + 0.4.16 3.0.2 @@ -226,6 +226,7 @@ httpclient-vertx kubernetes-client-deps-compatibility-tests log4j + zjsonpatch @@ -715,6 +716,11 @@ kube-api-test-client-inject ${project.version} + + io.fabric8 + zjsonpatch + ${project.version} + @@ -882,13 +888,19 @@ okio ${okio.version} - - - io.fabric8 + com.flipkart.zjsonpatch zjsonpatch ${zjsonpatch.version} + + + org.apache.commons + commons-collections4 + + + + org.mockito mockito-core diff --git a/uberjar/pom.xml b/uberjar/pom.xml index b4c17d32449..9c7944bc9e0 100644 --- a/uberjar/pom.xml +++ b/uberjar/pom.xml @@ -199,6 +199,12 @@ openshift-server-mock ${project.version} + + + io.fabric8 + zjsonpatch + ${project.version} + @@ -233,7 +239,7 @@ - io.fabric8 + com.flipkart.zjsonpatch zjsonpatch ${zjsonpatch.version} diff --git a/zjsonpatch/pom.xml b/zjsonpatch/pom.xml new file mode 100644 index 00000000000..52da9ac10ca --- /dev/null +++ b/zjsonpatch/pom.xml @@ -0,0 +1,97 @@ + + + + 4.0.0 + + io.fabric8 + kubernetes-client-project + 7.0-SNAPSHOT + + + bundle + zjsonpatch + + + + * + + + com.flipkart.zjsonpatch, + org.apache.commons.collections4* + + + + + + com.flipkart.zjsonpatch + zjsonpatch + ${zjsonpatch.version} + + + org.apache.commons + commons-collections4 + + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + bundle + package + + bundle + + + + ${project.name} + ${project.groupId}.${project.artifactId} + ${osgi.export} + ${osgi.import} + ${osgi.dynamic.import} + ${osgi.private} + ${osgi.bundles} + ${osgi.activator} + ${osgi.export.service} + + + + + + + + diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/DefaultEquator.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/DefaultEquator.java new file mode 100644 index 00000000000..19774d1f9cd --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/DefaultEquator.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.io.Serializable; + +/** + * Ported from Apache + * Commons Collections + */ +public class DefaultEquator implements Equator, Serializable { + + /** + * Static instance + */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final DefaultEquator INSTANCE = new DefaultEquator(); + /** + * Hashcode used for null objects. + */ + public static final int HASHCODE_NULL = -1; + /** + * Serial version UID + */ + private static final long serialVersionUID = 825802648423525485L; + + /** + * Restricted constructor. + */ + private DefaultEquator() { + super(); + } + + /** + * Factory returning the typed singleton instance. + * + * @param the object type + * @return the singleton instance + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static DefaultEquator defaultEquator() { + return (DefaultEquator) DefaultEquator.INSTANCE; + } + + /** + * {@inheritDoc} Delegates to {@link Object#equals(Object)}. + */ + public boolean equate(final T o1, final T o2) { + return o1 == o2 || o1 != null && o1.equals(o2); + } + + /** + * {@inheritDoc} + * + * @return o.hashCode() if o is non- + * null, else {@link #HASHCODE_NULL}. + */ + public int hash(final T o) { + return o == null ? HASHCODE_NULL : o.hashCode(); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/Equator.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/Equator.java new file mode 100644 index 00000000000..b427a1a9af8 --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/Equator.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Ported from Apache + * Commons Collections + */ +public interface Equator { + boolean equate(T o1, T o2); + + int hash(T o); +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/ListUtils.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/ListUtils.java new file mode 100644 index 00000000000..936bdb4426c --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/ListUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.apache.commons.collections4.sequence.CommandVisitor; +import org.apache.commons.collections4.sequence.EditScript; +import org.apache.commons.collections4.sequence.SequencesComparator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Ported from Apache + * Commons Collections + */ +public class ListUtils { + /** + * Returns the longest common subsequence (LCS) of two sequences (lists). + * + * @param the element type + * @param a the first list + * @param b the second list + * @return the longest common subsequence + * @throws NullPointerException if either list is {@code null} + * @since 4.0 + */ + public static List longestCommonSubsequence(final List a, final List b) { + return longestCommonSubsequence(a, b, DefaultEquator.defaultEquator()); + } + + /** + * Returns the longest common subsequence (LCS) of two sequences (lists). + * + * @param the element type + * @param listA the first list + * @param listB the second list + * @param equator the equator used to test object equality + * @return the longest common subsequence + * @throws NullPointerException if either list or the equator is {@code null} + * @since 4.0 + */ + public static List longestCommonSubsequence(final List listA, final List listB, + final Equator equator) { + Objects.requireNonNull(listA, "listA"); + Objects.requireNonNull(listB, "listB"); + Objects.requireNonNull(equator, "equator"); + + final SequencesComparator comparator = new SequencesComparator<>(listA, listB, equator); + final EditScript script = comparator.getScript(); + final LcsVisitor visitor = new LcsVisitor<>(); + script.visit(visitor); + return visitor.getSubSequence(); + } + + /** + * A helper class used to construct the longest common subsequence. + */ + private static final class LcsVisitor implements CommandVisitor { + private ArrayList sequence; + + public LcsVisitor() { + sequence = new ArrayList<>(); + } + + public void visitInsertCommand(final E object) { + } + + public void visitDeleteCommand(final E object) { + } + + public void visitKeepCommand(final E object) { + sequence.add(object); + } + + public List getSubSequence() { + return sequence; + } + } + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/CommandVisitor.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/CommandVisitor.java new file mode 100644 index 00000000000..23a65657311 --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/CommandVisitor.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Ported from Apache + * Commons Collections + */ +public interface CommandVisitor { + + /** + * Method called when a delete command is encountered. + * + * @param object object to delete (this object comes from the first sequence) + */ + void visitDeleteCommand(T object); + + /** + * Method called when an insert command is encountered. + * + * @param object object to insert (this object comes from the second sequence) + */ + void visitInsertCommand(T object); + + /** + * Method called when a keep command is encountered. + * + * @param object object to keep (this object comes from the first sequence) + */ + void visitKeepCommand(T object); + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/DeleteCommand.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/DeleteCommand.java new file mode 100644 index 00000000000..e48b3fde8fe --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/DeleteCommand.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Ported from Apache + * Commons Collections + */ +public class DeleteCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of {@link DeleteCommand}. + * + * @param object the object of the first sequence that should be deleted + */ + public DeleteCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When a {@code DeleteCommand} accepts a visitor, it calls + * its {@link CommandVisitor#visitDeleteCommand visitDeleteCommand} method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitDeleteCommand(getObject()); + } +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditCommand.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditCommand.java new file mode 100644 index 00000000000..1171b186290 --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Ported from Apache + * Commons Collections + */ +public abstract class EditCommand { + + /** Object on which the command should be applied. */ + private final T object; + + /** + * Simple constructor. Creates a new instance of EditCommand + * + * @param object reference to the object associated with this command, this + * refers to an element of one of the sequences being compared + */ + protected EditCommand(final T object) { + this.object = object; + } + + /** + * Accept a visitor. + *

+ * This method is invoked for each command belonging to + * an {@link EditScript EditScript}, in order to implement the visitor design pattern + * + * @param visitor the visitor to be accepted + */ + public abstract void accept(CommandVisitor visitor); + + /** + * Returns the object associated with this command. + * + * @return the object on which the command is applied + */ + protected T getObject() { + return object; + } + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditScript.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditScript.java new file mode 100644 index 00000000000..7d91b316d4a --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/EditScript.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import java.util.ArrayList; +import java.util.List; + +/** + * Ported from Apache + * Commons Collections + */ +public class EditScript { + + /** Container for the commands. */ + private final List> commands; + + /** Length of the longest common subsequence. */ + private int lcsLength; + + /** Number of modifications. */ + private int modifications; + + /** + * Simple constructor. Creates a new empty script. + */ + public EditScript() { + commands = new ArrayList<>(); + lcsLength = 0; + modifications = 0; + } + + /** + * Add a delete command to the script. + * + * @param command command to add + */ + public void append(final DeleteCommand command) { + commands.add(command); + ++modifications; + } + + /** + * Add an insert command to the script. + * + * @param command command to add + */ + public void append(final InsertCommand command) { + commands.add(command); + ++modifications; + } + + /** + * Add a keep command to the script. + * + * @param command command to add + */ + public void append(final KeepCommand command) { + commands.add(command); + ++lcsLength; + } + + /** + * Gets the length of the Longest Common Subsequence (LCS). The length of the + * longest common subsequence is the number of {@link KeepCommand keep + * commands} in the script. + * + * @return length of the Longest Common Subsequence + */ + public int getLCSLength() { + return lcsLength; + } + + /** + * Gets the number of effective modifications. The number of effective + * modification is the number of {@link DeleteCommand delete} and + * {@link InsertCommand insert} commands in the script. + * + * @return number of effective modifications + */ + public int getModifications() { + return modifications; + } + + /** + * Visit the script. The script implements the visitor design + * pattern, this method is the entry point to which the user supplies its + * own visitor, the script will be responsible to drive it through the + * commands in order and call the appropriate method as each command is + * encountered. + * + * @param visitor the visitor that will visit all commands in turn + */ + public void visit(final CommandVisitor visitor) { + for (final EditCommand command : commands) { + command.accept(visitor); + } + } + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/InsertCommand.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/InsertCommand.java new file mode 100644 index 00000000000..098fd50a4da --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/InsertCommand.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Ported from Apache + * Commons Collections + */ +public class InsertCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of InsertCommand + * + * @param object the object of the second sequence that should be inserted + */ + public InsertCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When an {@code InsertCommand} accepts a visitor, + * it calls its {@link CommandVisitor#visitInsertCommand visitInsertCommand} + * method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitInsertCommand(getObject()); + } + +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/KeepCommand.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/KeepCommand.java new file mode 100644 index 00000000000..8c17571e904 --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/KeepCommand.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Ported from Apache + * Commons Collections + */ +public class KeepCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of KeepCommand + * + * @param object the object belonging to both sequences (the object is a + * reference to the instance in the first sequence which is known + * to be equal to an instance in the second sequence) + */ + public KeepCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When a {@code KeepCommand} accepts a visitor, it + * calls its {@link CommandVisitor#visitKeepCommand visitKeepCommand} method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitKeepCommand(getObject()); + } +} diff --git a/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/SequencesComparator.java b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/SequencesComparator.java new file mode 100644 index 00000000000..6c462a84e71 --- /dev/null +++ b/zjsonpatch/src/main/java/org/apache/commons/collections4/sequence/SequencesComparator.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import org.apache.commons.collections4.DefaultEquator; +import org.apache.commons.collections4.Equator; + +import java.util.List; + +/** + * Ported from Apache + * Commons Collections + */ +public class SequencesComparator { + + /** + * This class is a simple placeholder to hold the end part of a path + * under construction in a {@link SequencesComparator SequencesComparator}. + */ + private static final class Snake { + + /** Start index. */ + private final int start; + + /** End index. */ + private final int end; + + /** Diagonal number. */ + private final int diag; + + /** + * Simple constructor. Creates a new instance of Snake with specified indices. + * + * @param start start index of the snake + * @param end end index of the snake + * @param diag diagonal number + */ + Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + + /** + * Gets the diagonal number of the snake. + * + * @return diagonal number of the snake + */ + public int getDiag() { + return diag; + } + + /** + * Gets the end index of the snake. + * + * @return end index of the snake + */ + public int getEnd() { + return end; + } + + /** + * Gets the start index of the snake. + * + * @return start index of the snake + */ + public int getStart() { + return start; + } + } + + /** First sequence. */ + private final List sequence1; + + /** Second sequence. */ + private final List sequence2; + + /** The equator used for testing object equality. */ + private final Equator equator; + /** Temporary variables. */ + private final int[] vDown; + + private final int[] vUp; + + /** + * Simple constructor. + *

+ * Creates a new instance of SequencesComparator using a {@link DefaultEquator}. + *

+ * It is guaranteed that the comparisons will always be done as + * {@code o1.equals(o2)} where {@code o1} belongs to the first + * sequence and {@code o2} belongs to the second sequence. This can be + * important if subclassing is used for some elements in the first sequence + * and the {@code equals} method is specialized. + * + * @param sequence1 first sequence to be compared + * @param sequence2 second sequence to be compared + */ + public SequencesComparator(final List sequence1, final List sequence2) { + this(sequence1, sequence2, DefaultEquator.defaultEquator()); + } + + /** + * Simple constructor. + *

+ * Creates a new instance of SequencesComparator with a custom {@link Equator}. + *

+ * It is guaranteed that the comparisons will always be done as + * {@code Equator.equate(o1, o2)} where {@code o1} belongs to the first + * sequence and {@code o2} belongs to the second sequence. + * + * @param sequence1 first sequence to be compared + * @param sequence2 second sequence to be compared + * @param equator the equator to use for testing object equality + */ + public SequencesComparator(final List sequence1, final List sequence2, final Equator equator) { + this.sequence1 = sequence1; + this.sequence2 = sequence2; + this.equator = equator; + + final int size = sequence1.size() + sequence2.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + } + + /** + * Build an edit script. + * + * @param start1 the start of the first sequence to be compared + * @param end1 the end of the first sequence to be compared + * @param start2 the start of the second sequence to be compared + * @param end2 the end of the second sequence to be compared + * @param script the edited script + */ + private void buildScript(final int start1, final int end1, final int start2, final int end2, + final EditScript script) { + + final Snake middle = getMiddleSnake(start1, end1, start2, end2); + + if (middle == null + || middle.getStart() == end1 && middle.getDiag() == end1 - end2 + || middle.getEnd() == start1 && middle.getDiag() == start1 - start2) { + + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equator.equate(sequence1.get(i), sequence2.get(j))) { + script.append(new KeepCommand<>(sequence1.get(i))); + ++i; + ++j; + } else if (end1 - start1 > end2 - start2) { + script.append(new DeleteCommand<>(sequence1.get(i))); + ++i; + } else { + script.append(new InsertCommand<>(sequence2.get(j))); + ++j; + } + } + + } else { + + buildScript(start1, middle.getStart(), + start2, middle.getStart() - middle.getDiag(), + script); + for (int i = middle.getStart(); i < middle.getEnd(); ++i) { + script.append(new KeepCommand<>(sequence1.get(i))); + } + buildScript(middle.getEnd(), end1, + middle.getEnd() - middle.getDiag(), end2, + script); + } + } + + /** + * Build a snake. + * + * @param start the value of the start of the snake + * @param diag the value of the diagonal of the snake + * @param end1 the value of the end of the first sequence to be compared + * @param end2 the value of the end of the second sequence to be compared + * @return the snake built + */ + private Snake buildSnake(final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 + && end < end1 + && equator.equate(sequence1.get(end), sequence2.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + /** + * Gets the middle snake corresponding to two subsequences of the + * main sequences. + *

+ * The snake is found using the MYERS Algorithm (this algorithm has + * also been implemented in the GNU diff program). This algorithm is + * explained in Eugene Myers article: + * + * An O(ND) Difference Algorithm and Its Variations. + * + * @param start1 the start of the first sequence to be compared + * @param end1 the end of the first sequence to be compared + * @param start2 the start of the second sequence to be compared + * @param end2 the end of the second sequence to be compared + * @return the middle snake + */ + private Snake getMiddleSnake(final int start1, final int end1, final int start2, final int end2) { + // Myers Algorithm + // Initialisations + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + vDown[1 + offset] = start1; + vUp[1 + offset] = end1 + 1; + + for (int d = 0; d <= offset; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && vDown[i - 1] < vDown[i + 1]) { + vDown[i] = vDown[i + 1]; + } else { + vDown[i] = vDown[i - 1] + 1; + } + + int x = vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equator.equate(sequence1.get(x), sequence2.get(y))) { + vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d && vUp[i - delta] <= vDown[i]) { // NOPMD + return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2); + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d || k != delta + d && vUp[i + 1] <= vUp[i - 1]) { + vUp[i] = vUp[i + 1] - 1; + } else { + vUp[i] = vUp[i - 1]; + } + + int x = vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && equator.equate(sequence1.get(x), sequence2.get(y))) { + vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d && vUp[i] <= vDown[i + delta]) { // NOPMD + return buildSnake(vUp[i], k + start1 - start2, end1, end2); + } + } + } + + // this should not happen + throw new IllegalStateException("Internal Error"); + } + + /** + * Gets the {@link EditScript} object. + *

+ * It is guaranteed that the objects embedded in the {@link InsertCommand + * insert commands} come from the second sequence and that the objects + * embedded in either the {@link DeleteCommand delete commands} or + * {@link KeepCommand keep commands} come from the first sequence. This can + * be important if subclassing is used for some elements in the first + * sequence and the {@code equals} method is specialized. + * + * @return the edit script resulting from the comparison of the two + * sequences + */ + public EditScript getScript() { + final EditScript script = new EditScript<>(); + buildScript(0, sequence1.size(), 0, sequence2.size(), script); + return script; + } +} diff --git a/zjsonpatch/src/test/java/io/fabric8/zjsonpatch/JsonDiffTest.java b/zjsonpatch/src/test/java/io/fabric8/zjsonpatch/JsonDiffTest.java new file mode 100644 index 00000000000..8adc5e0737d --- /dev/null +++ b/zjsonpatch/src/test/java/io/fabric8/zjsonpatch/JsonDiffTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.zjsonpatch; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.flipkart.zjsonpatch.JsonDiff; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class JsonDiffTest { + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("asJson should use ported ListUtils.longestCommonSubsequence in absence of Apache Commons Collections") + void asJson_inAbsenceOfCommonsCollectionsDependency_shouldNotThrowError() { + // Given + Map m1 = Collections.singletonMap("key1", "value1"); + Map m2 = new HashMap<>(); + m2.put("key1", "value1"); + m2.put("key2", "value2"); + ArrayNode node1 = objectMapper.createArrayNode(); + node1.add(objectMapper.convertValue(m1, JsonNode.class)); + ArrayNode node2 = objectMapper.createArrayNode(); + node2.add(objectMapper.convertValue(m2, JsonNode.class)); + + // When + JsonNode jsonNode = JsonDiff.asJson(node1, node2); + + // Then + assertThat(jsonNode) + .satisfies(j -> assertThat(j.isArray()).isTrue()) + .satisfies(n -> assertThat(n.get(0).get("op").asText()).isEqualTo("add")) + .satisfies(n -> assertThat(n.get(0).get("path").asText()).isEqualTo("/0/key2")) + .satisfies(n -> assertThat(n.get(0).get("value").asText()).isEqualTo("value2")) + .isNotNull(); + } +} diff --git a/zjsonpatch/src/test/java/org/apache/commons/collections4/ListUtilsTest.java b/zjsonpatch/src/test/java/org/apache/commons/collections4/ListUtilsTest.java new file mode 100644 index 00000000000..c770b584d93 --- /dev/null +++ b/zjsonpatch/src/test/java/org/apache/commons/collections4/ListUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatNullPointerException; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class ListUtilsTest { + @Test + void longestCommonSubsequence() { + assertThatNullPointerException() + .isThrownBy(() -> ListUtils.longestCommonSubsequence((List) null, null)) + .withMessageContaining("listA"); + assertThatNullPointerException() + .isThrownBy(() -> ListUtils.longestCommonSubsequence(Collections.singletonList('A'), null)) + .withMessageContaining("listB"); + assertThatNullPointerException() + .isThrownBy(() -> ListUtils.longestCommonSubsequence(null, Collections.singletonList('A'))) + .withMessageContaining("listA"); + + assertThat(ListUtils.longestCommonSubsequence(Collections.emptyList(), Collections.emptyList())).isEmpty(); + + final List list1 = Arrays.asList('B', 'A', 'N', 'A', 'N', 'A'); + final List list2 = Arrays.asList('A', 'N', 'A', 'N', 'A', 'S'); + assertThat(ListUtils.longestCommonSubsequence(list1, list2)) + .containsExactly('A', 'N', 'A', 'N', 'A'); + + final List list3 = Arrays.asList('A', 'T', 'A', 'N', 'A'); + assertThat(ListUtils.longestCommonSubsequence(list1, list3)) + .containsExactly('A', 'A', 'N', 'A'); + + assertThat(ListUtils.longestCommonSubsequence(list1, Arrays.asList('Z', 'O', 'R', 'R', 'O'))).isEmpty(); + } +}