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}