Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.56% |
86 / 90 |
|
66.67% |
6 / 9 |
CRAP | |
0.00% |
0 / 1 |
ZTypedMap | |
95.56% |
86 / 90 |
|
66.67% |
6 / 9 |
28 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDefinition | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
buildType | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
isValid | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
7.23 | |||
getKeyType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getValueType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getList | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getValueGivenKey | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
6.04 | |||
setValueForKey | |
97.78% |
44 / 45 |
|
0.00% |
0 / 1 |
9 |
1 | <?php |
2 | /** |
3 | * WikiLambda ZTypedMap |
4 | * |
5 | * @file |
6 | * @ingroup Extensions |
7 | * @copyright 2020– WikiLambda team; see AUTHORS.txt |
8 | * @license MIT |
9 | */ |
10 | |
11 | namespace MediaWiki\Extension\WikiLambda\ZObjects; |
12 | |
13 | use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry; |
14 | use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry; |
15 | use MediaWiki\Extension\WikiLambda\ZErrorException; |
16 | use MediaWiki\Extension\WikiLambda\ZErrorFactory; |
17 | use MediaWiki\Extension\WikiLambda\ZObjectUtils; |
18 | |
19 | class ZTypedMap extends ZObject { |
20 | |
21 | /** |
22 | * Create a new ZTypedMap instance |
23 | * |
24 | * @param ZFunctionCall $functionCall |
25 | * @param ZTypedList|null $list |
26 | */ |
27 | public function __construct( $functionCall, $list = null ) { |
28 | $this->type = $functionCall; |
29 | $this->data[ 'K1' ] = $list; |
30 | } |
31 | |
32 | /** |
33 | * @inheritDoc |
34 | */ |
35 | public static function getDefinition(): array { |
36 | return [ |
37 | 'type' => [ |
38 | 'type' => ZTypeRegistry::Z_FUNCTIONCALL, |
39 | 'value' => ZTypeRegistry::Z_FUNCTION_TYPED_MAP, |
40 | ], |
41 | 'keys' => [ |
42 | 'K1' => [ |
43 | 'type' => ZTypeRegistry::Z_FUNCTION_TYPED_LIST, |
44 | ], |
45 | ], |
46 | ]; |
47 | } |
48 | |
49 | /** |
50 | * Build the function call that defines the type of this Typed Map |
51 | * |
52 | * @param string $keyType |
53 | * @param string $valueType |
54 | * @return ZFunctionCall |
55 | */ |
56 | public static function buildType( $keyType, $valueType ): ZFunctionCall { |
57 | return new ZFunctionCall( |
58 | new ZReference( ZTypeRegistry::Z_FUNCTION_TYPED_MAP ), |
59 | [ |
60 | ZTypeRegistry::Z_FUNCTION_TYPED_MAP_KEY_TYPE => new ZReference( $keyType ), |
61 | ZTypeRegistry::Z_FUNCTION_TYPED_MAP_VALUE_TYPE => new ZReference( $valueType ) |
62 | ] |
63 | ); |
64 | } |
65 | |
66 | /** |
67 | * Valid if each ZTypedPair in the ZTypedList is of the same type as the map's type. |
68 | * |
69 | * @inheritDoc |
70 | */ |
71 | public function isValid(): bool { |
72 | $typedList = $this->getList(); |
73 | if ( $typedList === null ) { |
74 | return false; |
75 | } |
76 | |
77 | foreach ( $typedList->getAsArray() as $index => $entry ) { |
78 | if ( !( $entry instanceof ZTypedPair ) ) { |
79 | return false; |
80 | } |
81 | |
82 | if ( |
83 | $entry->getFirstType()->getZValue() !== $this->getKeyType()->getZValue() || |
84 | $entry->getSecondType()->getZValue() !== $this->getValueType()->getZValue() |
85 | ) { |
86 | return false; |
87 | } |
88 | |
89 | if ( !$entry->isValid() ) { |
90 | return false; |
91 | } |
92 | } |
93 | |
94 | return true; |
95 | } |
96 | |
97 | /** |
98 | * Returns the type of the keys of this ZTypedMap |
99 | * |
100 | * @return ZReference The type of the keys |
101 | */ |
102 | public function getKeyType(): ZReference { |
103 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType phan can't tell this must be a ZReference |
104 | return $this->type->getValueByKey( ZTypeRegistry::Z_FUNCTION_TYPED_MAP_KEY_TYPE ); |
105 | } |
106 | |
107 | /** |
108 | * Returns the type of the values of this ZTypedMap |
109 | * |
110 | * @return ZReference The type of the values |
111 | */ |
112 | public function getValueType(): ZReference { |
113 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType phan can't tell this must be a ZReference |
114 | return $this->type->getValueByKey( ZTypeRegistry::Z_FUNCTION_TYPED_MAP_VALUE_TYPE ); |
115 | } |
116 | |
117 | /** |
118 | * Returns the ZTypedList |
119 | * |
120 | * @return ?ZTypedList The list of typed pairs |
121 | */ |
122 | public function getList(): ?ZTypedList { |
123 | return $this->data[ 'K1' ] ?? null; |
124 | } |
125 | |
126 | /** |
127 | * Returns the ZTypedList |
128 | * |
129 | * @param ZObject $key |
130 | * @return ?ZObject The value at the key, if available |
131 | */ |
132 | public function getValueGivenKey( ZObject $key ): ?ZObject { |
133 | $typedList = $this->getList(); |
134 | if ( $typedList === null ) { |
135 | return null; |
136 | } |
137 | |
138 | foreach ( $typedList->getAsArray() as $index => $pair ) { |
139 | if ( !( $pair instanceof ZTypedPair ) ) { |
140 | continue; |
141 | } |
142 | |
143 | $mapKey = $pair->getFirstElement(); |
144 | |
145 | if ( $mapKey && $mapKey->getZValue() === $key->getZValue() ) { |
146 | return $pair->getSecondElement(); |
147 | } |
148 | } |
149 | return null; |
150 | } |
151 | |
152 | /** |
153 | * Ensures there is an entry for the given key / value in the ZMap. If there is |
154 | * already an entry for the given key, overwrites the corresponding value. Otherwise, |
155 | * creates a new entry. N.B.: Modifies the content of the ZMap's list in place. |
156 | * |
157 | * TODO (T302015): When ZMap keys are extended beyond Z6/Z39, update accordingly |
158 | * |
159 | * @param ZObject $key A Z6 or Z39 instance to serve as the key |
160 | * @param ?ZObject $value A ZObject to set; if null, no object is set |
161 | */ |
162 | public function setValueForKey( ZObject $key, ?ZObject $value ) { |
163 | if ( $value === null ) { |
164 | return; |
165 | } |
166 | |
167 | $typedList = $this->getList(); |
168 | if ( $typedList === null ) { |
169 | // The list we're wrapping was not created correctly; nothing we can do but throw |
170 | throw new ZErrorException( |
171 | ZErrorFactory::createZErrorInstance( |
172 | ZErrorTypeRegistry::Z_ERROR_WRONG_LIST_TYPE, |
173 | [ |
174 | 'data' => $typedList |
175 | ] |
176 | ) |
177 | ); |
178 | } |
179 | |
180 | // Check the types of the key and value for compatibility |
181 | if ( !ZObjectUtils::isCompatibleType( $this->getKeyType(), $key ) ) { |
182 | // The key we've been given is of an unacceptable type |
183 | throw new ZErrorException( |
184 | ZErrorFactory::createZErrorInstance( |
185 | ZErrorTypeRegistry::Z_ERROR_ARRAY_TYPE_MISMATCH, |
186 | [ |
187 | 'key' => 'key', |
188 | 'expected' => $this->getKeyType()->getZValue(), |
189 | 'data' => $key->getValueByKey( ZTypeRegistry::Z_OBJECT_TYPE ), |
190 | ] |
191 | ) |
192 | ); |
193 | } |
194 | |
195 | if ( !ZObjectUtils::isCompatibleType( $this->getValueType(), $value ) ) { |
196 | // The value we've been given is of an unacceptable type |
197 | throw new ZErrorException( |
198 | ZErrorFactory::createZErrorInstance( |
199 | ZErrorTypeRegistry::Z_ERROR_ARRAY_TYPE_MISMATCH, |
200 | [ |
201 | 'key' => 'value', |
202 | 'expected' => $this->getValueType()->getZValue(), |
203 | 'data' => $value->getValueByKey( ZTypeRegistry::Z_OBJECT_TYPE ), |
204 | ] |
205 | ) |
206 | ); |
207 | } |
208 | |
209 | foreach ( $typedList->getAsArray() as $index => $pair ) { |
210 | if ( !( $pair instanceof ZTypedPair ) ) { |
211 | continue; |
212 | } |
213 | |
214 | $mapKey = $pair->getFirstElement(); |
215 | |
216 | if ( $mapKey && $mapKey->getZValue() === $key->getZValue() ) { |
217 | $pair->setSecondElement( $value ); |
218 | return; |
219 | } |
220 | } |
221 | |
222 | // The key isn't present in the map, so add an entry for it |
223 | $pairType = ZTypedPair::buildType( $this->getKeyType()->getZValue(), $this->getValueType()->getZValue() ); |
224 | $newPair = new ZTypedPair( $pairType, $key, $value ); |
225 | $typedList->appendArray( [ $newPair ], false ); |
226 | $this->data[ 'K1' ] = $typedList; |
227 | } |
228 | |
229 | } |