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

RangeDifferencer   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 161
ccs 27
cts 27
cp 1
rs 10
c 0
b 0
f 0
wmc 8

3 Methods

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