File: Diff.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (308 lines) | stat: -rw-r--r-- 11,163 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl;

import ca.odell.glazedlists.EventList;

import java.util.*;

/**
 * Implementation of Eugene W. Myer's paper, "An O(ND) Difference Algorithm and
 * Its Variations", the same algorithm found in GNU diff.
 *
 * <p>Note that this is a cleanroom implementation of this popular algorithm
 * that is particularly suited for the Java programmer. The variable names are
 * descriptive and the approach is more object-oriented than Myer's sample
 * algorithm.
 *
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 */
public final class Diff {

    /**
     * Convenience method for {@link #replaceAll(EventList,List,boolean,Comparator)
     * replaceAll()} that uses {@link Object#equals(Object)} to determine
     * equality.
     */
    public static <E> void replaceAll(EventList<E> target, List<E> source, boolean updates) {
        replaceAll(target, source, updates, (Comparator)GlazedListsImpl.equalsComparator());
    }

    /**
     * Replace the complete contents of the target {@link EventList} with the
     * complete contents of the source {@link EventList} while making as few
     * list changes as possible.
     *
     * @param comparator a {@link Comparator} to use to test only for equality.
     *      This comparator shall return 0 to signal that two elements are
     *      equal, and nonzero otherwise.
     * @param updates whether to fire update events for Objects that are equal
     *      in both {@link List}s.
     */
    public static <E> void replaceAll(EventList<E> target, List<E> source,
                                      boolean updates, Comparator<E> comparator) {
        DiffMatcher listDiffMatcher = new ListDiffMatcher<E>(target, source, comparator);
        List<Point> editScript = shortestEditScript(listDiffMatcher);
        
        // target is x axis. Changes in X mean advance target index
        // source is y axis. Changes to y mean advance source index
        int targetIndex = 0;
        int sourceIndex = 0;

        // walk through points, applying changes as they arrive
        Point previousPoint = null;
        for(Iterator<Point> i = editScript.iterator(); i.hasNext();) {
            Point currentPoint = i.next();
            
            // skip the first point
            if(previousPoint == null) {
                previousPoint = currentPoint;
                continue;
            }
            
            // figure out what the relationship in the values is
            int deltaX = currentPoint.getX() - previousPoint.getX();
            int deltaY = currentPoint.getY() - previousPoint.getY();
            
            // handle an update
            if(deltaX == deltaY) {
                if(updates) {
                    for(int u = 0; u < deltaX; u++) {
                        target.set(targetIndex + u, source.get(sourceIndex + u));
                    }
                }
                targetIndex += deltaX;
                sourceIndex += deltaY;

            // handle a remove
            } else if(deltaX == 1 && deltaY == 0) {
                target.remove(targetIndex);

            // handle an insert
            } else if(deltaX == 0 && deltaY == 1) {
                target.add(targetIndex, source.get(sourceIndex));
                sourceIndex++;
                targetIndex++;

            // should never be reached
            } else {
                throw new IllegalStateException();
            }
            
            // the next previous point is this current point
            previousPoint = currentPoint;
        }
    }


    /**
     * Calculate the length of the longest common subsequence for the specified
     * input.
     */
    private static List<Point> shortestEditScript(DiffMatcher input) {
        // calculate limits based on the size of the input matcher
        int N = input.getAlphaLength();
        int M = input.getBetaLength();
        Point maxPoint = new Point(N, M);
        int maxSteps = N + M;
        
        // use previous round furthest reaching D-path to determine the 
        // new furthest reaching (D+1)-path
        Map<Integer,Point> furthestReachingPoints = new HashMap<Integer,Point>();

        // walk through in stages, each stage adding one non-diagonal.
        // D == count of non-diagonals in current stage
        for(int D = 0; D <= maxSteps; D++) {

            // exploit diagonals in order to save storing both X and Y
            // diagonal k means every point on k, (k = x - y)
            for(int k = -D; k <= D; k += 2) {
                // the furthest reaching D-path on the left and right diagonals
                // either of these may be null. The terms 'below left' and 'above
                // right' refer to the diagonals that the points are on and may
                // not be representative of the point positions
                Point belowLeft = furthestReachingPoints.get(new Integer(k - 1));
                Point aboveRight = furthestReachingPoints.get(new Integer(k + 1));
                
                // the new furthest reaching point to create
                Point point;
                
                // first round: we have matched zero in word X
                if(furthestReachingPoints.isEmpty()) {
                    point = new Point(0, 0);

                // if this is the leftmost diagonal, or the left edge is
                // further than the right edge, our new X is that value and
                // our y is one greater (shift verically by one)
                } else if(k == -D || (k != D && belowLeft.getX() < aboveRight.getX())) {
                    point = aboveRight.createDeltaPoint(0, 1);

                // if the right edge is further than the left edge, use that
                // x and keep y the same (shift horizontally by one)
                } else {
                    point = belowLeft.createDeltaPoint(1, 0);
                }
                
                // match as much diagonal as possible from the previous endpoint
                while(point.isLessThan(maxPoint) && input.matchPair(point.getX(), point.getY())) {
                    point = point.incrementDiagonally();
                }

                // save this furthest reaching path
                furthestReachingPoints.put(new Integer(k), point);
                
                // if we're past the end, we have a solution!
                if(point.isEqualToOrGreaterThan(maxPoint)) {
                    return point.trail();
                }
            }
        }
        // no solution was found
        throw new IllegalStateException();
    }

