UNPKG

@itwin/ecschema-metadata

Version:

ECObjects core concepts in typescript

284 lines • 11.9 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Metadata */ import { DelayedPromiseWithProps } from "../DelayedPromise"; import { XmlSerializationUtils } from "../Deserialization/XmlSerializationUtils"; import { parseStrengthDirection, SchemaItemType } from "../ECObjects"; import { ECSchemaError, ECSchemaStatus } from "../Exception"; import { ECClass } from "./Class"; import { Mixin } from "./Mixin"; import { NavigationProperty } from "./Property"; import { RelationshipClass } from "./RelationshipClass"; /** * A Typescript class representation of an ECEntityClass. * @public @preview */ export class EntityClass extends ECClass { schemaItemType = EntityClass.schemaItemType; /** @internal */ static get schemaItemType() { return SchemaItemType.EntityClass; } _mixins; get mixins() { if (!this._mixins) return []; return this._mixins; } *getMixinsSync() { if (!this._mixins) return; for (const mixin of this._mixins) { const mObj = this.schema.lookupItemSync(mixin, Mixin); if (mObj) { yield mObj; } } } /** * * @param mixin * @internal */ addMixin(mixin) { if (!this._mixins) this._mixins = []; this._mixins.push(new DelayedPromiseWithProps(mixin.key, async () => mixin)); return; } /** * Searches the base class, if one exists, first then any mixins that exist for the property with the name provided. * @param name The name of the property to find. */ async getInheritedProperty(name) { let inheritedProperty = await super.getInheritedProperty(name); if (!inheritedProperty && this._mixins) { const mixinProps = await Promise.all(this._mixins.map(async (mixin) => (await mixin).getProperty(name))); mixinProps.some((prop) => { inheritedProperty = prop; return inheritedProperty !== undefined; }); } return inheritedProperty; } /** * Searches the base class, if one exists, first then any mixins that exist for the property with the name provided. * @param name The name of the property to find. */ getInheritedPropertySync(name) { const inheritedProperty = super.getInheritedPropertySync(name); if (inheritedProperty) return inheritedProperty; if (!this._mixins) { return undefined; } for (const mixin of this._mixins) { const mObj = this.schema.lookupItemSync(mixin); if (mObj && ECClass.isECClass(mObj)) { const result = mObj.getPropertySync(name, true); if (result) { return result; } } } return undefined; } /** * * @param cache * @returns * * @internal */ async buildPropertyCache() { const cache = new Map(); const baseClass = await this.baseClass; if (baseClass) { for (const property of await baseClass.getProperties()) { if (!cache.has(property.name.toUpperCase())) cache.set(property.name.toUpperCase(), property); } } for (const mixin of this.mixins) { const mixinObj = await mixin; const mixinProps = mixinObj.getPropertiesSync(); for (const property of mixinProps) { if (!cache.has(property.name.toUpperCase())) cache.set(property.name.toUpperCase(), property); } } const localProps = await this.getProperties(true); if (localProps) { for (const property of localProps) { cache.set(property.name.toUpperCase(), property); } } return cache; } /** * * @param cache * @internal */ buildPropertyCacheSync() { const cache = new Map(); const baseClass = this.getBaseClassSync(); if (baseClass) { Array.from(baseClass.getPropertiesSync()).forEach((property) => { if (!cache.has(property.name.toUpperCase())) cache.set(property.name.toUpperCase(), property); }); } for (const mixin of this.getMixinsSync()) { const mixinProps = mixin.getPropertiesSync(); for (const property of mixinProps) { if (!cache.has(property.name.toUpperCase())) cache.set(property.name.toUpperCase(), property); } } const localProps = this.getPropertiesSync(true); if (localProps) { Array.from(localProps).forEach(property => { cache.set(property.name.toUpperCase(), property); }); } return cache; } /** * * @param name * @param relationship * @param direction * @internal */ async createNavigationProperty(name, relationship, direction) { return this.addProperty(await createNavigationProperty(this, name, relationship, direction)); } /** * * @param name * @param relationship * @param direction * @internal */ createNavigationPropertySync(name, relationship, direction) { return this.addProperty(createNavigationPropertySync(this, name, relationship, direction)); } /** * Save this EntityClass' properties to an object for serializing to JSON. * @param standalone Serialization includes only this object (as opposed to the full schema). * @param includeSchemaVersion Include the Schema's version information in the serialized object. */ toJSON(standalone = false, includeSchemaVersion = false) { const schemaJson = super.toJSON(standalone, includeSchemaVersion); if (this.mixins.length > 0) schemaJson.mixins = this.mixins.map((mixin) => mixin.fullName); return schemaJson; } /** @internal */ async toXml(schemaXml) { const itemElement = await super.toXml(schemaXml); for (const lazyMixin of this.mixins) { const mixin = await lazyMixin; const mixinElement = schemaXml.createElement("BaseClass"); const mixinName = XmlSerializationUtils.createXmlTypedName(this.schema, mixin.schema, mixin.name); mixinElement.textContent = mixinName; itemElement.appendChild(mixinElement); } return itemElement; } async fromJSON(entityClassProps) { this.fromJSONSync(entityClassProps); } fromJSONSync(entityClassProps) { super.fromJSONSync(entityClassProps); if (undefined !== entityClassProps.mixins) { if (!this._mixins) this._mixins = []; for (const name of entityClassProps.mixins) { const mixinSchemaItemKey = this.schema.getSchemaItemKey(name); if (!mixinSchemaItemKey) throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The ECEntityClass ${this.name} has a mixin ("${name}") that cannot be found.`); if (!this._mixins.find((value) => mixinSchemaItemKey.matchesFullName(value.fullName))) { this._mixins.push(new DelayedPromiseWithProps(mixinSchemaItemKey, async () => { const mixin = await this.schema.lookupItem(mixinSchemaItemKey, Mixin); if (undefined === mixin) throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The ECEntityClass ${this.name} has a mixin ("${name}") that cannot be found.`); return mixin; })); } } } } /** * Type guard to check if the SchemaItem is of type EntityClass. * @param item The SchemaItem to check. * @returns True if the item is an EntityClass, false otherwise. */ static isEntityClass(item) { if (item && item.schemaItemType === SchemaItemType.EntityClass) return true; return false; } /** * Type assertion to check if the SchemaItem is of type EntityClass. * @param item The SchemaItem to check. * @returns The item cast to EntityClass if it is an EntityClass, undefined otherwise. * @internal */ static assertIsEntityClass(item) { if (!this.isEntityClass(item)) throw new ECSchemaError(ECSchemaStatus.InvalidSchemaItemType, `Expected '${SchemaItemType.EntityClass}' (EntityClass)`); } } /** * Hackish approach that works like a "friend class" so we can access protected members without making them public. * @internal */ export class MutableEntityClass extends EntityClass { } /** @internal */ export async function createNavigationProperty(ecClass, name, relationship, direction) { if (await ecClass.getProperty(name, true)) throw new ECSchemaError(ECSchemaStatus.DuplicateProperty, `An ECProperty with the name ${name} already exists in the class ${ecClass.name}.`); let resolvedRelationship; if (typeof (relationship) === "string") { resolvedRelationship = await ecClass.schema.lookupItem(relationship, RelationshipClass); } else resolvedRelationship = relationship; if (!resolvedRelationship) throw new ECSchemaError(ECSchemaStatus.InvalidType, `The provided RelationshipClass, ${relationship}, is not a valid RelationshipClassInterface.`); // eslint-disable-line @typescript-eslint/no-base-to-string if (typeof (direction) === "string") { const tmpDirection = parseStrengthDirection(direction); if (undefined === tmpDirection) throw new ECSchemaError(ECSchemaStatus.InvalidStrengthDirection, `The provided StrengthDirection, ${direction}, is not a valid StrengthDirection.`); direction = tmpDirection; } const lazyRelationship = new DelayedPromiseWithProps(resolvedRelationship.key, async () => resolvedRelationship); return new NavigationProperty(ecClass, name, lazyRelationship, direction); } /** @internal */ export function createNavigationPropertySync(ecClass, name, relationship, direction) { if (ecClass.getPropertySync(name, true)) throw new ECSchemaError(ECSchemaStatus.DuplicateProperty, `An ECProperty with the name ${name} already exists in the class ${ecClass.name}.`); let resolvedRelationship; if (typeof (relationship) === "string") { resolvedRelationship = ecClass.schema.lookupItemSync(relationship, RelationshipClass); } else resolvedRelationship = relationship; if (!resolvedRelationship) throw new ECSchemaError(ECSchemaStatus.InvalidType, `The provided RelationshipClass, ${relationship}, is not a valid RelationshipClassInterface.`); // eslint-disable-line @typescript-eslint/no-base-to-string if (typeof (direction) === "string") { const tmpDirection = parseStrengthDirection(direction); if (undefined === tmpDirection) throw new ECSchemaError(ECSchemaStatus.InvalidStrengthDirection, `The provided StrengthDirection, ${direction}, is not a valid StrengthDirection.`); direction = tmpDirection; } const lazyRelationship = new DelayedPromiseWithProps(resolvedRelationship.key, async () => resolvedRelationship); return new NavigationProperty(ecClass, name, lazyRelationship, direction); } //# sourceMappingURL=EntityClass.js.map