UNPKG

@itwin/presentation-common

Version:

Common pieces for iModel.js presentation packages

467 lines • 16.4 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Core */ Object.defineProperty(exports, "__esModule", { value: true }); exports.KeySet = exports.Key = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const Error_js_1 = require("./Error.js"); /** @public */ // eslint-disable-next-line @typescript-eslint/no-redeclare var Key; (function (Key) { /** Check if the supplied key is a `NodeKey` */ function isNodeKey(key) { return !!key.type; } Key.isNodeKey = isNodeKey; /** Check if the supplied key is an `InstanceKey` */ function isInstanceKey(key) { return !!key.className && !!key.id; } Key.isInstanceKey = isInstanceKey; /** Check if the supplied key is an `EntityProps` */ function isEntityProps(key) { return !!key.classFullName && !!key.id; } Key.isEntityProps = isEntityProps; })(Key || (exports.Key = Key = {})); /** * A class that holds multiple [[Key]] objects. It's basically * used as a container that holds multiple keys of different types. * * @public */ class KeySet { // note: all keys are stored as strings because we need ability to find them by value _instanceKeys; // lower case class name => instance ids _lowerCaseMap; // lower case class name => most recent class name _nodeKeys; _guid; /** * Creates an instance of KeySet. * @param source Optional source to initialize from. */ constructor(source) { this._instanceKeys = new Map(); this._lowerCaseMap = new Map(); this._nodeKeys = new Set(); this.recalculateGuid(); if (source) { this.add(source); } } recalculateGuid() { // empty keyset should have empty guid, otherwise use a random guid value this._guid = this.isEmpty ? core_bentley_1.Guid.empty : core_bentley_1.Guid.createValue(); } /** * Get a GUID that identifies changes in this keyset. The value * does not uniquely identify contents of the keyset, but it can be * used to check whether keyset has changed. */ get guid() { return this._guid; } /** * Get a map of instance keys stored in this KeySet * * **Warning**: getting instance keys might be expensive for * large KeySets. */ get instanceKeys() { const map = new Map(); for (const entry of this._instanceKeys) { map.set(this._lowerCaseMap.get(entry["0"]), new Set([...entry["1"]].map((key) => core_bentley_1.Id64.fromJSON(key)))); } return map; } /** * Get instance keys count */ get instanceKeysCount() { let count = 0; this._instanceKeys.forEach((set) => (count += set.size)); return count; } /** * Get a set of node keys stored in this KeySet * * **Warning**: getting node keys might be expensive for * large KeySets. */ get nodeKeys() { const set = new Set(); for (const serialized of this._nodeKeys) { set.add(JSON.parse(serialized)); } return set; } /** * Get node keys count */ get nodeKeysCount() { return this._nodeKeys.size; } isKeySet(set) { return !!set._nodeKeys && !!set._instanceKeys; } isKeysArray(keys) { return Array.isArray(keys); } /** * Clear this KeySet. * @returns itself */ clear() { if (this.isEmpty) { return this; } this._instanceKeys = new Map(); this._lowerCaseMap = new Map(); this._nodeKeys = new Set(); this.recalculateGuid(); return this; } addKeySet(keyset, pred) { for (const key of keyset._nodeKeys) { if (!pred || pred(JSON.parse(key))) { this._nodeKeys.add(key); } } for (const entry of keyset._instanceKeys) { let set = this._instanceKeys.get(entry["0"]); const className = keyset._lowerCaseMap.get(entry["0"]); if (!set) { set = new Set(); this._instanceKeys.set(entry["0"], set); this._lowerCaseMap.set(entry["0"], className); } entry["1"].forEach((id) => { if (!pred || pred({ className, id })) { set.add(id); } }); } } addKeySetJSON(keyset) { for (const key of keyset.nodeKeys) { this._nodeKeys.add(JSON.stringify(key)); } for (const entry of keyset.instanceKeys) { const normalizedClassName = normalizeClassName(entry["0"]); const lcClassName = normalizedClassName.toLowerCase(); const idsJson = entry["1"]; const ids = typeof idsJson === "string" ? (idsJson === core_bentley_1.Id64.invalid ? new Set([core_bentley_1.Id64.invalid]) : core_bentley_1.CompressedId64Set.decompressSet(idsJson)) : new Set(idsJson); this._instanceKeys.set(lcClassName, ids); this._lowerCaseMap.set(lcClassName, normalizedClassName); } } /** * Add a key or keys to this KeySet. * @param value A key or keys to add. * @param pred An optional predicate function that indicates whether a key should be added * @returns itself */ add(value, pred) { if (!value) { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${value}`); } const sizeBefore = this.size; if (this.isKeySet(value)) { this.addKeySet(value, pred); } else if (this.isKeysArray(value)) { value.forEach((key) => this.add(key, pred)); } else if (!pred || pred(value)) { if (Key.isEntityProps(value)) { this.add({ className: value.classFullName, id: core_bentley_1.Id64.fromJSON(value.id) }); } else if (Key.isInstanceKey(value)) { const normalizedClassName = normalizeClassName(value.className); const lcClassName = normalizedClassName.toLowerCase(); if (!this._instanceKeys.has(lcClassName)) { this._instanceKeys.set(lcClassName, new Set()); } this._lowerCaseMap.set(lcClassName, normalizedClassName); this._instanceKeys.get(lcClassName).add(value.id); } else if (Key.isNodeKey(value)) { this._nodeKeys.add(JSON.stringify(value)); } else { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${JSON.stringify(value)}`); } } if (this.size !== sizeBefore) { this.recalculateGuid(); } return this; } deleteKeySet(keyset) { for (const key of keyset._nodeKeys) { this._nodeKeys.delete(key); } for (const entry of keyset._instanceKeys) { const set = this._instanceKeys.get(entry["0"]); if (set) { entry["1"].forEach((key) => { set.delete(key); }); } } } /** * Deletes a key or keys from this KeySet. * @param value A key or keys to delete. * @returns itself */ delete(value) { if (!value) { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${value}`); } const sizeBefore = this.size; if (this.isKeySet(value)) { this.deleteKeySet(value); } else if (this.isKeysArray(value)) { for (const key of value) { this.delete(key); } } else if (Key.isEntityProps(value)) { this.delete({ className: value.classFullName, id: value.id }); } else if (Key.isInstanceKey(value)) { const normalizedClassName = normalizeClassName(value.className); const set = this._instanceKeys.get(normalizedClassName.toLowerCase()); if (set) { set.delete(value.id); } } else if (Key.isNodeKey(value)) { this._nodeKeys.delete(JSON.stringify(value)); } else { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${JSON.stringify(value)}`); } if (this.size !== sizeBefore) { this.recalculateGuid(); } return this; } /** * Check if this KeySet contains the specified key. * @param value The key to check. */ has(value) { if (!value) { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${value}`); } if (Key.isEntityProps(value)) { return this.has({ className: value.classFullName, id: value.id }); } if (Key.isInstanceKey(value)) { const normalizedClassName = normalizeClassName(value.className); const set = this._instanceKeys.get(normalizedClassName.toLowerCase()); return !!(set && set.has(value.id)); } if (Key.isNodeKey(value)) { return this._nodeKeys.has(JSON.stringify(value)); } throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${JSON.stringify(value)}`); } hasKeySet(readonlyKeys, checkType) { // note: cast-away read-onlyness to access private members... const keys = readonlyKeys; if (checkType === "all") { if (this._nodeKeys.size < keys._nodeKeys.size || this._instanceKeys.size < keys._instanceKeys.size) { return false; } if ([...keys._nodeKeys].some((key) => !this._nodeKeys.has(key))) { return false; } for (const otherEntry of keys._instanceKeys) { const thisEntryKeys = this._instanceKeys.get(otherEntry["0"]); if (!thisEntryKeys || thisEntryKeys.size < otherEntry["1"].size) { return false; } if ([...otherEntry["1"]].some((key) => !thisEntryKeys.has(key))) { return false; } } return true; } // "any" check type if ([...keys._nodeKeys].some((key) => this._nodeKeys.has(key))) { return true; } for (const otherEntry of keys._instanceKeys) { const thisEntryKeys = this._instanceKeys.get(otherEntry["0"]); if (thisEntryKeys && [...otherEntry["1"]].some((key) => thisEntryKeys.has(key))) { return true; } } return false; } hasKeysArray(keys, checkType) { if (checkType === "all") { if (this.size < keys.length) { return false; } for (const key of keys) { if (!this.has(key)) { return false; } } return true; } // "any" check type for (const key of keys) { if (this.has(key)) { return true; } } return false; } /** * Check if this KeySet contains all the specified keys. * @param keys The keys to check. */ hasAll(keys) { if (!keys) { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${keys}`); } if (this.isKeySet(keys)) { return this.hasKeySet(keys, "all"); } if (this.isKeysArray(keys)) { return this.hasKeysArray(keys, "all"); } throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: keys = ${keys}`); } /** * Check if this KeySet contains any of the specified keys. * @param keys The keys to check. */ hasAny(keys) { if (!keys) { throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: value = ${keys}`); } if (this.isKeySet(keys)) { return this.hasKeySet(keys, "any"); } if (this.isKeysArray(keys)) { return this.hasKeysArray(keys, "any"); } throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid argument: keys = ${keys}`); } /** * Get the number of keys stored in this KeySet. */ get size() { const nodeKeysCount = this._nodeKeys.size; let instanceIdsCount = 0; for (const set of this._instanceKeys.values()) { instanceIdsCount += set.size; } return nodeKeysCount + instanceIdsCount; } /** * Is this KeySet currently empty. */ get isEmpty() { return 0 === this.size; } /** Iterate over all keys in this keyset. */ some(callback) { for (const entry of this._instanceKeys) { const className = this._lowerCaseMap.get(entry["0"].toLowerCase()); if (some(entry[1], (id) => callback({ className, id }))) { return true; } } return some(this._nodeKeys, (serializedKey) => callback(JSON.parse(serializedKey))); } /** Iterate over all keys in this keyset. */ forEach(callback) { let index = 0; this._instanceKeys.forEach((ids, className) => { const recentClassName = this._lowerCaseMap.get(className.toLowerCase()); ids.forEach((id) => callback({ className: recentClassName, id }, index++)); }); this._nodeKeys.forEach((serializedKey) => { callback(JSON.parse(serializedKey), index++); }); } /** Iterate over all keys in this keyset in batches */ forEachBatch(batchSize, callback) { const size = this.size; const count = Math.ceil(size / batchSize); if (1 === count) { callback(this, 0); return; } let batch = new KeySet(); let batchIndex = 0; let currBatchSize = 0; this.forEach((key, index) => { batch.add(key); ++currBatchSize; if (currBatchSize === batchSize || index === size - 1) { callback(batch, batchIndex++); batch = new KeySet(); currBatchSize = 0; } }); } /** * Serializes this KeySet to JSON * @public */ toJSON() { const instanceKeys = []; for (const entry of this._instanceKeys.entries()) { if (entry["1"].size > 0) { const className = this._lowerCaseMap.get(entry["0"].toLowerCase()); const compressedIds = core_bentley_1.CompressedId64Set.sortAndCompress(entry["1"]); instanceKeys.push([className, compressedIds.length > 0 ? compressedIds : core_bentley_1.Id64.invalid]); } } const nodeKeys = []; for (const serializedKey of this._nodeKeys.values()) { nodeKeys.push(JSON.parse(serializedKey)); } return { instanceKeys, nodeKeys, }; } /** * Creates a KeySet from JSON * @public */ static fromJSON(json) { const keyset = new KeySet(); keyset.addKeySetJSON(json); return keyset; } } exports.KeySet = KeySet; function normalizeClassName(className) { return className.replace(".", ":"); } const some = (set, cb) => { for (const item of set) { if (cb(item)) { return true; } } return false; }; //# sourceMappingURL=KeySet.js.map