com.github.fge.jsonpatch.diff.LCS.java Source code

Java tutorial

Introduction

Here is the source code for com.github.fge.jsonpatch.diff.LCS.java

Source

/*
 * Copyright (c) 2013, Randy Watler <watler@wispertel.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Lesser GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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.base.Equivalence;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import java.util.List;

/**
 * Longest common subsequence algorithm implementation
 *
 * <p>This is an adaptation of the code found at <a
 * href="http://rosettacode.org/wiki/Longest_common_subsequence#Dynamic_Programming_2">Rosetta
 * Code</a> for {@link ArrayNode} instances.</p>
 *
 * <p>For instance, given these two arrays:</p>
 *
 * <ul>
 *     <li>{@code [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]},</li>
 *     <li>{@code [ 1, 2, 10, 11, 5, 12, 8, 9 ]}</li>
 * </ul>
 *
 * <p>this code will return {@code [ 1, 2, 5, 8, 9 ]}.</p>
 */
final class LCS {
    private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();

    private LCS() {
    }

    /**
     * Get the longest common subsequence of elements of two array nodes
     *
     * <p>This is an implementation of the classic 'diff' algorithm often used
     * to compare text files line by line.</p>
     *
     * @param first first array node to compare
     * @param second second array node to compare
     */
    static List<JsonNode> 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<JsonNode> l1 = Lists.newArrayList(first);
        List<JsonNode> l2 = Lists.newArrayList(second);

        final List<JsonNode> ret = head(l1, l2);
        final int headSize = ret.size();

        l1 = l1.subList(headSize, l1.size());
        l2 = l2.subList(headSize, l2.size());

        final List<JsonNode> 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;
    }

    /**
     * Compute longest common subsequence out of two lists
     *
     * <p>When entering this function, both lists are trimmed from their
     * common leading and trailing nodes.</p>
     *
     * @param l1 the first list
     * @param l2 the second list
     * @return the longest common subsequence
     */
    private static List<JsonNode> doLCS(final List<JsonNode> l1, final List<JsonNode> l2) {
        final List<JsonNode> 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
     *
     * <p>Note that the arguments are NOT altered.</p>
     *
     * @param l1 first list
     * @param l2 second list
     * @return a list of common head elements
     */
    private static List<JsonNode> head(final List<JsonNode> l1, final List<JsonNode> l2) {
        final List<JsonNode> 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
     *
     * <p>Note that the arguments are NOT altered. Elements are returned in
     * their order of appearance.</p>
     *
     * @param l1 first list
     * @param l2 second list
     * @return a list of common tail elements
     */
    private static List<JsonNode> tail(final List<JsonNode> l1, final List<JsonNode> l2) {
        final List<JsonNode> l = head(Lists.reverse(l1), Lists.reverse(l2));
        return Lists.reverse(l);
    }
}