@itwin/presentation-common
Version:
Common pieces for iModel.js presentation packages
458 lines • 16.2 kB
JavaScript
"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");
/** @public */
// eslint-disable-next-line @typescript-eslint/no-redeclare
var Key;
(function (Key) {
/**
* Check if the supplied key is a `NodeKey`
*
* @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md)
* package for creating hierarchies.
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
function isNodeKey(key) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
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.
*
* @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md)
* package for creating hierarchies.
*/
get nodeKeys() {
// eslint-disable-next-line @typescript-eslint/no-deprecated
const set = new Set();
for (const serialized of this._nodeKeys) {
set.add(JSON.parse(serialized));
}
return set;
}
/**
* Get node keys count
*
* @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md)
* package for creating hierarchies.
*/
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) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
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) {
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);
// eslint-disable-next-line @typescript-eslint/no-deprecated
}
else if (Key.isNodeKey(value)) {
this._nodeKeys.add(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) {
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);
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
}
else if (Key.isNodeKey(value)) {
this._nodeKeys.delete(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 (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));
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
(0, core_bentley_1.assert)(Key.isNodeKey(value));
return this._nodeKeys.has(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 (this.isKeySet(keys)) {
return this.hasKeySet(keys, "all");
}
(0, core_bentley_1.assert)(this.isKeysArray(keys));
return this.hasKeysArray(keys, "all");
}
/**
* Check if this KeySet contains any of the specified keys.
* @param keys The keys to check.
*/
hasAny(keys) {
if (this.isKeySet(keys)) {
return this.hasKeySet(keys, "any");
}
(0, core_bentley_1.assert)(this.isKeysArray(keys));
return this.hasKeysArray(keys, "any");
}
/**
* 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. */
// eslint-disable-next-line @typescript-eslint/no-deprecated
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]);
}
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
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