Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.09% covered (warning)
89.09%
98 / 110
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
LexemeDiffer
89.09% covered (warning)
89.09%
98 / 110
80.00% covered (warning)
80.00%
12 / 15
30.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 canDiffEntityType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 diffEntities
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 diffLexemes
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
1
 toDiffArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getConstructionDiff
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDestructionDiff
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFormsDiff
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
5
 toFormsDiffArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getNextFormIdCounterDiff
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getSensesDiff
56.52% covered (warning)
56.52%
13 / 23
0.00% covered (danger)
0.00%
0 / 1
7.05
 toSensesDiffArray
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getNextSenseIdCounterDiff
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getLexicalCategoryAsArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getLanguageAsArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Wikibase\Lexeme\Domain\Diff;
4
5use Diff\Comparer\CallbackComparer;
6use Diff\Comparer\ComparableComparer;
7use Diff\Differ\MapDiffer;
8use Diff\DiffOp\Diff\Diff;
9use Diff\DiffOp\DiffOpAdd;
10use Diff\DiffOp\DiffOpChange;
11use Diff\DiffOp\DiffOpRemove;
12use InvalidArgumentException;
13use UnexpectedValueException;
14use Wikibase\DataModel\Entity\EntityDocument;
15use Wikibase\DataModel\Services\Diff\EntityDiff;
16use Wikibase\DataModel\Services\Diff\EntityDifferStrategy;
17use Wikibase\DataModel\Services\Diff\StatementListDiffer;
18use Wikibase\Lexeme\Domain\Model\Form;
19use Wikibase\Lexeme\Domain\Model\FormSet;
20use Wikibase\Lexeme\Domain\Model\Lexeme;
21use Wikibase\Lexeme\Domain\Model\Sense;
22use Wikibase\Lexeme\Domain\Model\SenseSet;
23use Wikimedia\Assert\Assert;
24
25/**
26 * @license GPL-2.0-or-later
27 */
28class LexemeDiffer implements EntityDifferStrategy {
29
30    /**
31     * @var StatementListDiffer
32     */
33    private $statementListDiffer;
34
35    /**
36     * @var MapDiffer
37     */
38    private $recursiveMapDiffer;
39
40    /**
41     * @var FormDiffer
42     */
43    private $formDiffer;
44
45    /**
46     * @var SenseDiffer
47     */
48    private $senseDiffer;
49
50    /**
51     * @var MapDiffer
52     */
53    private $itemIdDiffer;
54
55    public function __construct() {
56        $this->recursiveMapDiffer = new MapDiffer( true );
57        $this->itemIdDiffer = new MapDiffer( false, null, new ComparableComparer() );
58        $this->statementListDiffer = new StatementListDiffer();
59        $this->formDiffer = new FormDiffer();
60        $this->senseDiffer = new SenseDiffer();
61    }
62
63    /**
64     * @param string $entityType
65     *
66     * @return bool
67     */
68    public function canDiffEntityType( $entityType ) {
69        return $entityType === Lexeme::ENTITY_TYPE;
70    }
71
72    /**
73     * @param Lexeme $from
74     * @param Lexeme $to
75     *
76     * @return EntityDiff
77     *
78     * @throws InvalidArgumentException
79     */
80    public function diffEntities( EntityDocument $from, EntityDocument $to ) {
81        Assert::parameterType( Lexeme::class, $from, '$from' );
82        Assert::parameterType( Lexeme::class, $to, '$to' );
83
84        return $this->diffLexemes( $from, $to );
85    }
86
87    /**
88     * @param Lexeme $from
89     * @param Lexeme $to
90     *
91     * @return EntityDiff
92     */
93    public function diffLexemes( Lexeme $from, Lexeme $to ) {
94        $diffOps = $this->recursiveMapDiffer->doDiff(
95            $this->toDiffArray( $from ),
96            $this->toDiffArray( $to )
97        );
98
99        $diffOps['lexicalCategory'] = new Diff( $this->itemIdDiffer->doDiff(
100            $this->getLexicalCategoryAsArray( $from ),
101            $this->getLexicalCategoryAsArray( $to )
102        ) );
103        $diffOps['language'] = new Diff( $this->itemIdDiffer->doDiff(
104            $this->getLanguageAsArray( $from ),
105            $this->getLanguageAsArray( $to )
106        ) );
107
108        $diffOps['claim'] = $this->statementListDiffer->getDiff(
109            $from->getStatements(),
110            $to->getStatements()
111        );
112
113        $diffOps['nextFormId'] = $this->getNextFormIdCounterDiff( $from, $to );
114
115        $diffOps['forms'] = $this->getFormsDiff(
116            $from->getForms(),
117            $to->getForms()
118        );
119
120        $diffOps['senses'] = $this->getSensesDiff(
121            $from->getSenses(),
122            $to->getSenses()
123        );
124
125        $diffOps['nextSenseId'] = $this->getNextSenseIdCounterDiff( $from, $to );
126
127        return new LexemeDiff( $diffOps );
128    }
129
130    /**
131     * @param Lexeme $lexeme
132     *
133     * @return array[]
134     */
135    private function toDiffArray( Lexeme $lexeme ) {
136        $array = [];
137        $lemmas = $lexeme->getLemmas();
138
139        $array['lemmas'] = $lemmas->toTextArray();
140
141        return $array;
142    }
143
144    /**
145     * @param EntityDocument $entity
146     *
147     * @return EntityDiff
148     *
149     * @throws InvalidArgumentException
150     */
151    public function getConstructionDiff( EntityDocument $entity ) {
152        Assert::parameterType( Lexeme::class, $entity, '$entity' );
153        '@phan-var Lexeme $entity';
154
155        return $this->diffEntities( new Lexeme(), $entity );
156    }
157
158    /**
159     * @param EntityDocument $entity
160     *
161     * @return EntityDiff
162     *
163     * @throws InvalidArgumentException
164     */
165    public function getDestructionDiff( EntityDocument $entity ) {
166        Assert::parameterType( Lexeme::class, $entity, '$entity' );
167        '@phan-var Lexeme $entity';
168
169        return $this->diffEntities( $entity, new Lexeme() );
170    }
171
172    /**
173     * @param FormSet $from
174     * @param FormSet $to
175     *
176     * @return Diff
177     */
178    private function getFormsDiff( FormSet $from, FormSet $to ) {
179        $differ = new MapDiffer(
180            false,
181            null,
182            new CallbackComparer(
183                static function ( Form $from, Form $to ) {
184                    return $from == $to;
185                }
186            )
187        );
188
189        $from = $this->toFormsDiffArray( $from );
190        $to = $this->toFormsDiffArray( $to );
191        $formDiffOps = $differ->doDiff( $from, $to );
192
193        foreach ( $formDiffOps as $index => $formDiffOp ) {
194            if ( $formDiffOp instanceof DiffOpChange ) {
195                /** @var DiffOpChange $formDiffOp */
196                $formDiffOps[$index] = $this->formDiffer->diff(
197                    $formDiffOp->getOldValue(),
198                    $formDiffOp->getNewValue()
199                );
200            }
201            if ( $formDiffOp instanceof DiffOpAdd ) {
202                $formDiffOps[$index] = $this->formDiffer->getAddFormDiff( $formDiffOp->getNewValue() );
203            }
204            if ( $formDiffOp instanceof DiffOpRemove ) {
205                $formDiffOps[$index] = $this->formDiffer->getRemoveFormDiff( $formDiffOp->getOldValue() );
206            }
207        }
208
209        return new Diff( $formDiffOps, true );
210    }
211
212    /**
213     * @param FormSet $forms
214     *
215     * @return Form[]
216     */
217    private function toFormsDiffArray( FormSet $forms ) {
218        $result = [];
219
220        foreach ( $forms->toArray() as $form ) {
221            $result[$form->getId()->getSerialization()] = $form;
222        }
223
224        return $result;
225    }
226
227    private function getNextFormIdCounterDiff( Lexeme $from, Lexeme $to ) {
228        if ( $to->getNextFormId() <= $from->getNextFormId() ) {
229            return new Diff( [] );
230        }
231
232        return new Diff( [ new DiffOpChange( $from->getNextFormId(), $to->getNextFormId() ) ] );
233    }
234
235    /**
236     * @param SenseSet $from
237     * @param SenseSet $to
238     *
239     * @return Diff
240     */
241    private function getSensesDiff( SenseSet $from, SenseSet $to ) {
242        $differ = new MapDiffer(
243            false,
244            null,
245            new CallbackComparer(
246                static function ( Sense $from, Sense $to ) {
247                    return $from == $to;
248                }
249            )
250        );
251
252        $from = $this->toSensesDiffArray( $from );
253        $to = $this->toSensesDiffArray( $to );
254        $senseDiffOps = $differ->doDiff( $from, $to );
255
256        foreach ( $senseDiffOps as $index => $senseDiffOp ) {
257            if ( $senseDiffOp instanceof DiffOpChange ) {
258                /** @var DiffOpChange $senseDiffOp */
259                $senseDiffOps[$index] = $this->senseDiffer->diffEntities(
260                    $senseDiffOp->getOldValue(),
261                    $senseDiffOp->getNewValue()
262                );
263            }
264            if ( $senseDiffOp instanceof DiffOpAdd ) {
265                $senseDiffOps[$index] = $this->senseDiffer->getAddSenseDiff( $senseDiffOp->getNewValue() );
266            }
267            if ( $senseDiffOp instanceof DiffOpRemove ) {
268                $senseDiffOps[$index] = $this->senseDiffer->getRemoveSenseDiff( $senseDiffOp->getOldValue() );
269            }
270        }
271
272        return new Diff( $senseDiffOps, true );
273    }
274
275    /**
276     * @param SenseSet $senses
277     *
278     * @return Sense[]
279     */
280    private function toSensesDiffArray( SenseSet $senses ) {
281        $result = [];
282
283        foreach ( $senses->toArray() as $sense ) {
284            $result[$sense->getId()->getSerialization()] = $sense;
285        }
286
287        return $result;
288    }
289
290    private function getNextSenseIdCounterDiff( Lexeme $from, Lexeme $to ) {
291        if ( $to->getNextSenseId() <= $from->getNextSenseId() ) {
292            return new Diff( [] );
293        }
294
295        return new Diff( [ new DiffOpChange( $from->getNextSenseId(), $to->getNextSenseId() ) ] );
296    }
297
298    private function getLexicalCategoryAsArray( Lexeme $lexeme ) {
299        try {
300            return [ 'id' => $lexeme->getLexicalCategory() ];
301        } catch ( UnexpectedValueException $ex ) {
302            return []; // It's fine to skip uninitialized properties in a diff
303        }
304    }
305
306    private function getLanguageAsArray( Lexeme $lexeme ) {
307        try {
308            return [ 'id' => $lexeme->getLanguage() ];
309        } catch ( UnexpectedValueException $ex ) {
310            return []; // It's fine to skip uninitialized properties in a diff
311        }
312    }
313
314}