Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.56% covered (success)
95.56%
86 / 90
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZTypedMap
95.56% covered (success)
95.56%
86 / 90
66.67% covered (warning)
66.67%
6 / 9
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDefinition
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 buildType
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 isValid
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
7.23
 getKeyType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValueType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValueGivenKey
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
6.04
 setValueForKey
97.78% covered (success)
97.78%
44 / 45
0.00% covered (danger)
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
11namespace MediaWiki\Extension\WikiLambda\ZObjects;
12
13use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry;
14use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
15use MediaWiki\Extension\WikiLambda\ZErrorException;
16use MediaWiki\Extension\WikiLambda\ZErrorFactory;
17use MediaWiki\Extension\WikiLambda\ZObjectUtils;
18
19class 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}