Passed
Pull Request — master (#11)
by Steve
02:11
created

RangeDifferencer::findRanges()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 21
nc 6
nop 2
dl 0
loc 34
ccs 22
cts 22
cp 1
crap 4
rs 9.584
c 0
b 0
f 0
1
<?php
2
/**
3
 * (c) Steve Nebes <[email protected]>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace SN\RangeDifferencer;
12
13
/**
14
 * A RangeDifferencer finds the differences between two or three RangeComparatorInterfaces.
15
 *
16
 * To use the differencer, clients provide an RangeComparatorInterface that breaks their input data into a sequence of
17
 * comparable entities. The differencer returns the differences among these sequences as an array of RangeDifference
18
 * objects (findDifferences methods). Every RangeDifference represents a single kind of difference and the
19
 * corresponding ranges of the underlying comparable entities in the left, right, and optionally ancestor sides.
20
 *
21
 * Alternatively, the findRanges methods not only return objects for the differing ranges but for non-differing ranges
22
 * too.
23
 *
24
 * The algorithm used is an objectified version of one described in: A File Comparison Program, by Webb Miller and
25
 * Eugene W. Myers, Software Practice and Experience, Vol. 15, Nov. 1985.
26
 */
27
final class RangeDifferencer
28
{
29
    /**
30
     * Prevent class instantiation.
31
     *
32
     * @codeCoverageIgnore
33
     */
34
    private function __construct()
35
    {
36
    }
37
38
    /**
39
     * Finds the differences between two RangeComparatorInterfaces. The differences are returned as an array of
40
     * RangeDifferences. If no differences are detected an empty array is returned.
41
     *
42
     * @param RangeComparatorInterface $left
43
     * @param RangeComparatorInterface $right
44
     * @return RangeDifference[]
45
     */
46 14
    public static function findDifferences(RangeComparatorInterface $left, RangeComparatorInterface $right): array
47
    {
48 14
        return RangeComparatorLCS::findDifferences($left, $right);
49
    }
50
51
    /**
52
     * Finds the differences among three RangeComparatorInterfaces. The differences are returned as a list of
53
     * RangeDifferences. If no differences are detected an empty list is returned. If the ancestor range comparator is
54
     * null, a two-way comparison is performed.
55
     *
56
     * @param RangeComparatorInterface $ancestor
57
     * @param RangeComparatorInterface $left
58
     * @param RangeComparatorInterface $right
59
     * @return RangeDifference[]
60
     */
61
//    public static function findDifferences3(
62
//        RangeComparatorInterface $ancestor,
63
//        RangeComparatorInterface $left,
64
//        RangeComparatorInterface $right
65
//    ): array {
66
//        $leftAncestorScript = [];
67
//        $rightAncestorScript = static::findDifferences($ancestor, $right);
68
//
69
//        if (!empty($rightAncestorScript)) {
70
//            $leftAncestorScript = static::findDifferences($ancestor, $left);
71
//        }
72
//
73
//        if (empty($rightAncestorScript) || empty($leftAncestorScript)) {
74
//            return [];
75
//        }
76
//
77
//        $myIter = new DifferencesIterator($rightAncestorScript);
78
//        $yourIter = new DifferencesIterator($leftAncestorScript);
79
//
80
//        // Add a sentinel.
81
//        $diff3 = [];
82
//        $diff3[] = new RangeDifference(RangeDifference::ERROR);
83
//
84
//        // Combine the two two-way edit scripts into one.
85
//        while (null !== $myIter->getDifference() || null !== $yourIter->getDifference()) {
86
//            $myIter->removeAll();
87
//            $yourIter->removeAll();
88
//
89
//            if (null === $myIter->getDifference()) {
90
//                $startThread = $yourIter;
91
//            } elseif (null === $yourIter->getDifference()) {
92
//                $startThread = $myIter;
93
//            } else {
94
//                // Not at end of both scripts take the lowest range.
95
//                if ($myIter->getDifference()->getLeftStart() < $yourIter->getDifference()->getLeftStart()) {
96
//                    // 2 -> common (Ancestor)
97
//                    $startThread = $myIter;
98
//                } elseif ($myIter->getDifference()->getLeftStart() > $yourIter->getDifference()->getLeftStart()) {
99
//                    $startThread = $yourIter;
100
//                } else {
101
//                    if ($myIter->getDifference()->getLeftLength() === 0 &&
102
//                        $yourIter->getDifference()->getLeftLength() === 0) {
103
//                        // Insertion into the same position is conflict.
104
//                        $changeRangeStart = $myIter->getDifference()->getLeftStart();
105
//                        $changeRangeEnd = $myIter->getDifference()->getLeftEnd();
106
//
107
//                        $myIter->next();
108
//                        $yourIter->next();
109
//
110
//                        $diff3[] = static::createRangeDifference3(
111
//                            $myIter, $yourIter, $diff3, $right, $left, $changeRangeStart, $changeRangeEnd);
112
//                        continue;
113
//                    } elseif (0 === $myIter->getDifference()->getLeftLength()) {
114
//                        // Insertion into a position, and modification to the next line, is not conflict.
115
//                        $startThread = $myIter;
116
//                    } elseif (0 === $yourIter->getDifference()->getLeftLength()) {
117
//                        $startThread = $yourIter;
118
//                    } else {
119
//                        // Modifications to overlapping lines is conflict.
120
//                        $startThread = $myIter;
121
//                    }
122
//                }
123
//            }
124
//
125
//            $changeRangeStart = $startThread->getDifference()->getLeftStart();
126
//            $changeRangeEnd = $startThread->getDifference()->getLeftEnd();
127
//            $startThread->next();
128
//
129
//            // Check for overlapping changes with other thread. Merge overlapping changes with this range.
130
//            $other = $startThread->other($myIter, $yourIter);
131
//
132
//            while (null !== $other->getDifference() && $other->getDifference()->getLeftStart() < $changeRangeEnd) {
133
//                $newMax = $other->getDifference()->getLeftEnd();
134
//                $other->next();
135
//
136
//                if ($newMax > $changeRangeEnd) {
137
//                    $changeRangeEnd = $newMax;
138
//                    $other = $other->other($myIter, $yourIter);
139
//                }
140
//            }
141
//
142
//            $diff3[] = static::createRangeDifference3(
143
//                $myIter, $yourIter, $diff3, $right, $left, $changeRangeStart, $changeRangeEnd);
144
//        }
145
//
146
//        // Remove sentinel.
147
//        \array_shift($diff3);
148
//
149
//        return $diff3;
150
//    }
151
152
    /**
153
     * Finds the differences among two RangeComparatorInterfaces. In contrast to findDifferences, the result contains
154
     * RangeDifference elements for non-differing ranges too.
155
     *
156
     * @param RangeComparatorInterface $left
157
     * @param RangeComparatorInterface $right
158
     * @return RangeDifference[]
159
     */
160
//    public static function findRanges(RangeComparatorInterface $left, RangeComparatorInterface $right): array
161
//    {
162
//        $in = static::findDifferences($left, $right);
163
//        $out = [];
164
//
165
//        $mStart = 0;
166
//        $yStart = 0;
167
//
168
//        for ($i = 0, $iMax = \count($in); $i < $iMax; $i++) {
169
//            $es = $in[$i];
170
//            $rd = new RangeDifference(
171
//                RangeDifference::NOCHANGE,
172
//                $mStart, $es->getRightStart() - $mStart,
173
//                $yStart, $es->getLeftStart() - $yStart);
174
//
175
//            if (0 !== $rd->getMaxLength()) {
176
//                $out[] = $rd;
177
//            }
178
//
179
//            $out[] = $es;
180
//
181
//            $mStart = $es->getRightEnd();
182
//            $yStart = $es->getLeftEnd();
183
//        }
184
//
185
//        $rd = new RangeDifference(RangeDifference::NOCHANGE,
186
//            $mStart, $right->getRangeCount() - $mStart,
187
//            $yStart, $left->getRangeCount() - $yStart);
188
//
189
//        if ($rd->getMaxLength() > 0) {
190
//            $out[] = $rd;
191
//        }
192
//
193
//        return $out;
194
//    }
195
196
    /**
197
     * Finds the differences among three RangeComparatorInterfaces. In contrast to findDifferences, the result contains
198
     * RangeDifference elements for non-differing ranges too. If the ancestor range comparator is null, a two-way
199
     * comparison is performed.
200
     *
201
     * @param RangeComparatorInterface $ancestor
202
     * @param RangeComparatorInterface $left
203
     * @param RangeComparatorInterface $right
204
     * @return RangeDifference[]
205
     */
206
//    public static function findRanges3(
207
//        RangeComparatorInterface $ancestor,
208
//        RangeComparatorInterface $left,
209
//        RangeComparatorInterface $right): array
210
//    {
211
//        $in = static::findDifferences3($ancestor, $left, $right);
212
//        $out = [];
213
//
214
//        $mStart = 0;
215
//        $yStart = 0;
216
//        $aStart = 0;
217
//
218
//        for ($i = 0, $iMax = \count($in); $i < $iMax; $i++) {
219
//            $es = $in[$i];
220
//            $rd = new RangeDifference(RangeDifference::NOCHANGE,
221
//                $mStart, $es->getRightStart() - $mStart,
222
//                $yStart, $es->getLeftStart() - $yStart,
223
//                $aStart, $es->getAncestorStart() - $aStart);
224
//
225
//            if ($rd->getMaxLength() > 0) {
226
//                $out[] = $rd;
227
//            }
228
//
229
//            $out[] = $es;
230
//
231
//            $mStart = $es->getRightEnd();
232
//            $yStart = $es->getLeftEnd();
233
//            $aStart = $es->getAncestorEnd();
234
//        }
235
//
236
//        $rd = new RangeDifference(RangeDifference::NOCHANGE,
237
//            $mStart, $right->getRangeCount() - $mStart,
238
//            $yStart, $left->getRangeCount() - $yStart,
239
//            $aStart, $ancestor->getRangeCount() - $aStart);
240
//
241
//        if ($rd->getMaxLength() > 0) {
242
//            $out[] = $rd;
243
//        }
244
//
245
//        return $out;
246
//    }
247
248
    /**
249
     * @param DifferencesIterator      $myIter
250
     * @param DifferencesIterator      $yourIter
251
     * @param array                    $diff3
252
     * @param RangeComparatorInterface $right
253
     * @param RangeComparatorInterface $left
254
     * @param int                      $changeRangeStart
255
     * @param int                      $changeRangeEnd
256
     * @return RangeDifference
257
     */
258
//    private static function createRangeDifference3(
259
//        DifferencesIterator $myIter,
260
//        DifferencesIterator $yourIter,
261
//        array &$diff3,
262
//        RangeComparatorInterface $right,
263
//        RangeComparatorInterface $left,
264
//        int $changeRangeStart,
265
//        int $changeRangeEnd
266
//    ): RangeDifference {
267
//        $kind = RangeDifference::ERROR;
268
//        /** @var RangeDifference $last */
269
//        $last = $diff3[\count($diff3) - 1];
270
//
271
//        // At least one range array must be non-empty.
272
//        assert(true === ($myIter->getCount() !== 0 || $yourIter->getCount() !== 0));
273
//
274
//        // Find corresponding lines to changeRangeStart/End in right and left.
275
//        if ($myIter->getCount() === 0) {
276
//            // Only left changed.
277
//            $rightStart = $changeRangeStart - $last->getAncestorEnd() + $last->getRightEnd();
278
//            $rightEnd = $changeRangeEnd - $last->getAncestorEnd() + $last->getRightEnd();
279
//            $kind = RangeDifference::LEFT;
280
//        } else {
281
//            $myRange = $myIter->getRange();
282
//            $f = $myRange[0];
283
//            $l = $myRange[\count($myRange) - 1];
284
//            $rightStart = $changeRangeStart - $f->getLeftStart() + $f->getRightStart();
285
//            $rightEnd = $changeRangeEnd - $l->getLeftEnd() + $l->getRightEnd();
286
//        }
287
//
288
//        if ($yourIter->getCount() === 0) {
289
//            // Only right changed.
290
//            $leftStart = $changeRangeStart - $last->getAncestorEnd() + $last->getLeftEnd();
291
//            $leftEnd = $changeRangeEnd - $last->getAncestorEnd() + $last->getLeftEnd();
292
//            $kind = RangeDifference::RIGHT;
293
//        } else {
294
//            $yourRange = $yourIter->getRange();
295
//            $f = $yourRange[0];
296
//            $l = $yourRange[\count($yourRange) - 1];
297
//            $leftStart = $changeRangeStart - $f->getLeftStart() + $f->getRightStart();
298
//            $leftEnd = $changeRangeEnd - $l->getLeftEnd() + $l->getRightEnd();
299
//        }
300
//
301
//        if ($kind === RangeDifference::ERROR) {
302
//            // Overlapping change (conflict) -> compare the changed ranges.
303
//            if (static::rangeSpansEqual(
304
//                $right, $rightStart, $rightEnd - $rightStart,
305
//                $left, $leftStart, $leftEnd - $leftStart)) {
306
//                $kind = RangeDifference::ANCESTOR;
307
//            } else {
308
//                $kind = RangeDifference::CONFLICT;
309
//            }
310
//        }
311
//
312
//        return new RangeDifference(
313
//            $kind,
314
//            $rightStart, $rightEnd - $rightStart,
315
//            $leftStart, $leftEnd - $leftStart,
316
//            $changeRangeStart, $changeRangeEnd - $changeRangeStart);
317
//    }
318
319
    /**
320
     * Tests whether right and left changed in the same way.
321
     *
322
     * @param RangeComparatorInterface $right
323
     * @param int                      $rightStart
324
     * @param int                      $rightLength
325
     * @param RangeComparatorInterface $left
326
     * @param int                      $leftStart
327
     * @param int                      $leftLength
328
     * @return bool
329
     */
330
//    private static function rangeSpansEqual(
331
//        RangeComparatorInterface $right,
332
//        int $rightStart,
333
//        int $rightLength,
334
//        RangeComparatorInterface $left,
335
//        int $leftStart,
336
//        int $leftLength
337
//    ): bool {
338
//        if ($rightLength === $leftLength) {
339
//            for ($i = 0; $i < $rightLength; $i++) {
340
//                if (!static::rangesEqual($right, $rightStart + $i, $left, $leftStart + $i)) {
341
//                    break;
342
//                }
343
//            }
344
//
345
//            if ($i === $rightLength) {
346
//                return true;
347
//            }
348
//        }
349
//
350
//        return false;
351
//    }
352
353
    /**
354
     * Tests if two ranges are equal.
355
     *
356
     * @param RangeComparatorInterface $a
357
     * @param int                      $ai
358
     * @param RangeComparatorInterface $b
359
     * @param int                      $bi
360
     * @return bool
361
     */
362
//    private static function rangesEqual(
363
//        RangeComparatorInterface $a,
364
//        int $ai,
365
//        RangeComparatorInterface $b,
366
//        int $bi
367
//    ): bool {
368
//        return $a->rangesEqual($ai, $b, $bi);
369
//    }
370
}
371