    /**
     * Models an X and Y point in a path. The top-left corner of the axis is the point (0,
     * 0). This is the lowest point in both the x and y dimensions. Negative points are
     * not allowed.
     */
    private static class Point {
        private int x = 0;
        private int y = 0;
        private Point predecessor = null;

        /**
         * Create a new point with the specified coordinates and no predecessor.
         */
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        /**
         * Creates a new point from this point by shifting its values as specified. The
         * new point keeps a reference to its source in order to create a path later.
         */
        public Point createDeltaPoint(int deltaX, int deltaY) {
            Point result = new Point(x + deltaX, y + deltaY);
            result.predecessor = this;
            return result;
        }

        /**
         * Shifts <code>x</code> and <code>y</code> values down and to the
         * right by one.
         */
        public Point incrementDiagonally() {
            Point result = createDeltaPoint(1, 1);

            // shortcut to the predecessor (to save memory!)
            if(predecessor != null) {
                int deltaX = result.x - predecessor.x;
                int deltaY = result.y - predecessor.y;

                if(deltaX == deltaY) {
                    result.predecessor = this.predecessor;
                }
            }

            return result;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public boolean isLessThan(Point other) {
            return x < other.x && y < other.y;
        }

        public boolean isEqualToOrGreaterThan(Point other) {
            return x >= other.x && y >= other.y;
        }

        @Override
        public String toString() {
            return "(" + x + "," + y + ")";
        }

        /**
         * Get a trail from the original point to this point. This is a list of
         * all points created via a series of {@link #createDeltaPoint(int,int)}
         * calls.
         */
        public List<Point> trail() {
            List<Point> reverse = new ArrayList<Point>();
            Point current = this;
            while (current != null) {
                reverse.add(current);
                current = current.predecessor;
            }
            Collections.reverse(reverse);
            return reverse;
        }
    }

    /**
     * Determines if the values at the specified points match or not.
     *
     * <p>This class specifies that each element should specify a character value.
     * This is for testing and debugging only and it is safe for implementing
     * classes to throw {@link UnsupportedOperationException} for both the
     * {@link #alphaAt(int)} and {@link #betaAt(int)} methods.
     */
    interface DiffMatcher {
        public int getAlphaLength();

        public int getBetaLength();

        public boolean matchPair(int alphaIndex, int betaIndex);

        /**
         * Output a character representing the specified element, for
         * the convenience of testing.
         */
        public char alphaAt(int index);
        public char betaAt(int index);
    }

    /**
     * Matcher for Lists.
     */
    static class ListDiffMatcher<E> implements DiffMatcher {
        private List<E> alpha;
        private List<E> beta;
        private Comparator<E> comparator;

        public ListDiffMatcher(List<E> alpha, List<E> beta, Comparator<E> comparator) {
            this.alpha = alpha;
            this.beta = beta;
            this.comparator = comparator;
        }

        public int getAlphaLength() {
            return alpha.size();
        }

        public char alphaAt(int index) {
            return alpha.get(index).toString().charAt(0);
        }

        public char betaAt(int index) {
            return beta.get(index).toString().charAt(0);
        }

        public int getBetaLength() {
            return beta.size();
        }

        public boolean matchPair(int alphaIndex, int betaIndex) {
            return (comparator.compare(alpha.get(alphaIndex), beta.get(betaIndex)) == 0);
        }
    }
}