@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
650 lines • 25.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 { parsePrimitiveType, PrimitiveType, primitiveTypeToString, SchemaItemType, StrengthDirection, strengthDirectionToString } from "../ECObjects";
import { ECSchemaError, ECSchemaStatus } from "../Exception";
import { PropertyType, propertyTypeToString, PropertyTypeUtils } from "../PropertyTypes";
import { ECName } from "../ECName";
import { serializeCustomAttributes } from "./CustomAttribute";
import { Enumeration } from "./Enumeration";
import { KindOfQuantity } from "./KindOfQuantity";
import { PropertyCategory } from "./PropertyCategory";
/**
* A common abstract class for all ECProperty types.
* @public @preview
*/
export class Property {
_name;
_class; // TODO: class seems to be unused?
_description;
_label;
_isReadOnly;
_priority;
_category;
_kindOfQuantity;
_customAttributes;
/** @internal */
_type;
/** @internal */
constructor(ecClass, name, type) {
this._class = ecClass;
this._name = new ECName(name);
this._type = type;
}
isArray() { return PropertyTypeUtils.isArray(this._type); }
isPrimitive() { return PropertyTypeUtils.isPrimitive(this._type); }
isStruct() { return PropertyTypeUtils.isStruct(this._type); }
isEnumeration() { return PropertyTypeUtils.isEnumeration(this._type); }
isNavigation() { return PropertyTypeUtils.isNavigation(this._type); }
get name() { return this._name.name; }
get class() { return this._class; }
get label() { return this._label; }
get description() { return this._description; }
get isReadOnly() { return this._isReadOnly || false; }
get priority() {
if (this._priority === undefined) {
const baseProperty = this.class.getInheritedPropertySync(this.name);
if (undefined !== baseProperty) {
return baseProperty.priority;
}
}
return this._priority || 0;
}
get category() {
if (this._category === undefined) {
const baseProperty = this.class.getInheritedPropertySync(this.name);
if (undefined !== baseProperty) {
return baseProperty.category;
}
}
return this._category;
}
get kindOfQuantity() {
if (this._kindOfQuantity === undefined) {
const baseProperty = this.class.getInheritedPropertySync(this.name);
if (undefined !== baseProperty) {
return baseProperty.kindOfQuantity;
}
}
return this._kindOfQuantity;
}
get propertyType() { return this._type; }
get customAttributes() { return this._customAttributes; }
/** Returns the name in the format 'ClassName.PropertyName'. */
get fullName() { return `${this._class.name}.${this.name}`; }
/** Returns the schema of the class holding the property. */
get schema() { return this._class.schema; }
getCategorySync() {
if (!this._category) {
const baseProperty = this.class.getInheritedPropertySync(this.name);
if (undefined !== baseProperty) {
return baseProperty.getCategorySync();
}
return undefined;
}
return this.class.schema.lookupItemSync(this._category, PropertyCategory);
}
getKindOfQuantitySync() {
if (!this._kindOfQuantity) {
const baseProperty = this.class.getInheritedPropertySync(this.name);
if (undefined !== baseProperty) {
return baseProperty.getKindOfQuantitySync();
}
return undefined;
}
return this.class.schema.lookupItemSync(this._kindOfQuantity, KindOfQuantity);
}
/**
* Save this Property's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = {};
schemaJson.name = this.name;
schemaJson.type = propertyTypeToString(this._type);
if (this.description !== undefined)
schemaJson.description = this.description;
if (this.label !== undefined)
schemaJson.label = this.label;
if (this._isReadOnly !== undefined)
schemaJson.isReadOnly = this._isReadOnly;
if (this._category !== undefined)
schemaJson.category = this._category.fullName; // needs to be fully qualified name
if (this._priority !== undefined)
schemaJson.priority = this._priority;
if (this._kindOfQuantity !== undefined)
schemaJson.kindOfQuantity = this._kindOfQuantity.fullName;
const customAttributes = serializeCustomAttributes(this.customAttributes);
if (customAttributes !== undefined)
schemaJson.customAttributes = customAttributes;
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const propType = `EC${propertyTypeToString(this._type)}`.replace("Primitive", "");
const itemElement = schemaXml.createElement(propType);
itemElement.setAttribute("propertyName", this.name);
if (undefined !== this.description)
itemElement.setAttribute("description", this.description);
if (undefined !== this.label)
itemElement.setAttribute("displayLabel", this.label);
if (undefined !== this.isReadOnly)
itemElement.setAttribute("readOnly", String(this.isReadOnly));
if (undefined !== this._category) {
const category = await this._category;
const categoryName = XmlSerializationUtils.createXmlTypedName(this.schema, category.schema, category.name);
itemElement.setAttribute("category", categoryName);
}
if (undefined !== this._priority)
itemElement.setAttribute("priority", this._priority.toString());
if (undefined !== this._kindOfQuantity) {
const kindOfQuantity = await this._kindOfQuantity;
const kindOfQuantityName = XmlSerializationUtils.createXmlTypedName(this.schema, kindOfQuantity.schema, kindOfQuantity.name);
itemElement.setAttribute("kindOfQuantity", kindOfQuantityName);
}
if (this._customAttributes) {
const caContainerElement = schemaXml.createElement("ECCustomAttributes");
for (const [name, attribute] of this._customAttributes) {
const caElement = await XmlSerializationUtils.writeCustomAttribute(name, attribute, schemaXml, this.schema);
caContainerElement.appendChild(caElement);
}
itemElement.appendChild(caContainerElement);
}
return itemElement;
}
fromJSONSync(propertyProps) {
if (undefined !== propertyProps.label) {
this._label = propertyProps.label;
}
if (undefined !== propertyProps.description) {
this._description = propertyProps.description;
}
if (undefined !== propertyProps.priority) {
this._priority = propertyProps.priority;
}
if (undefined !== propertyProps.isReadOnly) {
this._isReadOnly = propertyProps.isReadOnly;
}
if (undefined !== propertyProps.category) {
const propertyCategorySchemaItemKey = this.class.schema.getSchemaItemKey(propertyProps.category);
if (!propertyCategorySchemaItemKey)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The Property ${this.name} has a 'category' ("${propertyProps.category}") that cannot be found.`);
this._category = new DelayedPromiseWithProps(propertyCategorySchemaItemKey, async () => {
const category = await this.class.schema.lookupItem(propertyCategorySchemaItemKey, PropertyCategory);
if (undefined === category)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The Property ${this.name} has a 'category' ("${propertyProps.category}") that cannot be found.`);
return category;
});
}
if (undefined !== propertyProps.kindOfQuantity) {
const koqSchemaItemKey = this.class.schema.getSchemaItemKey(propertyProps.kindOfQuantity);
if (!koqSchemaItemKey)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The Property ${this.name} has a 'kindOfQuantity' ("${propertyProps.kindOfQuantity}") that cannot be found.`);
this._kindOfQuantity = new DelayedPromiseWithProps(koqSchemaItemKey, async () => {
const koq = await this.class.schema.lookupItem(koqSchemaItemKey, KindOfQuantity);
if (undefined === koq)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `The Property ${this.name} has a 'kindOfQuantity' ("${propertyProps.kindOfQuantity}") that cannot be found.`);
return koq;
});
}
}
async fromJSON(propertyProps) {
this.fromJSONSync(propertyProps);
}
/** @internal */
addCustomAttribute(customAttribute) {
if (!this._customAttributes)
this._customAttributes = new Map();
this._customAttributes.set(customAttribute.className, customAttribute);
}
/** @internal */
setName(name) {
this._name = name;
}
/**
* @internal Used in schema editing.
*/
setDescription(description) {
this._description = description;
}
/**
* @internal Used in schema editing.
*/
setLabel(label) {
this._label = label;
}
/**
* @internal Used in schema editing.
*/
setIsReadOnly(isReadOnly) {
this._isReadOnly = isReadOnly;
}
/**
* @internal Used in schema editing.
*/
setPriority(priority) {
this._priority = priority;
}
/**
* @internal Used in schema editing.
*/
setCategory(category) {
this._category = category;
}
/**
* @internal Used in schema editing.
*/
setKindOfQuantity(kindOfQuantity) {
this._kindOfQuantity = kindOfQuantity;
}
/**
* Retrieve all custom attributes in the current property and its base
* This is the async version of getCustomAttributesSync()
*/
async getCustomAttributes() {
return this.getCustomAttributesSync();
}
/**
* Retrieve all custom attributes in the current property and its base.
*/
getCustomAttributesSync() {
let customAttributes = this._customAttributes;
if (undefined === customAttributes) {
customAttributes = new Map();
}
const baseProperty = this.class.getInheritedPropertySync(this.name);
let baseCustomAttributes;
if (undefined !== baseProperty)
baseCustomAttributes = baseProperty.getCustomAttributesSync();
if (undefined !== baseCustomAttributes) {
customAttributes = new Map([...baseCustomAttributes, ...customAttributes]);
}
return customAttributes;
}
/**
* @internal
*/
static isProperty(object) {
const property = object;
return property !== undefined && property.class !== undefined && property.name !== undefined
&& property.propertyType !== undefined;
}
}
/** @public @preview */
export class PrimitiveOrEnumPropertyBase extends Property {
/** @internal */
_extendedTypeName;
/** @internal */
_minLength;
/** @internal */
_maxLength;
/** @internal */
_minValue;
/** @internal */
_maxValue;
get extendedTypeName() { return this._extendedTypeName; }
get minLength() { return this._minLength; }
get maxLength() { return this._maxLength; }
get minValue() { return this._minValue; }
get maxValue() { return this._maxValue; }
/** @internal */
constructor(ecClass, name, type) {
super(ecClass, name, type);
}
/**
* Save this PrimitiveOrEnumPropertyBase's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
if (this.extendedTypeName !== undefined)
schemaJson.extendedTypeName = this.extendedTypeName;
if (this._minLength !== undefined)
schemaJson.minLength = this.minLength;
if (this._maxLength !== undefined)
schemaJson.maxLength = this.maxLength;
if (this._minValue !== undefined)
schemaJson.minValue = this.minValue;
if (this._maxValue !== undefined)
schemaJson.maxValue = this.maxValue;
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
if (undefined !== this.extendedTypeName)
itemElement.setAttribute("extendedTypeName", this.extendedTypeName);
if (undefined !== this.minValue)
itemElement.setAttribute("minimumValue", this.minValue.toString());
if (undefined !== this.maxValue)
itemElement.setAttribute("maximumValue", this.maxValue.toString());
if (undefined !== this.minLength)
itemElement.setAttribute("minimumLength", this.minLength.toString());
if (undefined !== this.maxLength)
itemElement.setAttribute("maximumLength", this.maxLength.toString());
return itemElement;
}
fromJSONSync(propertyBaseProps) {
super.fromJSONSync(propertyBaseProps);
if (undefined !== propertyBaseProps.minLength) {
this._minLength = propertyBaseProps.minLength;
}
if (undefined !== propertyBaseProps.maxLength) {
this._maxLength = propertyBaseProps.maxLength;
}
if (undefined !== propertyBaseProps.minValue) {
this._minValue = propertyBaseProps.minValue;
}
if (undefined !== propertyBaseProps.maxValue) {
this._maxValue = propertyBaseProps.maxValue;
}
if (undefined !== propertyBaseProps.extendedTypeName) {
this._extendedTypeName = propertyBaseProps.extendedTypeName;
}
}
/**
* @internal Used in schema editing.
*/
setExtendedTypeName(extendedTypeName) {
this._extendedTypeName = extendedTypeName;
}
/**
* @internal Used in schema editing.
*/
setMinLength(minLength) {
this._minLength = minLength;
}
/**
* @internal Used in schema editing.
*/
setMaxLength(maxLength) {
this._maxLength = maxLength;
}
/**
* @internal Used in schema editing.
*/
setMinValue(minValue) {
this._minValue = minValue;
}
/**
* @internal Used in schema editing.
*/
setMaxValue(maxValue) {
this._maxValue = maxValue;
}
async fromJSON(propertyBaseProps) {
this.fromJSONSync(propertyBaseProps);
}
}
/** @public @preview */
export class PrimitiveProperty extends PrimitiveOrEnumPropertyBase {
get primitiveType() { return PropertyTypeUtils.getPrimitiveType(this.propertyType); }
/** @internal */
constructor(ecClass, name, primitiveType = PrimitiveType.Integer) {
super(ecClass, name, PropertyTypeUtils.fromPrimitiveType(primitiveType));
}
fromJSONSync(primitivePropertyProps) {
super.fromJSONSync(primitivePropertyProps);
if (undefined !== primitivePropertyProps.typeName) {
if (this.primitiveType !== parsePrimitiveType(primitivePropertyProps.typeName))
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, ``);
}
}
async fromJSON(primitivePropertyProps) {
this.fromJSONSync(primitivePropertyProps);
}
/**
* Save this PrimitiveProperty's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
schemaJson.typeName = primitiveTypeToString(this.primitiveType);
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
itemElement.setAttribute("typeName", primitiveTypeToString(this.primitiveType));
return itemElement;
}
}
/** @public @preview */
export class EnumerationProperty extends PrimitiveOrEnumPropertyBase {
/** @internal */
_enumeration;
get enumeration() { return this._enumeration; }
/**
* Save this EnumerationProperty's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
schemaJson.typeName = this.enumeration.fullName;
return schemaJson;
}
/** @internal */
constructor(ecClass, name, type) {
// TODO: Should we allow specifying the backing type?
super(ecClass, name, PropertyType.Integer_Enumeration);
this._enumeration = type;
}
fromJSONSync(enumerationPropertyProps) {
super.fromJSONSync(enumerationPropertyProps);
if (undefined !== enumerationPropertyProps.typeName) {
if (!(this.enumeration.fullName).match(enumerationPropertyProps.typeName)) // need to match {schema}.{version}.{itemName} on typeName
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, ``);
const enumSchemaItemKey = this.class.schema.getSchemaItemKey(this.enumeration.fullName);
if (!enumSchemaItemKey)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `Unable to locate the enumeration ${enumerationPropertyProps.typeName}.`);
this._enumeration = new DelayedPromiseWithProps(enumSchemaItemKey, async () => {
const enumeration = await this.class.schema.lookupItem(enumSchemaItemKey, Enumeration);
if (undefined === enumeration)
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `Unable to locate the enumeration ${enumerationPropertyProps.typeName}.`);
return enumeration;
});
}
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
const enumeration = await this.enumeration;
const enumerationName = XmlSerializationUtils.createXmlTypedName(this.schema, enumeration.schema, enumeration.name);
itemElement.setAttribute("typeName", enumerationName);
return itemElement;
}
async fromJSON(enumerationPropertyProps) {
this.fromJSONSync(enumerationPropertyProps);
}
}
/** @public @preview */
export class StructProperty extends Property {
/** @internal */
_structClass;
get structClass() { return this._structClass; }
/** @internal */
constructor(ecClass, name, type) {
super(ecClass, name, PropertyType.Struct);
this._structClass = type;
}
/**
* Save this StructProperty's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
schemaJson.typeName = this.structClass.fullName;
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
const structClassName = XmlSerializationUtils.createXmlTypedName(this.schema, this.structClass.schema, this.structClass.name);
itemElement.setAttribute("typeName", structClassName);
return itemElement;
}
fromJSONSync(structPropertyProps) {
super.fromJSONSync(structPropertyProps);
if (undefined !== structPropertyProps.typeName) {
if (!this.structClass.key.matchesFullName(structPropertyProps.typeName))
throw new ECSchemaError(ECSchemaStatus.InvalidECJson, ``);
}
}
async fromJSON(structPropertyProps) {
this.fromJSONSync(structPropertyProps);
}
}
/** @public @preview */
export class NavigationProperty extends Property {
/** @internal */
_relationshipClass;
/** @internal */
_direction;
get relationshipClass() { return this._relationshipClass; }
getRelationshipClassSync() {
if (!this._relationshipClass) // eslint-disable-line @typescript-eslint/no-misused-promises
return undefined;
// We cannot use the type guard here to avoid a circular dependency
const result = this.class.schema.lookupItemSync(this._relationshipClass);
return result?.schemaItemType === SchemaItemType.RelationshipClass ? result : undefined;
}
get direction() { return this._direction; }
/**
* Save this NavigationProperty's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
schemaJson.relationshipName = this.relationshipClass.fullName;
schemaJson.direction = strengthDirectionToString(this.direction);
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
const relationshipClass = await this.relationshipClass;
const relationshipClassName = XmlSerializationUtils.createXmlTypedName(this.schema, relationshipClass.schema, relationshipClass.name);
itemElement.setAttribute("relationshipName", relationshipClassName);
itemElement.setAttribute("direction", strengthDirectionToString(this.direction));
return itemElement;
}
/** @internal */
constructor(ecClass, name, relationship, direction) {
super(ecClass, name, PropertyType.Navigation);
this._relationshipClass = relationship;
this._direction = (direction !== undefined) ? direction : StrengthDirection.Forward;
}
}
// TODO: Consolidate all of the INT32_MAX variables.
const INT32_MAX = 2147483647;
/** @public @preview */
export class ArrayProperty extends Property {
/** @internal */
_minOccurs = 0;
/** @internal */
_maxOccurs = INT32_MAX;
get minOccurs() { return this._minOccurs; }
get maxOccurs() { return this._maxOccurs; }
/**
* @internal Used in schema editing.
*/
setMinOccurs(minOccurs) {
this._minOccurs = minOccurs;
}
/**
* @internal Used in schema editing.
*/
setMaxOccurs(maxOccurs) {
this._maxOccurs = maxOccurs;
}
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const ArrayPropertyMixin = (Base) => {
return class extends Base {
/** @internal */
_minOccurs = 0;
/** @internal */
_maxOccurs = INT32_MAX;
get minOccurs() { return this._minOccurs; }
get maxOccurs() { return this._maxOccurs; }
constructor(...args) {
super(...args);
this._type = PropertyTypeUtils.asArray(this.propertyType);
}
fromJSONSync(arrayPropertyProps) {
super.fromJSONSync(arrayPropertyProps);
if (undefined !== arrayPropertyProps.minOccurs) {
this._minOccurs = arrayPropertyProps.minOccurs;
}
if (undefined !== arrayPropertyProps.maxOccurs) {
this._maxOccurs = arrayPropertyProps.maxOccurs;
}
}
async fromJSON(arrayPropertyProps) {
this.fromJSONSync(arrayPropertyProps);
}
/**
* Save this ArrayProperty's properties to an object for serializing to JSON.
*/
toJSON() {
const schemaJson = super.toJSON();
schemaJson.minOccurs = this.minOccurs;
if (this.maxOccurs !== undefined)
schemaJson.maxOccurs = this.maxOccurs;
return schemaJson;
}
/** @internal */
async toXml(schemaXml) {
const itemElement = await super.toXml(schemaXml);
itemElement.setAttribute("minOccurs", this.minOccurs.toString());
if (this.maxOccurs)
itemElement.setAttribute("maxOccurs", this.maxOccurs.toString());
return itemElement;
}
/**
* @internal Used in schema editing.
*/
setMinOccurs(minOccurs) {
this._minOccurs = minOccurs;
}
/**
* @internal Used in schema editing.
*/
setMaxOccurs(maxOccurs) {
this._maxOccurs = maxOccurs;
}
};
};
/** @public @preview */
export class PrimitiveArrayProperty extends ArrayPropertyMixin(PrimitiveProperty) {
/** @internal */
constructor(ecClass, name, primitiveType = PrimitiveType.Integer) {
super(ecClass, name, primitiveType);
}
/**
* Save this PrimitiveArrayProperty's properties to an object for serializing to JSON.
*/
toJSON() {
return super.toJSON();
}
}
/** @public @preview */
export class EnumerationArrayProperty extends ArrayPropertyMixin(EnumerationProperty) {
constructor(ecClass, name, type) {
super(ecClass, name, type);
}
}
/** @public @preview */
export class StructArrayProperty extends ArrayPropertyMixin(StructProperty) {
/** @internal */
constructor(ecClass, name, type) {
super(ecClass, name, type);
}
}
/**
* Hackish approach that works like a "friend class" so we can access protected members without making them public.
* @internal
*/
export class MutableProperty extends Property {
}
//# sourceMappingURL=Property.js.map