1namespace collections { 2 export interface SortOptions<T> { 3 comparer: (a: T, b: T) => number; 4 sort: "insertion" | "comparison"; 5 } 6 7 export class SortedMap<K, V> { 8 private _comparer: (a: K, b: K) => number; 9 private _keys: K[] = []; 10 private _values: V[] = []; 11 private _order: number[] | undefined; 12 private _version = 0; 13 private _copyOnWrite = false; 14 15 constructor(comparer: ((a: K, b: K) => number) | SortOptions<K>, iterable?: Iterable<[K, V]>) { 16 this._comparer = typeof comparer === "object" ? comparer.comparer : comparer; 17 this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined; 18 if (iterable) { 19 const iterator = getIterator(iterable); 20 try { 21 for (let i = nextResult(iterator); i; i = nextResult(iterator)) { 22 const [key, value] = i.value; 23 this.set(key, value); 24 } 25 } 26 finally { 27 closeIterator(iterator); 28 } 29 } 30 } 31 32 public get size() { 33 return this._keys.length; 34 } 35 36 public get comparer() { 37 return this._comparer; 38 } 39 40 public get [Symbol.toStringTag]() { 41 return "SortedMap"; 42 } 43 44 public has(key: K) { 45 return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0; 46 } 47 48 public get(key: K) { 49 const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer); 50 return index >= 0 ? this._values[index] : undefined; 51 } 52 53 public getEntry(key: K): [ K, V ] | undefined { 54 const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer); 55 return index >= 0 ? [ this._keys[index], this._values[index] ] : undefined; 56 } 57 58 public set(key: K, value: V) { 59 const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer); 60 if (index >= 0) { 61 this._values[index] = value; 62 } 63 else { 64 this.writePreamble(); 65 insertAt(this._keys, ~index, key); 66 insertAt(this._values, ~index, value); 67 if (this._order) insertAt(this._order, ~index, this._version); 68 this.writePostScript(); 69 } 70 return this; 71 } 72 73 public delete(key: K) { 74 const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer); 75 if (index >= 0) { 76 this.writePreamble(); 77 ts.orderedRemoveItemAt(this._keys, index); 78 ts.orderedRemoveItemAt(this._values, index); 79 if (this._order) ts.orderedRemoveItemAt(this._order, index); 80 this.writePostScript(); 81 return true; 82 } 83 return false; 84 } 85 86 public clear() { 87 if (this.size > 0) { 88 this.writePreamble(); 89 this._keys.length = 0; 90 this._values.length = 0; 91 if (this._order) this._order.length = 0; 92 this.writePostScript(); 93 } 94 } 95 96 public forEach(callback: (value: V, key: K, collection: this) => void, thisArg?: any) { 97 const keys = this._keys; 98 const values = this._values; 99 const indices = this.getIterationOrder(); 100 const version = this._version; 101 this._copyOnWrite = true; 102 try { 103 if (indices) { 104 for (const i of indices) { 105 callback.call(thisArg, values[i], keys[i], this); 106 } 107 } 108 else { 109 for (let i = 0; i < keys.length; i++) { 110 callback.call(thisArg, values[i], keys[i], this); 111 } 112 } 113 } 114 finally { 115 if (version === this._version) { 116 this._copyOnWrite = false; 117 } 118 } 119 } 120 121 public * keys() { 122 const keys = this._keys; 123 const indices = this.getIterationOrder(); 124 const version = this._version; 125 this._copyOnWrite = true; 126 try { 127 if (indices) { 128 for (const i of indices) { 129 yield keys[i]; 130 } 131 } 132 else { 133 yield* keys; 134 } 135 } 136 finally { 137 if (version === this._version) { 138 this._copyOnWrite = false; 139 } 140 } 141 } 142 143 public * values() { 144 const values = this._values; 145 const indices = this.getIterationOrder(); 146 const version = this._version; 147 this._copyOnWrite = true; 148 try { 149 if (indices) { 150 for (const i of indices) { 151 yield values[i]; 152 } 153 } 154 else { 155 yield* values; 156 } 157 } 158 finally { 159 if (version === this._version) { 160 this._copyOnWrite = false; 161 } 162 } 163 } 164 165 public * entries() { 166 const keys = this._keys; 167 const values = this._values; 168 const indices = this.getIterationOrder(); 169 const version = this._version; 170 this._copyOnWrite = true; 171 try { 172 if (indices) { 173 for (const i of indices) { 174 yield [keys[i], values[i]] as [K, V]; 175 } 176 } 177 else { 178 for (let i = 0; i < keys.length; i++) { 179 yield [keys[i], values[i]] as [K, V]; 180 } 181 } 182 } 183 finally { 184 if (version === this._version) { 185 this._copyOnWrite = false; 186 } 187 } 188 } 189 190 public [Symbol.iterator]() { 191 return this.entries(); 192 } 193 194 private writePreamble() { 195 if (this._copyOnWrite) { 196 this._keys = this._keys.slice(); 197 this._values = this._values.slice(); 198 if (this._order) this._order = this._order.slice(); 199 this._copyOnWrite = false; 200 } 201 } 202 203 private writePostScript() { 204 this._version++; 205 } 206 207 private getIterationOrder() { 208 if (this._order) { 209 const order = this._order; 210 return this._order 211 .map((_, i) => i) 212 .sort((x, y) => order[x] - order[y]); 213 } 214 return undefined; 215 } 216 } 217 218 export function insertAt<T>(array: T[], index: number, value: T): void { 219 if (index === 0) { 220 array.unshift(value); 221 } 222 else if (index === array.length) { 223 array.push(value); 224 } 225 else { 226 for (let i = array.length; i > index; i--) { 227 array[i] = array[i - 1]; 228 } 229 array[index] = value; 230 } 231 } 232 233 export function getIterator<T>(iterable: Iterable<T>): Iterator<T> { 234 return iterable[Symbol.iterator](); 235 } 236 237 export function nextResult<T>(iterator: Iterator<T>): IteratorResult<T> | undefined { 238 const result = iterator.next(); 239 return result.done ? undefined : result; 240 } 241 242 export function closeIterator<T>(iterator: Iterator<T>) { 243 const fn = iterator.return; 244 if (typeof fn === "function") fn.call(iterator); 245 } 246 247 /** 248 * A collection of metadata that supports inheritance. 249 */ 250 export class Metadata { 251 private static readonly _undefinedValue = {}; 252 private _parent: Metadata | undefined; 253 private _map: { [key: string]: any }; 254 private _version = 0; 255 private _size = -1; 256 private _parentVersion: number | undefined; 257 258 constructor(parent?: Metadata) { 259 this._parent = parent; 260 this._map = Object.create(parent ? parent._map : null); // eslint-disable-line no-null/no-null 261 } 262 263 public get size(): number { 264 if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) { 265 let size = 0; 266 for (const _ in this._map) size++; 267 this._size = size; 268 if (this._parent) { 269 this._parentVersion = this._parent._version; 270 } 271 } 272 return this._size; 273 } 274 275 public get parent() { 276 return this._parent; 277 } 278 279 public has(key: string): boolean { 280 return this._map[Metadata._escapeKey(key)] !== undefined; 281 } 282 283 public get(key: string): any { 284 const value = this._map[Metadata._escapeKey(key)]; 285 return value === Metadata._undefinedValue ? undefined : value; 286 } 287 288 public set(key: string, value: any): this { 289 this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value; 290 this._size = -1; 291 this._version++; 292 return this; 293 } 294 295 public delete(key: string): boolean { 296 const escapedKey = Metadata._escapeKey(key); 297 if (this._map[escapedKey] !== undefined) { 298 delete this._map[escapedKey]; 299 this._size = -1; 300 this._version++; 301 return true; 302 } 303 return false; 304 } 305 306 public clear(): void { 307 this._map = Object.create(this._parent ? this._parent._map : null); // eslint-disable-line no-null/no-null 308 this._size = -1; 309 this._version++; 310 } 311 312 public forEach(callback: (value: any, key: string, map: this) => void) { 313 for (const key in this._map) { 314 callback(this._map[key], Metadata._unescapeKey(key), this); 315 } 316 } 317 318 private static _escapeKey(text: string) { 319 return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text); 320 } 321 322 private static _unescapeKey(text: string) { 323 return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text); 324 } 325 } 326} 327