• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2025 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
18 /**
19  * A WeakMap is a collection of key/value pairs whose keys must be objects,
20  * with values of any arbitrary type, and which does not create strong references to its keys.
21  * That is, an object's presence as a key in a WeakMap does not prevent the object from being garbage collected.
22  * Once an object used as a key has been collected,
23  * its corresponding values in any WeakMap become candidates for garbage collection as well
24  */
25export final class WeakMap<K extends Object, V> {
26    private readonly keyRefToEntry = new Map<WeakRef<K>, WeakMapEntry<V>>
27    private readonly keyHashToKeyRefs = new Map<Int, Array<WeakRef<K>>>()
28
29    private readonly registry: FinalizationRegistry<WeakRef<K>>
30
31    private static readonly KEY = 0
32    private static readonly VAL = 1
33
34    /**
35     * The WeakMap() constructor creates WeakMap objects.
36     */
37    constructor(entries?: ([K, V])[] | null) {
38        this.registry = new FinalizationRegistry<WeakRef<K>>((keyRef: WeakRef<K>) => {
39            const entry = this.keyRefToEntry.get(keyRef)
40            if (entry === undefined) {
41                return
42            }
43
44            this.keyRefToEntry.delete(keyRef)
45
46            const keyRefs = this.keyHashToKeyRefs.get(entry.keyHash)
47            if (keyRefs !== undefined) {
48                this.deleteKeyRef(keyRefs, keyRef, entry.keyHash)
49            }
50        })
51
52        if (entries != null) {
53            for (const entry of entries) {
54                this.set(entry[WeakMap.KEY], entry[WeakMap.VAL])
55            }
56        }
57    }
58
59    /**
60     * The WeakMap() constructor creates WeakMap objects.
61     */
62    constructor(entries: Iterable<[K, V]>) {
63        this(null)
64
65        iteratorForEach<[K, V]>(entries.$_iterator(), (entry: [K, V]) => {
66            this.set(entry[0] as K, entry[1] as V)
67        })
68    }
69
70    /**
71     * The set() method adds a new element with a specified key
72     * and value to a WeakMap object.
73     */
74    set(key: K, val: V): WeakMap<K, V> {
75        const keyHash = Runtime.getHashCode(key)
76
77        const keyRefs = this.keyHashToKeyRefs.get(keyHash)
78        if (keyRefs !== undefined) {
79            const keyRef = this.findKeyRef(keyRefs, key)
80            if (keyRef !== undefined) {
81                this.keyRefToEntry.set(keyRef, new WeakMapEntry<V>(keyHash, val))
82            } else {
83                const newKeyRef = this.addEntry(key, keyHash, val)
84                keyRefs.push(newKeyRef)
85            }
86        } else {
87            const newKeyRef = this.addEntry(key, keyHash, val)
88
89            const newKeyRefs = new Array<WeakRef<K>>(newKeyRef)
90            this.keyHashToKeyRefs.set(keyHash, newKeyRefs)
91        }
92
93        return this
94    }
95
96    /**
97     * The has() method returns a boolean indicating whether
98     * an element with the specified key exists in the WeakMap
99     * object or not.
100     */
101    has(key: K): boolean {
102        const keyHash = Runtime.getHashCode(key)
103
104        const keyRefs = this.keyHashToKeyRefs.get(keyHash)
105        if (keyRefs === undefined) {
106            return false
107        }
108
109        const keyRef = this.findKeyRef(keyRefs, key)
110        if (keyRef === undefined) {
111            return false
112        }
113
114        return this.keyRefToEntry.has(keyRef)
115    }
116
117    /**
118     * The get() method returns a specified element from
119     * a WeakMap object.
120     */
121    get(key: K): V | undefined {
122        const keyHash = Runtime.getHashCode(key)
123
124        const keyRefs = this.keyHashToKeyRefs.get(keyHash)
125        if (keyRefs === undefined) {
126            return undefined
127        }
128
129        const keyRef = this.findKeyRef(keyRefs, key)
130        if (keyRef === undefined) {
131            return undefined
132        }
133
134        const entry = this.keyRefToEntry.get(keyRef)
135        if (entry === undefined) {
136            return undefined
137        }
138
139        return entry.value
140    }
141
142    /**
143     * The delete() method removes the specified element from
144     * a WeakMap object.
145     */
146    delete(key: K): boolean {
147        const keyHash = Runtime.getHashCode(key)
148
149        const keyRefs = this.keyHashToKeyRefs.get(keyHash)
150        if (keyRefs === undefined) {
151            return false
152        }
153
154        const keyRef = this.findKeyRef(keyRefs, key)
155        if (keyRef === undefined) {
156            return false
157        }
158
159        // actual removal
160        this.keyRefToEntry.delete(keyRef)
161
162        this.deleteKeyRef(keyRefs, keyRef, keyHash)
163
164        return true
165    }
166
167    private findKeyRef(keyRefs: ReadonlyArray<WeakRef<K>>, key: K): WeakRef<K> | undefined {
168        return keyRefs.find((ref: WeakRef<K>) => key === ref.deref())
169    }
170
171    private addEntry(key: K, keyHash: int, val: V): WeakRef<K> {
172        const newKeyRef = new WeakRef<K>(key)
173        this.keyRefToEntry.set(newKeyRef, new WeakMapEntry<V>(keyHash, val))
174
175        this.registry.register(key, newKeyRef)
176
177        return newKeyRef
178    }
179
180    private deleteKeyRef(keyRefs: Array<WeakRef<K>>, keyRef: WeakRef<K>, keyHash: int): void {
181        if (keyRefs.length == 0) {
182            return
183        }
184
185        if (keyRefs.length == 1) {
186            keyRefs.pop()
187        } else {
188            const keyRefPos = keyRefs.indexOf(keyRef)
189            if (keyRefPos == -1) {
190                return
191            }
192
193            if (keyRefPos != keyRefs.length - 1) {
194                const lastRefPos = keyRefs.length - 1
195                // overwriting found key ref with the last key ref in the list
196                keyRefs[keyRefPos] = keyRefs[lastRefPos]
197            }
198
199            keyRefs.pop()
200        }
201
202        if (keyRefs.length == 0) {
203            this.keyHashToKeyRefs.delete(keyHash)
204        }
205    }
206}
207
208class WeakMapEntry<V> {
209    private readonly _keyHash: int
210    private readonly _value: V
211
212    constructor(keyHash: int, value: V) {
213        this._keyHash = keyHash
214        this._value = value
215    }
216
217    get keyHash(): int {
218        return this._keyHash
219    }
220
221    get value(): V {
222        return this._value
223    }
224}
225