
com.github.fge.jsonpatch.diff.LCS Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of json-patch Show documentation
Show all versions of json-patch Show documentation
JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) implementation in Java
/*
* Copyright (c) 2014, Francis Galiegue ([email protected])
*
* This software is dual-licensed under:
*
* - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
* later version;
* - the Apache Software License (ASL) version 2.0.
*
* The text of this file and of both licenses is available at the root of this
* project or, if you have the jar distribution, in directory META-INF/, under
* the names LGPL-3.0.txt and ASL-2.0.txt respectively.
*
* Direct link to the sources:
*
* - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
* - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package com.github.fge.jsonpatch.diff;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.github.fge.jackson.JsonNumEquals;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Equivalence;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.util.List;
/**
* Longest common subsequence algorithm implementation
*
* This is an adaptation of the code found at Rosetta
* Code for {@link ArrayNode} instances.
*
* For instance, given these two arrays:
*
*
* - {@code [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]},
* - {@code [ 1, 2, 10, 11, 5, 12, 8, 9 ]}
*
*
* this code will return {@code [ 1, 2, 5, 8, 9 ]}.
*/
final class LCS
{
private static final Equivalence EQUIVALENCE
= JsonNumEquals.getInstance();
private LCS()
{
}
/**
* Get the longest common subsequence of elements of two array nodes
*
* This is an implementation of the classic 'diff' algorithm often used
* to compare text files line by line.
*
* @param first first array node to compare
* @param second second array node to compare
*/
@VisibleForTesting
static List getLCS(final JsonNode first, final JsonNode second)
{
Preconditions.checkArgument(first.isArray(),
"LCS can only work on JSON arrays");
Preconditions.checkArgument(second.isArray(),
"LCS can only work on JSON arrays");
final int minSize = Math.min(first.size(), second.size());
List l1 = Lists.newArrayList(first);
List l2 = Lists.newArrayList(second);
final List ret = head(l1, l2);
final int headSize = ret.size();
l1 = l1.subList(headSize, l1.size());
l2 = l2.subList(headSize, l2.size());
final List tail = tail(l1, l2);
final int trim = tail.size();
l1 = l1.subList(0, l1.size() - trim);
l2 = l2.subList(0, l2.size() - trim);
if (headSize < minSize)
ret.addAll(doLCS(l1, l2));
ret.addAll(tail);
return ret;
}
static IndexedJsonArray doLCS(final JsonNode first, final JsonNode second)
{
return new IndexedJsonArray(getLCS(first, second));
}
/**
* Compute longest common subsequence out of two lists
*
* When entering this function, both lists are trimmed from their
* common leading and trailing nodes.
*
* @param l1 the first list
* @param l2 the second list
* @return the longest common subsequence
*/
private static List doLCS(final List l1,
final List l2)
{
final List lcs = Lists.newArrayList();
// construct LCS lengths matrix
final int size1 = l1.size();
final int size2 = l2.size();
final int[][] lengths = new int[size1 + 1][size2 + 1];
JsonNode node1;
JsonNode node2;
int len;
for (int i = 0; i < size1; i++)
for (int j = 0; j < size2; j++) {
node1 = l1.get(i);
node2 = l2.get(j);
len = EQUIVALENCE.equivalent(node1, node2) ? lengths[i][j] + 1
: Math.max(lengths[i + 1][j], lengths[i][j + 1]);
lengths[i + 1][j + 1] = len;
}
// return result out of the LCS lengths matrix
int x = size1, y = size2;
while (x > 0 && y > 0) {
if (lengths[x][y] == lengths[x - 1][y])
x--;
else if (lengths[x][y] == lengths[x][y - 1])
y--;
else {
lcs.add(l1.get(x - 1));
x--;
y--;
}
}
return Lists.reverse(lcs);
}
/**
* Return a list with common head elements of two lists
*
* Note that the arguments are NOT altered.
*
* @param l1 first list
* @param l2 second list
* @return a list of common head elements
*/
private static List head(final List l1,
final List l2)
{
final List ret = Lists.newArrayList();
final int len = Math.min(l1.size(), l2.size());
JsonNode node;
for (int index = 0; index < len; index++) {
node = l1.get(index);
if (!EQUIVALENCE.equivalent(node, l2.get(index)))
break;
ret.add(node);
}
return ret;
}
/**
* Return the list of common tail elements of two lists
*
* Note that the arguments are NOT altered. Elements are returned in
* their order of appearance.
*
* @param l1 first list
* @param l2 second list
* @return a list of common tail elements
*/
private static List tail(final List l1,
final List l2)
{
final List l = head(Lists.reverse(l1), Lists.reverse(l2));
return Lists.reverse(l);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy