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