View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.text.diff;
18  
19  /**
20   * <p>
21   * It is guaranteed that the comparisons will always be done as
22   * {@code o1.equals(o2)} where {@code o1} belongs to the first
23   * sequence and {@code o2} belongs to the second sequence. This can
24   * be important if subclassing is used for some elements in the first
25   * sequence and the {@code equals} method is specialized.
26   * </p>
27   * <p>
28   * Comparison can be seen from two points of view: either as giving the smallest
29   * modification allowing to transform the first sequence into the second one, or
30   * as giving the longest sequence which is a subsequence of both initial
31   * sequences. The {@code equals} method is used to compare objects, so any
32   * object can be put into sequences. Modifications include deleting, inserting
33   * or keeping one object, starting from the beginning of the first sequence.
34   * </p>
35   * <p>
36   * This class implements the comparison algorithm, which is the very efficient
37   * algorithm from Eugene W. Myers
38   * <a href="https://www.cis.upenn.edu/~bcpierce/courses/dd/papers/diff.ps">
39   * An O(ND) Difference Algorithm and Its Variations</a>. This algorithm produces
40   * the shortest possible {@link EditScript edit script} containing all the
41   * {@link EditCommand commands} needed to transform the first sequence into
42   * the second one.
43   *
44   * <p>
45   * This code has been adapted from Apache Commons Collections 4.0.
46   * </p>
47   *
48   * @see EditScript
49   * @see EditCommand
50   * @see CommandVisitor
51   * @since 1.0
52   */
53  public class StringsComparator {
54  
55      /**
56       * Holds the end part of a path under construction in a {@link StringsComparator StringsComparator}.
57       */
58      private static final class Snake {
59  
60          /** Start index. */
61          private final int start;
62  
63          /** End index. */
64          private final int end;
65  
66          /** Diagonal number. */
67          private final int diag;
68  
69          /**
70           * Constructs a new instance of Snake with specified indices.
71           *
72           * @param start  start index of the snake.
73           * @param end  end index of the snake.
74           * @param diag  diagonal number.
75           */
76          Snake(final int start, final int end, final int diag) {
77              this.start = start;
78              this.end   = end;
79              this.diag  = diag;
80          }
81  
82          /**
83           * Gets the diagonal number of the snake.
84           *
85           * @return diagonal number of the snake.
86           */
87          public int getDiag() {
88              return diag;
89          }
90  
91          /**
92           * Gets the end index of the snake.
93           *
94           * @return end index of the snake.
95           */
96          public int getEnd() {
97              return end;
98          }
99  
100         /**
101          * Gets the start index of the snake.
102          *
103          * @return start index of the snake.
104          */
105         public int getStart() {
106             return start;
107         }
108     }
109     /**
110      * First character sequence.
111      */
112     private final String left;
113     /**
114      * Second character sequence.
115      */
116     private final String right;
117     /**
118      * Temporary array.
119      */
120     private final int[] vDown;
121 
122     /**
123      * Temporary array.
124      */
125     private final int[] vUp;
126 
127     /**
128      * Constructs a new instance of StringsComparator.
129      * <p>
130      * It is <em>guaranteed</em> that the comparisons will always be done as {@code o1.equals(o2)} where {@code o1} belongs to the first sequence and {@code o2}
131      * 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
132      * specialized.
133      * </p>
134      *
135      * @param left  first character sequence to be compared.
136      * @param right second character sequence to be compared.
137      */
138     public StringsComparator(final String left, final String right) {
139         this.left = left;
140         this.right = right;
141 
142         final int size = left.length() + right.length() + 2;
143         vDown = new int[size];
144         vUp   = new int[size];
145     }
146 
147     /**
148      * Builds an edit script.
149      *
150      * @param start1 the begin of the first sequence to be compared.
151      * @param end1   the end of the first sequence to be compared.
152      * @param start2 the begin of the second sequence to be compared.
153      * @param end2   the end of the second sequence to be compared.
154      * @param script the edited script.
155      */
156     private void buildScript(final int start1, final int end1, final int start2, final int end2,
157             final EditScript<Character> script) {
158         final Snake middle = getMiddleSnake(start1, end1, start2, end2);
159 
160         if (middle == null
161                 || middle.getStart() == end1 && middle.getDiag() == end1 - end2
162                 || middle.getEnd() == start1 && middle.getDiag() == start1 - start2) {
163 
164             int i = start1;
165             int j = start2;
166             while (i < end1 || j < end2) {
167                 if (i < end1 && j < end2 && left.charAt(i) == right.charAt(j)) {
168                     script.append(new KeepCommand<>(left.charAt(i)));
169                     ++i;
170                     ++j;
171                 } else if (end1 - start1 > end2 - start2) {
172                     script.append(new DeleteCommand<>(left.charAt(i)));
173                     ++i;
174                 } else {
175                     script.append(new InsertCommand<>(right.charAt(j)));
176                     ++j;
177                 }
178             }
179 
180         } else {
181 
182             buildScript(start1, middle.getStart(),
183                         start2, middle.getStart() - middle.getDiag(),
184                         script);
185             for (int i = middle.getStart(); i < middle.getEnd(); ++i) {
186                 script.append(new KeepCommand<>(left.charAt(i)));
187             }
188             buildScript(middle.getEnd(), end1,
189                         middle.getEnd() - middle.getDiag(), end2,
190                         script);
191         }
192     }
193 
194     /**
195      * Builds a snake.
196      *
197      * @param start the value of the start of the snake.
198      * @param diag  the value of the diagonal of the snake.
199      * @param end1  the value of the end of the first sequence to be compared.
200      * @param end2  the value of the end of the second sequence to be compared.
201      * @return The snake built.
202      */
203     private Snake buildSnake(final int start, final int diag, final int end1, final int end2) {
204         int end = start;
205         while (end - diag < end2
206                 && end < end1
207                 && left.charAt(end) == right.charAt(end - diag)) {
208             ++end;
209         }
210         return new Snake(start, end, diag);
211     }
212 
213     /**
214      * Gets the middle snake corresponding to two subsequences of the main sequences.
215      * <p>
216      * The snake is found using the MYERS Algorithm (this algorithms has also been implemented in the GNU diff program). This algorithm is explained in Eugene
217      * Myers article: <a href="https://www.cs.arizona.edu/people/gene/PAPERS/diff.ps"> An O(ND) Difference Algorithm and Its Variations</a>.
218      * </p>
219      *
220      * @param start1 the begin of the first sequence to be compared.
221      * @param end1   the end of the first sequence to be compared.
222      * @param start2 the begin of the second sequence to be compared.
223      * @param end2   the end of the second sequence to be compared.
224      * @return The middle snake.
225      */
226     private Snake getMiddleSnake(final int start1, final int end1, final int start2, final int end2) {
227         // Myers Algorithm
228         // Initializations
229         final int m = end1 - start1;
230         final int n = end2 - start2;
231         if (m == 0 || n == 0) {
232             return null;
233         }
234 
235         final int delta  = m - n;
236         final int sum    = n + m;
237         final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2;
238         vDown[1 + offset] = start1;
239         vUp[1 + offset]   = end1 + 1;
240 
241         for (int d = 0; d <= offset; ++d) {
242             // Down
243             for (int k = -d; k <= d; k += 2) {
244                 // First step
245 
246                 final int i = k + offset;
247                 if (k == -d || k != d && vDown[i - 1] < vDown[i + 1]) {
248                     vDown[i] = vDown[i + 1];
249                 } else {
250                     vDown[i] = vDown[i - 1] + 1;
251                 }
252 
253                 int x = vDown[i];
254                 int y = x - start1 + start2 - k;
255 
256                 while (x < end1 && y < end2 && left.charAt(x) == right.charAt(y)) {
257                     vDown[i] = ++x;
258                     ++y;
259                 }
260                 // Second step
261                 if (delta % 2 != 0 && delta - d <= k && k <= delta + d && vUp[i - delta] <= vDown[i]) { // NOPMD
262                     return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2);
263                 }
264             }
265 
266             // Up
267             for (int k = delta - d; k <= delta + d; k += 2) {
268                 // First step
269                 final int i = k + offset - delta;
270                 if (k == delta - d
271                         || k != delta + d && vUp[i + 1] <= vUp[i - 1]) {
272                     vUp[i] = vUp[i + 1] - 1;
273                 } else {
274                     vUp[i] = vUp[i - 1];
275                 }
276 
277                 int x = vUp[i] - 1;
278                 int y = x - start1 + start2 - k;
279                 while (x >= start1 && y >= start2
280                         && left.charAt(x) == right.charAt(y)) {
281                     vUp[i] = x--;
282                     y--;
283                 }
284                 // Second step
285                 if (delta % 2 == 0 && -d <= k && k <= d && vUp[i] <= vDown[i + delta]) { // NOPMD
286                     return buildSnake(vUp[i], k + start1 - start2, end1, end2);
287                 }
288             }
289         }
290 
291         // this should not happen
292         throw new IllegalStateException("Internal Error");
293     }
294 
295     /**
296      * Gets the {@link EditScript} object.
297      * <p>
298      * It is guaranteed that the objects embedded in the {@link InsertCommand insert commands} come from the second sequence and that the objects embedded in
299      * either the {@link DeleteCommand delete commands} or {@link KeepCommand keep commands} come from the first sequence. This can be important if subclassing
300      * is used for some elements in the first sequence and the {@code equals} method is specialized.
301      * </p>
302      *
303      * @return The edit script resulting from the comparison of the two sequences.
304      */
305     public EditScript<Character> getScript() {
306         final EditScript<Character> script = new EditScript<>();
307         buildScript(0, left.length(), 0, right.length(), script);
308         return script;
309     }
310 
311 }