    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.text.diff;
    018
    019/**
    020 * <p>
    021 * It is guaranteed that the comparisons will always be done as
    022 * {@code o1.equals(o2)} where {@code o1} belongs to the first
    023 * sequence and {@code o2} belongs to the second sequence. This can
    024 * be important if subclassing is used for some elements in the first
    025 * sequence and the {@code equals} method is specialized.
    026 * </p>
    027 * <p>
    028 * Comparison can be seen from two points of view: either as giving the smallest
    029 * modification allowing to transform the first sequence into the second one, or
    030 * as giving the longest sequence which is a subsequence of both initial
    031 * sequences. The {@code equals} method is used to compare objects, so any
    032 * object can be put into sequences. Modifications include deleting, inserting
    033 * or keeping one object, starting from the beginning of the first sequence.
    034 * </p>
    035 * <p>
    036 * This class implements the comparison algorithm, which is the very efficient
    037 * algorithm from Eugene W. Myers
    038 * <a href="http://www.cis.upenn.edu/~bcpierce/courses/dd/papers/diff.ps">
    039 * An O(ND) Difference Algorithm and Its Variations</a>. This algorithm produces
    040 * the shortest possible {@link EditScript edit script} containing all the
    041 * {@link EditCommand commands} needed to transform the first sequence into
    042 * the second one.
    043 *
    044 * <p>
    045 * This code has been adapted from Apache Commons Collections 4.0.
    046 * </p>
    047 *
    048 * @see EditScript
    049 * @see EditCommand
    050 * @see CommandVisitor
    051 * @since 1.0
    052 */
    053public class StringsComparator {
    054
    055    /**
    056     * This class is a simple placeholder to hold the end part of a path
    057     * under construction in a {@link StringsComparator StringsComparator}.
    058     */
    059    private static final class Snake {
    060
    061        /** Start index. */
    062        private final int start;
    063
    064        /** End index. */
    065        private final int end;
    066
    067        /** Diagonal number. */
    068        private final int diag;
    069
    070        /**
    071         * Constructs a new instance of Snake with specified indices.
    072         *
    073         * @param start  start index of the snake
    074         * @param end  end index of the snake
    075         * @param diag  diagonal number
    076         */
    077        Snake(final int start, final int end, final int diag) {
    078            this.start = start;
    079            this.end   = end;
    080            this.diag  = diag;
    081        }
    082
    083        /**
    084         * Gets the diagonal number of the snake.
    085         *
    086         * @return diagonal number of the snake
    087         */
    088        public int getDiag() {
    089            return diag;
    090        }
    091
    092        /**
    093         * Gets the end index of the snake.
    094         *
    095         * @return end index of the snake
    096         */
    097        public int getEnd() {
    098            return end;
    099        }
    100
    101        /**
    102         * Gets the start index of the snake.
    103         *
    104         * @return start index of the snake
    105         */
    106        public int getStart() {
    107            return start;
    108        }
    109    }
    110    /**
    111     * First character sequence.
    112     */
    113    private final String left;
    114    /**
    115     * Second character sequence.
    116     */
    117    private final String right;
    118    /**
    119     * Temporary array.
    120     */
    121    private final int[] vDown;
    122
    123    /**
    124     * Temporary array.
    125     */
    126    private final int[] vUp;
    127
    128    /**
    129     * Constructs a new instance of StringsComparator.
    130     * <p>
    131     * It is <em>guaranteed</em> that the comparisons will always be done as
    132     * {@code o1.equals(o2)} where {@code o1} belongs to the first
    133     * sequence and {@code o2} belongs to the second sequence. This can be
    134     * important if subclassing is used for some elements in the first sequence
    135     * and the {@code equals} method is specialized.
    136     * </p>
    137     *
    138     * @param left first character sequence to be compared
    139     * @param right second character sequence to be compared
    140     */
    141    public StringsComparator(final String left, final String right) {
    142        this.left = left;
    143        this.right = right;
    144
    145        final int size = left.length() + right.length() + 2;
    146        vDown = new int[size];
    147        vUp   = new int[size];
    148    }
    149
    150    /**
    151     * Builds an edit script.
    152     *
    153     * @param start1  the begin of the first sequence to be compared
    154     * @param end1  the end of the first sequence to be compared
    155     * @param start2  the begin of the second sequence to be compared
    156     * @param end2  the end of the second sequence to be compared
    157     * @param script the edited script
    158     */
    159    private void buildScript(final int start1, final int end1, final int start2, final int end2,
    160            final EditScript<Character> script) {
    161        final Snake middle = getMiddleSnake(start1, end1, start2, end2);
    162
    163        if (middle == null
    164                || middle.getStart() == end1 && middle.getDiag() == end1 - end2
    165                || middle.getEnd() == start1 && middle.getDiag() == start1 - start2) {
    166
    167            int i = start1;
    168            int j = start2;
    169            while (i < end1 || j < end2) {
    170                if (i < end1 && j < end2 && left.charAt(i) == right.charAt(j)) {
    171                    script.append(new KeepCommand<>(left.charAt(i)));
    172                    ++i;
    173                    ++j;
    174                } else if (end1 - start1 > end2 - start2) {
    175                    script.append(new DeleteCommand<>(left.charAt(i)));
    176                    ++i;
    177                } else {
    178                    script.append(new InsertCommand<>(right.charAt(j)));
    179                    ++j;
    180                }
    181            }
    182
    183        } else {
    184
    185            buildScript(start1, middle.getStart(),
    186                        start2, middle.getStart() - middle.getDiag(),
    187                        script);
    188            for (int i = middle.getStart(); i < middle.getEnd(); ++i) {
    189                script.append(new KeepCommand<>(left.charAt(i)));
    190            }
    191            buildScript(middle.getEnd(), end1,
    192                        middle.getEnd() - middle.getDiag(), end2,
    193                        script);
    194        }
    195    }
    196
    197    /**
    198     * Builds a snake.
    199     *
    200     * @param start  the value of the start of the snake
    201     * @param diag  the value of the diagonal of the snake
    202     * @param end1  the value of the end of the first sequence to be compared
    203     * @param end2  the value of the end of the second sequence to be compared
    204     * @return The snake built
    205     */
    206    private Snake buildSnake(final int start, final int diag, final int end1, final int end2) {
    207        int end = start;
    208        while (end - diag < end2
    209                && end < end1
    210                && left.charAt(end) == right.charAt(end - diag)) {
    211            ++end;
    212        }
    213        return new Snake(start, end, diag);
    214    }
    215
    216    /**
    217     * Gets the middle snake corresponding to two subsequences of the
    218     * main sequences.
    219     * <p>
    220     * The snake is found using the MYERS Algorithm (this algorithms has
    221     * also been implemented in the GNU diff program). This algorithm is
    222     * explained in Eugene Myers article:
    223     * <a href="http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps">
    224     * An O(ND) Difference Algorithm and Its Variations</a>.
    225     * </p>
    226     *
    227     * @param start1  the begin of the first sequence to be compared
    228     * @param end1  the end of the first sequence to be compared
    229     * @param start2  the begin of the second sequence to be compared
    230     * @param end2  the end of the second sequence to be compared
    231     * @return The middle snake
    232     */
    233    private Snake getMiddleSnake(final int start1, final int end1, final int start2, final int end2) {
    234        // Myers Algorithm
    235        // Initializations
    236        final int m = end1 - start1;
    237        final int n = end2 - start2;
    238        if (m == 0 || n == 0) {
    239            return null;
    240        }
    241
    242        final int delta  = m - n;
    243        final int sum    = n + m;
    244        final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2;
    245        vDown[1 + offset] = start1;
    246        vUp[1 + offset]   = end1 + 1;
    247
    248        for (int d = 0; d <= offset; ++d) {
    249            // Down
    250            for (int k = -d; k <= d; k += 2) {
    251                // First step
    252
    253                final int i = k + offset;
    254                if (k == -d || k != d && vDown[i - 1] < vDown[i + 1]) {
    255                    vDown[i] = vDown[i + 1];
    256                } else {
    257                    vDown[i] = vDown[i - 1] + 1;
    258                }
    259
    260                int x = vDown[i];
    261                int y = x - start1 + start2 - k;
    262
    263                while (x < end1 && y < end2 && left.charAt(x) == right.charAt(y)) {
    264                    vDown[i] = ++x;
    265                    ++y;
    266                }
    267                // Second step
    268                if (delta % 2 != 0 && delta - d <= k && k <= delta + d && vUp[i - delta] <= vDown[i]) { // NOPMD
    269                    return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2);
    270                }
    271            }
    272
    273            // Up
    274            for (int k = delta - d; k <= delta + d; k += 2) {
    275                // First step
    276                final int i = k + offset - delta;
    277                if (k == delta - d
    278                        || k != delta + d && vUp[i + 1] <= vUp[i - 1]) {
    279                    vUp[i] = vUp[i + 1] - 1;
    280                } else {
    281                    vUp[i] = vUp[i - 1];
    282                }
    283
    284                int x = vUp[i] - 1;
    285                int y = x - start1 + start2 - k;
    286                while (x >= start1 && y >= start2
    287                        && left.charAt(x) == right.charAt(y)) {
    288                    vUp[i] = x--;
    289                    y--;
    290                }
    291                // Second step
    292                if (delta % 2 == 0 && -d <= k && k <= d && vUp[i] <= vDown[i + delta]) { // NOPMD
    293                    return buildSnake(vUp[i], k + start1 - start2, end1, end2);
    294                }
    295            }
    296        }
    297
    298        // this should not happen
    299        throw new IllegalStateException("Internal Error");
    300    }
    301
    302    /**
    303     * Gets the {@link EditScript} object.
    304     * <p>
    305     * It is guaranteed that the objects embedded in the {@link InsertCommand
    306     * insert commands} come from the second sequence and that the objects
    307     * embedded in either the {@link DeleteCommand delete commands} or
    308     * {@link KeepCommand keep commands} come from the first sequence. This can
    309     * be important if subclassing is used for some elements in the first
    310     * sequence and the {@code equals} method is specialized.
    311     * </p>
    312     *
    313     * @return The edit script resulting from the comparison of the two
    314     *         sequences
    315     */
    316    public EditScript<Character> getScript() {
    317        final EditScript<Character> script = new EditScript<>();
    318        buildScript(0, left.length(), 0, right.length(), script);
    319        return script;
    320    }
    321
    322}