UNPKG

@itwin/ecschema-metadata

Version:

ECObjects core concepts in typescript

450 lines • 21.6 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 Metadata */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MutableRelationshipClass = exports.RelationshipMultiplicity = exports.MutableRelationshipConstraint = exports.RelationshipConstraint = exports.RelationshipClass = void 0; const DelayedPromise_1 = require("../DelayedPromise"); const Helper_1 = require("../Deserialization/Helper"); const XmlSerializationUtils_1 = require("../Deserialization/XmlSerializationUtils"); const ECObjects_1 = require("../ECObjects"); const Exception_1 = require("../Exception"); const Class_1 = require("./Class"); const CustomAttribute_1 = require("./CustomAttribute"); const EntityClass_1 = require("./EntityClass"); const Mixin_1 = require("./Mixin"); const SchemaItem_1 = require("./SchemaItem"); /** * A Typescript class representation of a ECRelationshipClass. * @public @preview */ class RelationshipClass extends Class_1.ECClass { schemaItemType = RelationshipClass.schemaItemType; /** @internal */ static get schemaItemType() { return ECObjects_1.SchemaItemType.RelationshipClass; } /** @internal */ _strength; /** @internal */ _strengthDirection; /** @internal */ _source; /** @internal */ _target; /** @internal */ constructor(schema, name, modifier) { super(schema, name, modifier); this._strengthDirection = ECObjects_1.StrengthDirection.Forward; this._strength = ECObjects_1.StrengthType.Referencing; this._source = new RelationshipConstraint(this, ECObjects_1.RelationshipEnd.Source); this._target = new RelationshipConstraint(this, ECObjects_1.RelationshipEnd.Target); } get strength() { return this._strength; } get strengthDirection() { return this._strengthDirection; } get source() { return this._source; } get target() { return this._target; } /** * * @param name * @param relationship * @param direction * @internal */ async createNavigationProperty(name, relationship, direction) { return this.addProperty(await (0, EntityClass_1.createNavigationProperty)(this, name, relationship, direction)); } /** @internal */ createNavigationPropertySync(name, relationship, direction) { return this.addProperty((0, EntityClass_1.createNavigationPropertySync)(this, name, relationship, direction)); } /** * @internal Used for schema editing. */ setStrength(strength) { this._strength = strength; } /** * @internal Used for schema editing. */ setStrengthDirection(direction) { this._strengthDirection = direction; } /** * @internal Used for schema editing. */ setSourceConstraint(source) { this._source = source; } /** * @internal Used for schema editing. */ setTargetConstraint(target) { this._target = target; } /** * Save this RelationshipClass's 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); schemaJson.strength = (0, ECObjects_1.strengthToString)(this.strength); schemaJson.strengthDirection = (0, ECObjects_1.strengthDirectionToString)(this.strengthDirection); schemaJson.source = this.source.toJSON(); schemaJson.target = this.target.toJSON(); return schemaJson; } /** @internal */ async toXml(schemaXml) { const itemElement = await super.toXml(schemaXml); itemElement.setAttribute("strength", (0, ECObjects_1.strengthToString)(this.strength)); itemElement.setAttribute("strengthDirection", (0, ECObjects_1.strengthDirectionToString)(this.strengthDirection)); itemElement.appendChild(await this.source.toXml(schemaXml)); itemElement.appendChild(await this.target.toXml(schemaXml)); return itemElement; } fromJSONSync(relationshipClassProps) { super.fromJSONSync(relationshipClassProps); let strength = (0, ECObjects_1.parseStrength)(relationshipClassProps.strength); if (undefined === strength) { if (Helper_1.SchemaReadHelper.isECSpecVersionNewer({ readVersion: relationshipClassProps.originalECSpecMajorVersion, writeVersion: relationshipClassProps.originalECSpecMinorVersion })) strength = ECObjects_1.StrengthType.Referencing; else throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidStrength, `The RelationshipClass ${this.fullName} has an invalid 'strength' attribute. '${relationshipClassProps.strength}' is not a valid StrengthType.`); } const strengthDirection = (0, ECObjects_1.parseStrengthDirection)(relationshipClassProps.strengthDirection); if (undefined === strengthDirection) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidStrength, `The RelationshipClass ${this.fullName} has an invalid 'strengthDirection' attribute. '${relationshipClassProps.strengthDirection}' is not a valid StrengthDirection.`); this._strength = strength; this._strengthDirection = strengthDirection; } async fromJSON(relationshipClassProps) { this.fromJSONSync(relationshipClassProps); } /** * Type guard to check if the SchemaItem is of type RelationshipClass. * @param item The SchemaItem to check. * @returns True if the item is a RelationshipClass, false otherwise. */ static isRelationshipClass(item) { if (item && item.schemaItemType === ECObjects_1.SchemaItemType.RelationshipClass) return true; return false; } /** * Type assertion to check if the SchemaItem is of type RelationshipClass. * @param item The SchemaItem to check. * @returns The item cast to RelationshipClass if it is a RelationshipClass, undefined otherwise. * @internal */ static assertIsRelationshipClass(item) { if (!this.isRelationshipClass(item)) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaItemType, `Expected '${ECObjects_1.SchemaItemType.RelationshipClass}' (RelationshipClass)`); } } exports.RelationshipClass = RelationshipClass; /** * A Typescript class representation of a ECRelationshipConstraint. * @public @preview */ class RelationshipConstraint { _abstractConstraint; _relationshipClass; _relationshipEnd; _multiplicity; _polymorphic; _roleLabel; _constraintClasses; _customAttributes; /** @internal */ constructor(relClass, relEnd, roleLabel, polymorphic) { this._relationshipEnd = relEnd; if (polymorphic) this._polymorphic = polymorphic; else this._polymorphic = false; this._multiplicity = RelationshipMultiplicity.zeroOne; this._relationshipClass = relClass; this._roleLabel = roleLabel; } get multiplicity() { return this._multiplicity ?? RelationshipMultiplicity.zeroOne; } get polymorphic() { return this._polymorphic ?? false; } get roleLabel() { return this._roleLabel; } get constraintClasses() { return this._constraintClasses; } get relationshipClass() { return this._relationshipClass; } get relationshipEnd() { return this._relationshipEnd; } get customAttributes() { return this._customAttributes; } /** Returns the constraint name, ie. 'RelationshipName.Source/Target' */ get fullName() { return `${this._relationshipClass.name}:${this.isSource ? "Source" : "Target"}`; } /** Returns the schema of the RelationshipClass. */ get schema() { return this._relationshipClass.schema; } get abstractConstraint() { if (this._abstractConstraint) return this._abstractConstraint; if (this.constraintClasses && this.constraintClasses.length === 1) return this.constraintClasses[0]; return this._abstractConstraint; } /** * True if this RelationshipConstraint is the Source relationship end. */ get isSource() { return this.relationshipEnd === ECObjects_1.RelationshipEnd.Source; } /** * Adds the provided class as a constraint class to this constraint. * @param constraint The class to add as a constraint class. * @internal */ addClass(constraint) { // TODO: Ensure we don't start mixing constraint class types // TODO: Check that this class is or subclasses abstract constraint? if (!this._constraintClasses) this._constraintClasses = []; // TODO: Handle relationship constraints this._constraintClasses.push(constraint); } /** * Removes the provided class as a constraint class from this constraint. * @param constraint The class to add as a constraint class. * * @internal */ removeClass(constraint) { if (undefined === this._constraintClasses) return; this._constraintClasses.forEach((item, index) => { const constraintName = item.fullName; if (constraintName === constraint.fullName) // eslint-disable-next-line @typescript-eslint/no-floating-promises this._constraintClasses?.splice(index, 1); }); } /** * Save this RelationshipConstraint's properties to an object for serializing to JSON. */ toJSON() { const schemaJson = {}; schemaJson.multiplicity = this.multiplicity.toString(); schemaJson.roleLabel = this.roleLabel; schemaJson.polymorphic = this.polymorphic; if (undefined !== this._abstractConstraint) schemaJson.abstractConstraint = this._abstractConstraint.fullName; if (undefined !== this.constraintClasses && this.constraintClasses.length > 0) schemaJson.constraintClasses = this.constraintClasses.map((constraintClass) => constraintClass.fullName); const customAttributes = (0, CustomAttribute_1.serializeCustomAttributes)(this.customAttributes); if (undefined !== customAttributes) schemaJson.customAttributes = customAttributes; return schemaJson; } /** @internal */ async toXml(schemaXml) { const elementName = this.isSource ? "Source" : "Target"; const itemElement = schemaXml.createElement(elementName); if (undefined !== this.polymorphic) itemElement.setAttribute("polymorphic", this.polymorphic.toString()); if (undefined !== this.roleLabel) itemElement.setAttribute("roleLabel", this.roleLabel); if (undefined !== this.multiplicity) itemElement.setAttribute("multiplicity", this.multiplicity.toString()); const abstractConstraint = await this.abstractConstraint; if (undefined !== abstractConstraint) { const abstractConstraintName = XmlSerializationUtils_1.XmlSerializationUtils.createXmlTypedName(this.schema, abstractConstraint.schema, abstractConstraint.name); itemElement.setAttribute("abstractConstraint", abstractConstraintName); } if (undefined !== this.constraintClasses) { for (const item of this.constraintClasses) { const constraintClass = await item; const classElement = schemaXml.createElement("Class"); const constraintClassName = XmlSerializationUtils_1.XmlSerializationUtils.createXmlTypedName(this.schema, constraintClass.schema, constraintClass.name); classElement.setAttribute("class", constraintClassName); itemElement.appendChild(classElement); } } if (this._customAttributes) { const caContainerElement = schemaXml.createElement("ECCustomAttributes"); for (const [name, attribute] of this._customAttributes) { const caElement = await XmlSerializationUtils_1.XmlSerializationUtils.writeCustomAttribute(name, attribute, schemaXml, this.schema); caContainerElement.appendChild(caElement); } itemElement.appendChild(caContainerElement); } return itemElement; } fromJSONSync(relationshipConstraintProps) { this._roleLabel = relationshipConstraintProps.roleLabel; this._polymorphic = relationshipConstraintProps.polymorphic; const parsedMultiplicity = RelationshipMultiplicity.fromString(relationshipConstraintProps.multiplicity); if (!parsedMultiplicity) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidMultiplicity, ``); this._multiplicity = parsedMultiplicity; const relClassSchema = this.relationshipClass.schema; if (undefined !== relationshipConstraintProps.abstractConstraint) { const abstractConstraintSchemaItemKey = relClassSchema.getSchemaItemKey(relationshipConstraintProps.abstractConstraint); if (!abstractConstraintSchemaItemKey) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate the abstractConstraint ${relationshipConstraintProps.abstractConstraint}.`); this.setAbstractConstraint(new DelayedPromise_1.DelayedPromiseWithProps(abstractConstraintSchemaItemKey, async () => { const tempAbstractConstraint = await relClassSchema.lookupItem(relationshipConstraintProps.abstractConstraint); if (undefined === tempAbstractConstraint || (!EntityClass_1.EntityClass.isEntityClass(tempAbstractConstraint) && !Mixin_1.Mixin.isMixin(tempAbstractConstraint) && !RelationshipClass.isRelationshipClass(tempAbstractConstraint))) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate the abstractConstraint ${relationshipConstraintProps.abstractConstraint}.`); return tempAbstractConstraint; })); } const loadEachConstraint = (constraintClassName) => { const tempConstraintClass = relClassSchema.lookupItemSync(constraintClassName); if (!tempConstraintClass || (!EntityClass_1.EntityClass.isEntityClass(tempConstraintClass) && !Mixin_1.Mixin.isMixin(tempConstraintClass) && !RelationshipClass.isRelationshipClass(tempConstraintClass))) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, ``); return tempConstraintClass; }; for (const constraintClassName of relationshipConstraintProps.constraintClasses) { const constraintClass = loadEachConstraint(constraintClassName); this.addClass(new DelayedPromise_1.DelayedPromiseWithProps(constraintClass.key, async () => constraintClass)); } } async fromJSON(relationshipConstraintProps) { this.fromJSONSync(relationshipConstraintProps); } /** * Indicates if the provided [[ECClass]] is supported by this [[RelationshipConstraint]]. * @param ecClass The class to check. */ async supportsClass(ecClass) { if (!this.constraintClasses) { if (this.relationshipClass.baseClass) { const baseRelationship = await this.relationshipClass.baseClass; const baseConstraint = this.isSource ? baseRelationship.source : baseRelationship.target; return baseConstraint.supportsClass(ecClass); } return false; } if (ecClass.schemaItemType !== ECObjects_1.SchemaItemType.EntityClass && ecClass.schemaItemType !== ECObjects_1.SchemaItemType.RelationshipClass && ecClass.schemaItemType !== ECObjects_1.SchemaItemType.Mixin) { return false; } const abstractConstraint = await this.abstractConstraint; if (abstractConstraint && await RelationshipConstraint.classCompatibleWithConstraint(abstractConstraint, ecClass, this.polymorphic || false)) return true; for (const constraint of this.constraintClasses) { if (await RelationshipConstraint.classCompatibleWithConstraint(await constraint, ecClass, this.polymorphic || false)) return true; } return false; } /** * Indicates if an ECClass is of the type or applies to the type (if a mixin) of the ECClass specified by the constraintClass parameter. * @param constraintClass The ECClass that is a constraint class of a relationship. * @param testClass The ECClass to check against the constraint class. * @param isPolymorphic Indicates if the testClass should be checked polymorphically. */ static async classCompatibleWithConstraint(constraintClass, testClass, isPolymorphic) { if (SchemaItem_1.SchemaItem.equalByKey(constraintClass, testClass)) return true; if (isPolymorphic) { if (testClass.schemaItemType === ECObjects_1.SchemaItemType.EntityClass || testClass.schemaItemType === ECObjects_1.SchemaItemType.RelationshipClass) { return testClass.is(constraintClass); } if (testClass.schemaItemType === ECObjects_1.SchemaItemType.Mixin) { if (constraintClass.schemaItemType === ECObjects_1.SchemaItemType.EntityClass) return testClass.applicableTo(constraintClass); else return testClass.is(constraintClass); } } return false; } /** * @internal */ static isRelationshipConstraint(object) { const relationshipConstraint = object; return relationshipConstraint !== undefined && relationshipConstraint.polymorphic !== undefined && relationshipConstraint.multiplicity !== undefined && relationshipConstraint.relationshipEnd !== undefined && relationshipConstraint._relationshipClass !== undefined; } /** @internal */ addCustomAttribute(customAttribute) { if (!this._customAttributes) this._customAttributes = new Map(); this._customAttributes.set(customAttribute.className, customAttribute); } /** @internal */ setRoleLabel(roleLabel) { this._roleLabel = roleLabel; } /** @internal */ setRelationshipEnd(relationshipEnd) { this._relationshipEnd = relationshipEnd; } /** @internal */ setPolymorphic(polymorphic) { this._polymorphic = polymorphic; } /** @internal */ setMultiplicity(multiplicity) { this._multiplicity = multiplicity; } /** @internal */ setAbstractConstraint(abstractConstraint) { this._abstractConstraint = abstractConstraint; } } exports.RelationshipConstraint = RelationshipConstraint; /** * Hackish approach that works like a "friend class" so we can access protected members without making them public. * @internal */ class MutableRelationshipConstraint extends RelationshipConstraint { } exports.MutableRelationshipConstraint = MutableRelationshipConstraint; const INT32_MAX = 2147483647; /** * @public @preview */ class RelationshipMultiplicity { static zeroOne = new RelationshipMultiplicity(0, 1); static zeroMany = new RelationshipMultiplicity(0, INT32_MAX); static oneOne = new RelationshipMultiplicity(1, 1); static oneMany = new RelationshipMultiplicity(1, INT32_MAX); lowerLimit; upperLimit; /** @internal */ constructor(lowerLimit, upperLimit) { this.lowerLimit = lowerLimit; this.upperLimit = upperLimit; } static fromString(str) { const matches = /^\(([0-9]*)\.\.([0-9]*|\*)\)$/.exec(str); if (matches === null || matches.length !== 3) return undefined; const lowerLimit = parseInt(matches[1], 10); const upperLimit = matches[2] === "*" ? INT32_MAX : parseInt(matches[2], 10); if (0 === lowerLimit && 1 === upperLimit) return RelationshipMultiplicity.zeroOne; else if (0 === lowerLimit && INT32_MAX === upperLimit) return RelationshipMultiplicity.zeroMany; else if (1 === lowerLimit && 1 === upperLimit) return RelationshipMultiplicity.oneOne; else if (1 === lowerLimit && INT32_MAX === upperLimit) return RelationshipMultiplicity.oneMany; return new RelationshipMultiplicity(lowerLimit, upperLimit); } equals(rhs) { return this.lowerLimit === rhs.lowerLimit && this.upperLimit === rhs.upperLimit; } toString() { return `(${this.lowerLimit}..${this.upperLimit === INT32_MAX ? "*" : this.upperLimit})`; } } exports.RelationshipMultiplicity = RelationshipMultiplicity; /** * @internal * An abstract class used for schema editing. */ class MutableRelationshipClass extends RelationshipClass { get source() { return this._source; } get target() { return this._target; } } exports.MutableRelationshipClass = MutableRelationshipClass; //# sourceMappingURL=RelationshipClass.js.map