• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package escompat;
17
18export interface ReadonlyMap<K, V> extends Iterable<[K, V]> {
19    /**
20     * Returns number of Entries with unique keys in the Map
21     *
22     * @returns number of Entries with unique keys in the Map
23     */
24    get size(): number;
25
26    /**
27     * Returns a value assosiated with key if present
28     *
29     * @param k the key to find in the Map/class
30     *
31     * @returns value if assosiated with key presents.
32     */
33    get(key: K): V | undefined;
34
35    /**
36     * Checks if a key is in the Map
37     *
38     * @param k the key to find in the Map
39     *
40     * @returns true if the value is in the Map
41     */
42    has(key: K): boolean;
43
44    /**
45     * Executes a provided function once per each key/value pair in the Map, in insertion order
46     *
47     * @param callbackfn to apply
48     */
49    forEach(callbackfn: () => void): void;
50
51    /**
52     * Executes a provided function once per each key/value pair in the Map, in insertion order
53     *
54     * @param callbackfn to apply
55     */
56    forEach(callbackfn: (value: V) => void): void;
57
58    /**
59     * Executes a provided function once per each key/value pair in the Map, in insertion order
60     *
61     * @param fn to apply
62     */
63    forEach(callbackfn: (value: V, key: K) => void): void;
64
65    /**
66     * Executes a provided function once per each key/value pair in the Map, in insertion order
67     *
68     * @param callbackfn to apply
69     */
70    forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void): void;
71
72    /**
73     * Returns elements from the Map as an keys Iterator
74     *
75     * @returns ValueIterator with map keys
76     */
77    keys(): IterableIterator<K>;
78
79    /**
80     * Returns elements from the Map as an values Iterator
81     *
82     * @returns ValueIterator with map values
83     */
84    values(): IterableIterator<V>;
85
86    /**
87     * Returns elements from the Map as an array of Entries
88     *
89     * @returns an array of Entries
90     */
91    entries(): IterableIterator<[K, V]>;
92}
93
94final class MapEntry<K, V> {
95    prev: MapEntry<K, V> = this
96    next: MapEntry<K, V> = this
97
98    key: K
99    val: V
100
101    constructor(key: K, val: V) {
102        this.key = key
103        this.val = val
104    }
105
106    toString(): string {
107        return `{${this.key},${this.val}}`
108    }
109}
110
111final class MapIterator<K, V, R> implements IterableIterator<R> {
112    private node: MapEntry<K, V>
113    private tail: MapEntry<K, V>
114
115    private mapper: (e: MapEntry<K, V>) => R
116
117    constructor(node: MapEntry<K, V>, tail: MapEntry<K, V>, mapper: (e: MapEntry<K, V>) => R) {
118        this.node = node
119        this.tail = tail
120        this.mapper = mapper
121    }
122
123    override next(): IteratorResult<R> {
124        const nextNode = this.node.next
125        if (__runtimeIsSameReference(nextNode, this.tail)) {
126            return new IteratorResult<R>()
127        }
128        this.node = nextNode
129        return new IteratorResult<R>(this.mapper(this.node))
130    }
131
132    override $_iterator(): IterableIterator<R> {
133        return this
134    }
135}
136
137final class EmptyMapIterator<R> implements IterableIterator<R> {
138    override next(): IteratorResult<R> {
139        return new IteratorResult<R>()
140    }
141
142    override $_iterator(): IterableIterator<R> {
143        return this
144    }
145}
146
147export class Map<K, V> implements ReadonlyMap<K, V> {
148    private headEntry: MapEntry<K, V> | undefined = undefined
149
150    private buckets: Array<Array<MapEntry<K, V>>> = new Array<Array<MapEntry<K, V>>>()
151    private sizeVal: int = 0
152
153    private static readonly ENTRY_KEY = 0
154    private static readonly ENTRY_VAL = 1
155
156    internal static hashOf(a: NullishType): int {
157        if (__runtimeIsSameReference(a, null)) {
158            return 0;
159        }
160        if (__runtimeIsSameReference(a, undefined)) {
161            return 1;
162        }
163        return a!.$_hashCode();
164    }
165
166    /**
167     * Constructs a Map from collection
168     * @param entries initial collection
169     */
170    constructor(entries?: ArrayLike<[K, V]> | Iterable<[K, V]> | null) {
171        if (entries != null) {
172            const entriesIter = entries.$_iterator()
173            const entriesBuf = new Array<[K, V]>()
174            for (let iterRes = entriesIter.next();!iterRes.done;iterRes = entriesIter.next()) {
175                this.buckets.push(new Array<MapEntry<K, V>>())
176
177                const entry = iterRes.value
178                if (entry != null) {
179                    entriesBuf.push(entry)
180                }
181            }
182
183            entriesBuf.forEach((e: [K, V]) => {
184                this.set(e[Map.ENTRY_KEY], e[Map.ENTRY_VAL])
185            })
186        } else {
187            this.buckets.push(new Array<MapEntry<K, V>>())
188        }
189    }
190
191    override toString(): String {
192        const strBuf = new StringBuilder()
193
194        const entriesIter = this.entries()
195        let entriesIterRes = entriesIter.next()
196        while (!entriesIterRes.done) {
197            const entry = entriesIterRes.value
198            if (!__runtimeIsSameReference(entry, undefined) && !__runtimeIsSameReference(entry, null)) {
199                strBuf.append(entry!)
200            }
201
202            entriesIterRes = entriesIter.next()
203            if (!entriesIterRes.done) {
204                strBuf.append(",")
205            }
206        }
207
208        return strBuf.toString()
209    }
210
211    private getBucket(v: NullishType): Array<MapEntry<K, V>> {
212        let idx = Map.hashOf(v) % this.buckets.actualLength;
213        idx += this.buckets.actualLength;
214        idx %= this.buckets.actualLength;
215        return this.buckets[idx]
216    }
217
218    private rehash(): void {
219        const entriesIter = this.mappedIterator<MapEntry<K, V>>((e: MapEntry<K, V>): MapEntry<K, V> => e)
220        const oldSize = this.buckets.length
221        for (let i = 0; i < oldSize; i++) {
222            const a = this.buckets[i]
223            a.length = 0
224            this.buckets.push(new Array<MapEntry<K, V>>())
225        }
226        iteratorForEach<MapEntry<K, V>>(entriesIter, (e: MapEntry<K, V>): void => {
227            this.getBucket(e.key).push(e)
228        })
229    }
230
231
232    /**
233     * Puts a pair of key and value into the Map
234     *
235     * @param key the key to put into the Map
236     *
237     * @param val the value to put into the Map
238     */
239    set(key: K, val: V): this {
240        // insert into buckets
241        const buck = this.getBucket(key)
242        const buckSize = buck.length
243        for (let i = 0; i < buckSize; i++) {
244            const entry = buck.$_get(i)
245            if (__runtimeSameValueZero(entry.key, key)) {
246                entry.val = val
247                return this
248            }
249        }
250
251        // not found, add to insertion-ordered list
252        const newEntry = new MapEntry<K, V>(key, val)
253
254        if (this.headEntry === undefined) {
255            this.headEntry = new MapEntry<K, V>(key, val)
256        }
257
258        const headEntry = this.headEntry!
259        newEntry.next = headEntry
260        newEntry.prev = headEntry.prev
261        headEntry.prev.next = newEntry
262        headEntry.prev = newEntry
263
264        buck.push(newEntry)
265
266        this.sizeVal += 1
267
268        // constant load factor (entries / buckets < 0.75)
269        if (this.sizeVal * 4 > this.buckets.length * 3) {
270            this.rehash();
271        }
272
273        return this;
274    }
275
276    /**
277     * Checks if a key is in the Map
278     *
279     * @param key the key to find in the Map
280     *
281     * @returns true if the value is in the Map
282     */
283    override has(key: K): boolean {
284        const buck = this.getBucket(key)
285        const buckSize = buck.length
286        for (let i = 0; i < buckSize; i++) {
287            const entry = buck.$_get(i)
288            if (__runtimeSameValueZero(entry.key, key)) {
289                return true
290            }
291        }
292        return false;
293    }
294
295    /**
296     * Returns number of Entries with unique keys in the Map
297     *
298     * @returns number of Entries with unique keys in the Map
299     */
300    override get size(): number {
301        return this.sizeVal
302    }
303
304    /**
305     * Removes an Entry with specified key from the Map
306     *
307     * @param key the key to remove
308     */
309    delete(key: K): boolean {
310        return this.deleteImpl(key)
311    }
312
313    internal deleteImpl(key: K): boolean {
314        const buck = this.getBucket(key)
315        const buckSize = buck.length
316        for (let i = 0; i < buckSize; i++) {
317            const entry = buck.$_get(i)
318            if (__runtimeSameValueZero(entry.key, key)) {
319                entry.prev.next = entry.next
320                entry.next.prev = entry.prev
321                buck.fill(buck.pop()!, i, i + 1)
322                this.sizeVal -= 1
323
324                if (this.sizeVal == 0) {
325                    this.headEntry = undefined
326                }
327
328                return true
329            }
330        }
331        return false
332    }
333
334    /**
335     * Deletes all elements from the Map
336     */
337    clear(): void {
338        this.sizeVal = 0
339
340        if (this.headEntry !== undefined) {
341            this.headEntry = undefined
342            this.buckets = new Array<Array<MapEntry<K, V>>>()
343            this.buckets.push(new Array<MapEntry<K, V>>())
344        }
345    }
346
347    /**
348     * Returns a value assosiated with key if present
349     *
350     * @param key the key to find in the Map
351     *
352     * @returns value if assosiated with key presents.
353     */
354    override get(key: K): V | undefined {
355        const buck = this.getBucket(key)
356        const buckSize = buck.length
357        for (let i = 0; i < buckSize; i++) {
358            const entry = buck.$_get(i)
359            if (__runtimeSameValueZero(entry.key, key)) {
360                return entry.val
361            }
362        }
363        return undefined;
364    }
365
366    /**
367     * Returns a value assosiated with key if present, or a default value otherwise
368     *
369     * @param key the key to find in the Map
370     *
371     * @param def a value to return if key is not in the Map
372     *
373     * @returns value if key presents, def otherwise
374     */
375    get(key: K, def: V): V {
376        const val = this.get(key)
377        if (val !== undefined) {
378            return val
379        }
380        return def
381    }
382
383    internal mappedIterator<R>(fn: (e: MapEntry<K, V>) => R): IterableIterator<R> {
384        const headEntry = this.headEntry
385        if (headEntry !== undefined) {
386            return new MapIterator<K, V, R>(headEntry, headEntry, fn)
387        } else {
388            return new EmptyMapIterator<R>()
389        }
390    }
391
392    /**
393     * Returns elements from the Map as an keys Iterator
394     *
395     * @returns iterator with map keys
396     */
397    override keys(): IterableIterator<K> {
398        return this.mappedIterator<K>((e: MapEntry<K, V>): K => e.key)
399    }
400
401    /**
402     * Returns elements from the Map as an values Iterator
403     *
404     * @returns iterator with map values
405     */
406    override values(): IterableIterator<V> {
407        return this.mappedIterator<V>((e: MapEntry<K, V>): V => e.val )
408    }
409
410    override $_iterator(): IterableIterator<[K, V]> {
411        return this.entries()
412    }
413
414    /**
415     * Returns elements from the Map as an array of Entries
416     *
417     * @returns an array of Entries
418     */
419    override entries(): IterableIterator<[K, V]> {
420        return this.mappedIterator<[K, V]>((e: MapEntry<K, V>): [K, V] => [e.key, e.val])
421    }
422
423    /**
424     * Executes a provided function once per each key/value pair in the Map, in insertion order
425     *
426     * @param callbackfn to apply
427     */
428    override forEach(callbackfn: (v: V, k: K, map: Map<K, V>) => void): void {
429        const entriesIter = this.mappedIterator<MapEntry<K, V>>((e: MapEntry<K, V>): MapEntry<K, V> => e)
430        iteratorForEach<MapEntry<K, V>>(entriesIter, (e: MapEntry<K, V>): void => {
431            callbackfn(e.val, e.key, this)
432        })
433    }
434
435    /**
436     * Executes a provided function once per each key/value pair in the Map, in insertion order
437     *
438     * @param callbackfn to apply
439     */
440    override forEach(callbackfn: (v: V, k: K) => void): void {
441        const entriesIter = this.mappedIterator<MapEntry<K, V>>((e: MapEntry<K, V>): MapEntry<K, V> => e)
442        iteratorForEach<MapEntry<K, V>>(entriesIter, (e: MapEntry<K, V>): void => {
443            callbackfn(e.val, e.key)
444        })
445    }
446
447    /**
448     * Executes a provided function once per each key/value pair in the Map, in insertion order
449     *
450     * @param callbackfn to apply
451     */
452    override forEach(callbackfn: (v: V) => void): void {
453        iteratorForEach<V>(this.values(), callbackfn)
454    }
455
456    /**
457     * Executes a provided function once per each key/value pair in the Map, in insertion order
458     *
459     * @param callbackfn to apply
460     */
461    override forEach(callbackfn: () => void): void {
462        iteratorForEach<V>(this.values(), (v: V) => callbackfn())
463    }
464}
465
466export class Record<K extends Numeric | string, V> extends Map<K, V> {
467    $_get(k : K) : V | undefined {
468        return super.get(k)
469    }
470
471    $_set(k: K, v: V) : void {
472        super.set(k, v)
473    }
474}