Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.27% |
107 / 110 |
|
86.67% |
13 / 15 |
CRAP | |
0.00% |
0 / 1 |
LexemeDiffer | |
97.27% |
107 / 110 |
|
86.67% |
13 / 15 |
29 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
canDiffEntityType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
diffEntities | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
diffLexemes | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
1 | |||
toDiffArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getConstructionDiff | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDestructionDiff | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getFormsDiff | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
5 | |||
toFormsDiffArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getNextFormIdCounterDiff | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getSensesDiff | |
91.30% |
21 / 23 |
|
0.00% |
0 / 1 |
5.02 | |||
toSensesDiffArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getNextSenseIdCounterDiff | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getLexicalCategoryAsArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getLanguageAsArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace Wikibase\Lexeme\Domain\Diff; |
4 | |
5 | use Diff\Comparer\CallbackComparer; |
6 | use Diff\Comparer\ComparableComparer; |
7 | use Diff\Differ\MapDiffer; |
8 | use Diff\DiffOp\Diff\Diff; |
9 | use Diff\DiffOp\DiffOpAdd; |
10 | use Diff\DiffOp\DiffOpChange; |
11 | use Diff\DiffOp\DiffOpRemove; |
12 | use InvalidArgumentException; |
13 | use UnexpectedValueException; |
14 | use Wikibase\DataModel\Entity\EntityDocument; |
15 | use Wikibase\DataModel\Services\Diff\EntityDiff; |
16 | use Wikibase\DataModel\Services\Diff\EntityDifferStrategy; |
17 | use Wikibase\DataModel\Services\Diff\StatementListDiffer; |
18 | use Wikibase\Lexeme\Domain\Model\Form; |
19 | use Wikibase\Lexeme\Domain\Model\FormSet; |
20 | use Wikibase\Lexeme\Domain\Model\Lexeme; |
21 | use Wikibase\Lexeme\Domain\Model\Sense; |
22 | use Wikibase\Lexeme\Domain\Model\SenseSet; |
23 | use Wikimedia\Assert\Assert; |
24 | |
25 | /** |
26 | * @license GPL-2.0-or-later |
27 | */ |
28 | class 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 | } |