diff --git a/change/@itwin-imodel-transformer-29a95bab-526a-4bfb-805c-b895d6666c8a.json b/change/@itwin-imodel-transformer-29a95bab-526a-4bfb-805c-b895d6666c8a.json new file mode 100644 index 00000000..ccdb3907 --- /dev/null +++ b/change/@itwin-imodel-transformer-29a95bab-526a-4bfb-805c-b895d6666c8a.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fixed BigMap class to implement Map (instead of extending it). Added missing method implementations.", + "packageName": "@itwin/imodel-transformer", + "email": "Julija.Ramoskiene@bentley.com", + "dependentChangeType": "patch" +} diff --git a/packages/transformer/src/BigMap.ts b/packages/transformer/src/BigMap.ts index 6554ed0a..d311f779 100644 --- a/packages/transformer/src/BigMap.ts +++ b/packages/transformer/src/BigMap.ts @@ -14,16 +14,15 @@ import { Id64String } from "@itwin/core-bentley"; * replace this stopgap with a more robust solution, utilizing a temporary SQLite database (https://github.com/iTwin/imodel-transformer/issues/83). * @internal */ -export class BigMap extends Map { +export class BigMap implements Map { private _maps: Record>; private _size: number; - public override get size(): number { + public get size(): number { return this._size; } public constructor() { - super(); this._maps = { 0: new Map(), 1: new Map(), @@ -45,12 +44,12 @@ export class BigMap extends Map { this._size = 0; } - public override clear(): void { + public clear(): void { Object.values(this._maps).forEach((m) => m.clear()); this._size = 0; } - public override delete(key: Id64String): boolean { + public delete(key: Id64String): boolean { const wasDeleted = this._maps[key[key.length - 1]].delete(key); if (wasDeleted) { this._size--; @@ -59,21 +58,21 @@ export class BigMap extends Map { return wasDeleted; } - public override forEach(callbackfn: (value: V, key: Id64String, map: Map) => void, thisArg?: any): void { + public forEach(callbackfn: (value: V, key: Id64String, map: Map) => void, thisArg?: any): void { Object.values(this._maps).forEach((m) => { m.forEach(callbackfn, thisArg); }); } - public override get(key: Id64String): V | undefined { + public get(key: Id64String): V | undefined { return this._maps[key[key.length - 1]].get(key); } - public override has(key: Id64String): boolean { + public has(key: Id64String): boolean { return this._maps[key[key.length - 1]].has(key); } - public override set(key: Id64String, value: V): this { + public set(key: Id64String, value: V): this { const mapForKey = this._maps[key[key.length - 1]]; if (mapForKey === undefined) throw Error(`Tried to set ${key}, but that key has no submap`); @@ -83,4 +82,36 @@ export class BigMap extends Map { this._size += (afterSize - beforeSize); return this; } + + public [Symbol.iterator](): IterableIterator<[Id64String, V]>{ + return this.entries(); + } + + public get [Symbol.toStringTag]() { + return "BigMap"; + } + + public *entries(): IterableIterator<[Id64String, V]> { + const maps = Object.values(this._maps); + for (const map of maps) { + for (const [key, value] of map.entries()) + yield [key, value]; + } + } + + public *keys(): IterableIterator { + const maps = Object.values(this._maps); + for (const map of maps) { + for (const key of map.keys()) + yield key; + } + } + + public *values(): IterableIterator { + const maps = Object.values(this._maps); + for (const map of maps) { + for (const value of map.values()) + yield value; + } + } } diff --git a/packages/transformer/src/test/standalone/BigMap.test.ts b/packages/transformer/src/test/standalone/BigMap.test.ts new file mode 100644 index 00000000..b46f2ca5 --- /dev/null +++ b/packages/transformer/src/test/standalone/BigMap.test.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ + +import { BigMap } from "../../BigMap"; +import { assert } from "chai"; + +describe("BigMap", function () { + + // Test map keys will be assigned into 2 different submaps when BigMap is created + const testMap = new Map ([["0x123f", "testVal1"], ["0x1231", "testVal2"]]); + + const createBigMap = (map: Map): BigMap => { + const bigMap = new BigMap (); + for (const entry of map.entries ()) { + bigMap.set(entry[0], entry[1]); + } + return bigMap; + }; + + describe("keys", function () { + it("should iterate all keys", async function () { + const bigMap = createBigMap (testMap); + assert.sameMembers([...bigMap.keys()], [...testMap.keys()]); + }); + }); + + describe("values", function () { + it("should get all values", async function () { + const bigMap = createBigMap (testMap); + assert.sameMembers([...bigMap.values()], [...testMap.values()]); + }); + }); + + describe("entries", function () { + it("should get all values", async function () { + const bigMap = createBigMap (testMap); + const actualMap = new Map([...bigMap.entries()]); + assert.deepEqual(actualMap, testMap); + }); + }); + + describe("iterator", function () { + it("should get all values", async function () { + const bigMap = createBigMap (testMap); + + const actualMap = new Map(); + for (const entry of bigMap) { + actualMap.set(entry[0], entry[1]); + } + assert.deepEqual(actualMap, testMap); + }); + }); + + describe("toStringTag", function () { + it("should return type name", async function () { + const typeName = Object.prototype.toString.call(new BigMap ()); + assert.equal(typeName, "[object BigMap]"); + }); + }); + + describe("has", function () { + it("should return true when value was set", async function () { + const bigMap = new BigMap (); + const key = "0x123f"; + bigMap.set(key, "12134"); + assert.isTrue (bigMap.has(key)); + }); + }); + + describe("set", function () { + it("should set when key has submap", async function () { + const bigMap = new BigMap (); + assert.doesNotThrow(() => bigMap.set("0x13", "12134")); + assert.equal(bigMap.size, 1); + }); + + it("should throw when key has no submap", async function () { + const bigMap = new BigMap (); + assert.throw(() => bigMap.set("g", "12134"), "Tried to set g, but that key has no submap"); + }); + }); +}); diff --git a/packages/transformer/src/test/standalone/IModelTransformer.test.ts b/packages/transformer/src/test/standalone/IModelTransformer.test.ts index 10913357..65341aba 100644 --- a/packages/transformer/src/test/standalone/IModelTransformer.test.ts +++ b/packages/transformer/src/test/standalone/IModelTransformer.test.ts @@ -19,7 +19,7 @@ import { import * as coreBackendPkgJson from "@itwin/core-backend/package.json"; import * as ECSchemaMetaData from "@itwin/ecschema-metadata"; import * as TestUtils from "../TestUtils"; -import { DbResult, Guid, Id64, Id64String, Logger, LogLevel, OpenMode } from "@itwin/core-bentley"; +import { DbResult, Guid, Id64, Id64String, Logger, LoggingMetaData, LogLevel, OpenMode } from "@itwin/core-bentley"; import { AxisAlignedBox3d, BriefcaseIdValue, Code, CodeScopeSpec, CodeSpec, ColorDef, CreateIModelProps, DefinitionElementProps, ElementAspectProps, ElementProps, ExternalSourceAspectProps, GeometricElement2dProps, ImageSourceFormat, IModel, IModelError, InformationPartitionElementProps, ModelProps, PhysicalElementProps, Placement3d, ProfileOptions, QueryRowFormat, RelatedElement, RelationshipProps, RepositoryLinkProps, @@ -71,6 +71,9 @@ describe("IModelTransformer", () => { if (!IModelJsFs.existsSync(outputDir)) { IModelJsFs.mkdirSync(outputDir); } + }); + + beforeEach(async () => { // initialize logging if (process.env.LOG_TRANSFORMER_IN_TESTS) { Logger.initializeToConsole(); @@ -642,6 +645,55 @@ describe("IModelTransformer", () => { iModelShared.close(); }); + it("should log unresolved references", async () => { + const iModelShared: SnapshotDb = IModelTransformerTestUtils.createSharedIModel(outputDir, ["A", "B"]); + const iModelA: SnapshotDb = IModelTransformerTestUtils.createTeamIModel(outputDir, "A", Point3d.create(0, 0, 0), ColorDef.green); + IModelTransformerTestUtils.assertTeamIModelContents(iModelA, "A"); + const iModelExporterA = new IModelExporter(iModelA); + + // Exclude element + const excludedId = iModelA.elements.queryElementIdByCode(Subject.createCode(iModelA, IModel.rootSubjectId, "Context")); + assert.isDefined (excludedId); + iModelExporterA.excludeElement(excludedId!); + + const subjectId: Id64String = IModelTransformerTestUtils.querySubjectId(iModelShared, "A"); + const transformerA2S = new IModelTransformer(iModelExporterA, iModelShared, { targetScopeElementId: subjectId }); + transformerA2S.context.remapElement(IModel.rootSubjectId, subjectId); + + // Configure logger to capture warning message about unresolved references + const messageStart = "The following elements were never fully resolved:\n"; + const messageEnd = "\nThis indicates that either some references were excluded from the transformation\nor the source has dangling references."; + + let unresolvedElementMessage: string | undefined; + const logWarning = (_category: string, message: string, _metaData: LoggingMetaData) => { + if (message.startsWith (messageStart)) { + unresolvedElementMessage = message; + } + }; + Logger.initialize(undefined, logWarning); + Logger.setLevelDefault(LogLevel.Warning); + + // Act + await transformerA2S.processAll(); + + // Collect expected ids + const result = iModelA.queryEntityIds({ from: "BisCore.Element", + where: "Model.Id = :rootId AND ECInstanceId NOT IN (:rootId, :excludedId)", + bindings: {rootId: IModel.rootSubjectId, excludedId } }); + const expectedIds = [...result].map ((x) => `e${x}`); + + // Collect actual ids + assert.isDefined(unresolvedElementMessage); + const actualIds = unresolvedElementMessage!.split(messageStart)[1].split(messageEnd)[0].split(","); + + // Assert + assert.sameMembers(actualIds, expectedIds); + + transformerA2S.dispose(); + iModelA.close(); + iModelShared.close(); + }); + it("should detect conflicting provenance scopes", async () => { const sourceDb1 = IModelTransformerTestUtils.createTeamIModel(outputDir, "S1", Point3d.create(0, 0, 0), ColorDef.green); const sourceDb2 = IModelTransformerTestUtils.createTeamIModel(outputDir, "S2", Point3d.create(0, 10, 0), ColorDef.blue);