UNPKG

@yellicode/elements

Version:

The meta model API for Yellicode - an extensible code generator.

562 lines (561 loc) 26.9 kB
/* * Copyright (c) 2020 Yellicode * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as utils from './utils'; import * as Interfaces from "./interfaces"; import { ElementTypeUtility } from './utils'; import { ElementComparerImpl } from "./element-comparer"; import { UniqueId } from '@yellicode/core'; import { ElementFactory } from './element-factory'; /** * Internal class to which all behaviour of the model classes is delegated. */ var ModelDelegateImpl = /** @class */ (function () { function ModelDelegateImpl(elementMap) { this.elementMap = elementMap; // Note that elementMap is null when not initialized through the DataToModelConverter this.elementFactory = new ElementFactory(this); } // ************************ Document **************************** // ModelDelegateImpl.prototype.findElementById = function (id) { if (!this.elementMap) return null; return this.elementMap.getElementById(id); }; // ************************** Element ******************************* // ModelDelegateImpl.prototype.getFirstCommentBody = function (element) { if (!element.ownedComments || element.ownedComments.length == 0) return ''; // return an empty string, no null or undefined, they are inconvenient when using template literals. return element.ownedComments[0].body; }; // ************************ TypedElement **************************** // ModelDelegateImpl.prototype.getTypeName = function (typedElement) { var t = typedElement.type; if (!t) return ''; // If the type was deleted, return nothing if (t.isOrphaned()) return ''; return t.name; }; // ***************************** Package **************************** // ModelDelegateImpl.getPackagedElementsWhere = function (pack, predicate) { if (!pack.packagedElements) return []; return pack.packagedElements.filter(predicate); }; ModelDelegateImpl.getAllPackagedElementsWhereRecursive = function (pack, predicate, result) { pack.packagedElements.forEach(function (e) { if (predicate(e)) { result.push(e); } if (ElementTypeUtility.isPackage(e.elementType) && e.packagedElements) { ModelDelegateImpl.getAllPackagedElementsWhereRecursive(e, predicate, result); } }); }; ModelDelegateImpl.getAllPackagedElementsWhere = function (pack, predicate) { var result = []; if (pack.packagedElements) { ModelDelegateImpl.getAllPackagedElementsWhereRecursive(pack, predicate, result); } return result; }; ModelDelegateImpl.prototype.getNestedPackages = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { return pe.elementType === Interfaces.ElementType.package; }); }; ModelDelegateImpl.prototype.getTypes = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { // Technically, an Association is also a Type, but it does not seem to make much sense here return ElementTypeUtility.isType(pe.elementType) && !ElementTypeUtility.isAssociation(pe.elementType); }); }; ModelDelegateImpl.prototype.getAllTypes = function (element) { return ModelDelegateImpl.getAllPackagedElementsWhere(element, function (pe) { // Technically, an Association is also a Type, but it does not seem to make much sense here return ElementTypeUtility.isType(pe.elementType) && !ElementTypeUtility.isAssociation(pe.elementType); }); }; ModelDelegateImpl.prototype.getClasses = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isClass(pe.elementType); }); }; ModelDelegateImpl.prototype.getAllClasses = function (element) { return ModelDelegateImpl.getAllPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isClass(pe.elementType); }); }; ModelDelegateImpl.prototype.getInterfaces = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isInterface(pe.elementType); }); }; ModelDelegateImpl.prototype.getAllInterfaces = function (element) { return ModelDelegateImpl.getAllPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isInterface(pe.elementType); }); }; ModelDelegateImpl.prototype.getDataTypes = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isDataType(pe.elementType); }); }; ModelDelegateImpl.prototype.getAllDataTypes = function (element) { return ModelDelegateImpl.getAllPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isDataType(pe.elementType); }); }; ModelDelegateImpl.prototype.getEnumerations = function (element) { return ModelDelegateImpl.getPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isEnumeration(pe.elementType); }); }; ModelDelegateImpl.prototype.getAllEnumerations = function (element) { return ModelDelegateImpl.getAllPackagedElementsWhere(element, function (pe) { return ElementTypeUtility.isEnumeration(pe.elementType); }); }; // ************************ PackageableElement **************************** // ModelDelegateImpl.prototype.getPackage = function (packagedElement) { return packagedElement.owner; }; /** * Gets all packages that own this Package, working inwards from the top Package to the owning package. * @returns {Interfaces.Package[]} A collection of Packages. */ ModelDelegateImpl.prototype.getNestingPackages = function (packagedElement, stopAtNamespaceRoot) { // Work outwards, then reverse.. var result = []; var owner = packagedElement.owner; while (owner && utils.isPackage(owner)) { result.push(owner); if (owner.isNamespaceRoot && stopAtNamespaceRoot === true) { break; } owner = owner.owner; } result.reverse(); return result; }; ModelDelegateImpl.prototype.getNamespaceName = function (packagedElement, separator) { if (separator === void 0) { separator = '.'; } return this.getNestingPackages(packagedElement, true).map(function (p) { return p.name; }).join(separator); }; ModelDelegateImpl.prototype.getQualifiedName = function (packagedElement, separator) { if (separator === void 0) { separator = '.'; } var namespaceName = this.getNamespaceName(packagedElement, separator); return namespaceName ? "" + namespaceName + separator + packagedElement.name : packagedElement.name; }; // ************************ Generalization **************************** // ModelDelegateImpl.prototype.getSpecific = function (generalization) { return generalization.owner; }; // ************************** Classifier ******************************* // ModelDelegateImpl.prototype.getFirstGeneralization = function (classifier) { if (classifier.generalizations.length === 0) return null; return classifier.generalizations[0]; }; ModelDelegateImpl.prototype.getFirstParent = function (classifier) { var firstGeneralization = this.getFirstGeneralization(classifier); return (firstGeneralization) ? firstGeneralization.general : null; }; ModelDelegateImpl.prototype.getParents = function (classifier) { return classifier.generalizations.map(function (g) { return g.general; }); }; /** * Returns all of the direct and indirect ancestors of a generalized Classifier, working outwards: more specific classifiers will * appear before more general classifiers. */ ModelDelegateImpl.prototype.getAllParents = function (classifier) { var allParents = []; this.getAllParentsRecursive(classifier, allParents); return allParents; }; ModelDelegateImpl.prototype.getAllParentsRecursive = function (classifier, allParents) { var _this = this; var ownParents = this.getParents(classifier); if (ownParents != null) { // More specific parents first. ownParents.forEach(function (p) { var ix = allParents.indexOf(p); // If the parent is already there, remove it and add it again at the end of the list. if (ix > -1) { allParents.splice(ix, 1); } allParents.push(p); }); // add the parents of each parent ownParents.forEach(function (p) { _this.getAllParentsRecursive(p, allParents); }); } }; ModelDelegateImpl.prototype.getSpecializations = function (classifier) { if (!this.elementMap) return []; return this.elementMap.getSpecializationsOf(classifier.id); }; ModelDelegateImpl.prototype.getAllSpecializations = function (classifier) { if (!this.elementMap) return []; return this.elementMap.getAllSpecializationsOf(classifier.id); }; // /** // * Returns true if one classifier has the other as direct general. // */ // private static hasGeneral(x: Interfaces.Classifier, y: Interfaces.Classifier): boolean { // // TODO: recursive? // const firstMatch = x.generalizations.find(g => g.general.id === y.id); // return firstMatch ? true: false; // } // ******************** MemberedClassifier **************************** // ModelDelegateImpl.prototype.getAllAttributes = function (memberedClassifier) { var distinctClassifierIds = []; // keep this list to check against recursion var result = []; this.getAllAttributesRecursive(memberedClassifier, distinctClassifierIds, result); return result; }; ModelDelegateImpl.prototype.getAllAttributesRecursive = function (memberedClassifier, distinctClassifierIds, result) { var _this = this; distinctClassifierIds.push(memberedClassifier.id); // First add the general members, then the owned memberedClassifier.generalizations.forEach(function (g) { var general = g.general; if (distinctClassifierIds.indexOf(general.id) === -1) { _this.getAllAttributesRecursive(general, distinctClassifierIds, result); } }); // Now add the owned members result.push.apply(result, memberedClassifier.ownedAttributes); }; ModelDelegateImpl.prototype.getAllOperations = function (memberedClassifier) { var distinctClassifierIds = []; // keep this list to check against recursion var result = []; this.getAllOperationsRecursive(memberedClassifier, distinctClassifierIds, result); return result; }; ModelDelegateImpl.prototype.getAllOperationsRecursive = function (memberedClassifier, distinctClassifierIds, result) { var _this = this; distinctClassifierIds.push(memberedClassifier.id); // keep this list to check against recursion // First add the general members, then the owned memberedClassifier.generalizations.forEach(function (g) { var general = g.general; if (distinctClassifierIds.indexOf(general.id) === -1) { _this.getAllOperationsRecursive(general, distinctClassifierIds, result); } }); // Now add the owned members. Replace any inherited members with the same signature memberedClassifier.ownedOperations.forEach(function (op) { // Is there an operation in the result that has the same signature? Then replace it. // TODO: can we set a "redefines" reference (referring to the base operation)? ModelDelegateImpl.removeItems(result, function (baseOperation) { return ElementComparerImpl.haveEqualSignatures(baseOperation, op); }); result.push(op); }); }; // ************************ Class **************************** // ModelDelegateImpl.prototype.getSuperClasses = function (cls) { return this.getSuperTypes(cls); // const result: TClass[] = []; // if (!cls.generalizations) return result; // cls.generalizations.forEach(g => { // if (ElementTypeUtility.isClass(g.general.elementType)) { // result.push(g.general as TClass); // } // }) // return result; }; ModelDelegateImpl.prototype.getSuperTypes = function (t) { var result = []; if (!t.generalizations) return result; t.generalizations.forEach(function (g) { if (g.general.isOrphaned()) return; if (g.general.elementType === t.elementType) { result.push(g.general); } }); return result; }; // ************************ MultiplicityElement **************************** // ModelDelegateImpl.prototype.getLower = function (element) { // If element is an operation, return the lower of the return parameter var multiplicityElement = element.elementType === Interfaces.ElementType.operation ? element.getReturnParameter() : element; if (!multiplicityElement) return null; // the operation has no return parameter var lowerValue = multiplicityElement.lowerValue; if (!lowerValue) return null; switch (lowerValue.elementType) { case Interfaces.ElementType.literalInteger: return lowerValue.value; case Interfaces.ElementType.literalString: var stringValue = lowerValue.getStringValue(); var parsed = Number.parseInt(stringValue); return isNaN(parsed) ? null : parsed; default: return null; } }; ModelDelegateImpl.prototype.getUpper = function (element) { // If element is an operation, return the upper of the return parameter var multiplicityElement = element.elementType === Interfaces.ElementType.operation ? element.getReturnParameter() : element; if (!multiplicityElement) return null; // the operation has no return parameter var upperValue = multiplicityElement.upperValue; if (upperValue == null) return null; switch (upperValue.elementType) { case Interfaces.ElementType.literalString: case Interfaces.ElementType.literalInteger: var stringValue = upperValue.getStringValue(); return new Interfaces.UnlimitedNatural(stringValue); case Interfaces.ElementType.literalUnlimitedNatural: return upperValue.value; default: return null; } }; ModelDelegateImpl.prototype.getLowerBound = function (element) { var lower = this.getLower(element); // Do an explicit null check, allowing for 0. An unspecified lower means a lower bound of 1. return (lower === null) ? 1 : lower; }; ModelDelegateImpl.prototype.getUpperBound = function (element) { var upper = this.getUpper(element); // Do an explicit null check, allowing for 0. An unspecified upper means an upper bound of 1. return (upper === null) ? new Interfaces.UnlimitedNatural(1) : upper; }; ModelDelegateImpl.prototype.isMultivalued = function (element) { var upper = this.getUpper(element); if (!upper) return false; return upper.IsInfinity || upper.Value > 1; }; ModelDelegateImpl.prototype.isOptional = function (element) { return this.getLowerBound(element) === 0; }; ModelDelegateImpl.prototype.isOptionalAndSinglevalued = function (element) { if (this.getLowerBound(element) !== 0) return false; // not optional var upper = this.getUpperBound(element); return !upper.IsInfinity && upper.Value === 1; }; ModelDelegateImpl.prototype.isRequiredAndSinglevalued = function (element) { if (this.getLowerBound(element) !== 1) return false; // not required var upper = this.getUpperBound(element); return !upper.IsInfinity && upper.Value === 1; }; // ******************** Default values **************************** // ModelDelegateImpl.prototype.getDefault = function (hasDefaultValue) { if (!hasDefaultValue.defaultValue) return null; return this.getValue(hasDefaultValue.defaultValue); }; // ******************** ValueSpecification **************************** // ModelDelegateImpl.prototype.getValue = function (valueSpecification) { if ("value" in valueSpecification) { // don't use hasOwnProperty here // The valueSpecification is a LiteralInteger, LiteralBoolean, etc. return valueSpecification["value"]; } else return null; // The valueSpecification is a LiteralNull or an unsupported implementation }; ModelDelegateImpl.prototype.getStringValue = function (valueSpecification) { var rawValue = valueSpecification.getValue(); if (rawValue === null) return null; if (valueSpecification.elementType === Interfaces.ElementType.literalUnlimitedNatural) { return rawValue.stringValue(); } return rawValue.toString(); }; // ******************** EnumerationLiteral **************************** // ModelDelegateImpl.prototype.getEnumeration = function (literal) { return literal.owner; }; ModelDelegateImpl.prototype.getSpecificationValue = function (literal) { if (!literal.specification) return null; return this.getValue(literal.specification); }; // ******************** Property **************************** // ModelDelegateImpl.prototype.getAssociation = function (property) { // The property can be owned by an association (association.ownedEnds) or by a classifier // If owned by an association, the path is short. if (utils.isAssociation(property.owner)) { return property.owner; } // The property is not owned by an association, but can still be part of one // (association.memberEnds) if (!this.elementMap) return null; return this.elementMap.getAssociationHavingMemberEnd(property); }; // ******************** Operation **************************** // ModelDelegateImpl.prototype.getReturnParameter = function (operation) { var val = operation.ownedParameters.find(function (p) { return p.direction === Interfaces.ParameterDirectionKind.return; }); return val || null; }; ModelDelegateImpl.prototype.getInputParameters = function (operation) { return operation.ownedParameters.filter(function (p) { return p.direction === Interfaces.ParameterDirectionKind.in || p.direction === Interfaces.ParameterDirectionKind.inout; }); }; ModelDelegateImpl.prototype.getOutputParameters = function (operation) { return operation.ownedParameters.filter(function (p) { return p.direction === Interfaces.ParameterDirectionKind.out || p.direction === Interfaces.ParameterDirectionKind.inout || p.direction === Interfaces.ParameterDirectionKind.return; }); }; ModelDelegateImpl.prototype.getReturnType = function (operation) { var returnParameter = this.getReturnParameter(operation); return returnParameter ? returnParameter.type : null; }; // ******************** Factory functions **************************** // ModelDelegateImpl.prototype.createValueSpecificationFromValue = function (value, owner) { switch (typeof (value)) { case 'number': var int = this.elementFactory.create('literalInteger', owner); int.value = value; return int; case 'string': var str = this.elementFactory.create('literalString', owner); str.value = value; return str; case 'boolean': var b = this.elementFactory.create('literalBoolean', owner); b.value = value; return b; default: throw "Cannot create ValueSpecification from value of type '" + typeof (value) + "'."; } }; ModelDelegateImpl.prototype.createElement = function (elementType, owner, properties, initFn) { var e = this.elementFactory.create(elementType, owner); if (properties) Object.assign(e, properties); // Ensure a ID if (!e.id && ElementFactory.requiresId(elementType)) { var exists = true; do { e.id = UniqueId.create(6); exists = this.elementMap.getElementById(e.id) != null; } while (exists); } // Initialize if (initFn) initFn(e); // Index if (e.id) { this.elementMap.addElement(e, null); } return e; }; /** * Notifies the delegate that a property was added as member end to an association. */ ModelDelegateImpl.prototype.onMemberEndAdded = function (association, end) { this.elementMap.addAssociationByEndId(end.id, association); }; /** * Notifies the delegate that a generalization was added. */ ModelDelegateImpl.prototype.onGeneralizationAdded = function (generalization) { if (!utils.isClassifier(generalization.owner)) return; // generalization is a Generalization of classifier, so classifier is a Specialization of generalization.general. this.elementMap.addSpecialization(generalization.general.id, generalization.owner); }; /** * Notifies the delegate that a an element was added to a collection. */ ModelDelegateImpl.prototype.onElementAdded = function (owner, element) { switch (element.elementType) { case Interfaces.ElementType.generalization: this.onGeneralizationAdded(element); break; case Interfaces.ElementType.property: if (utils.isAssociation(owner)) { this.onMemberEndAdded(owner, element); } break; } }; /** * Notifies the delegate that a owned element was deleted from a collection. */ ModelDelegateImpl.prototype.onElementDeleted = function (owner, element) { switch (element.elementType) { case Interfaces.ElementType.generalization: var generalization = element; this.elementMap.removeSpecialization(generalization.general.id, generalization.owner); break; case Interfaces.ElementType.property: if (utils.isAssociation(owner)) { this.elementMap.removeAssociationByEndId(element.id); } break; } }; /** * Sets the default value of the element to the specified value. */ ModelDelegateImpl.prototype.setDefaultValue = function (hasDefaultValue, value) { if (!value) return; hasDefaultValue.defaultValue = this.createValueSpecificationFromValue(value, hasDefaultValue); }; /** * Sets the default value of the element to null. */ ModelDelegateImpl.prototype.setDefaultValueNull = function (hasDefaultValue) { hasDefaultValue.defaultValue = this.elementFactory.create('literalNull', hasDefaultValue); }; /** * Sets the lower value of the element to unlimited. */ ModelDelegateImpl.prototype.setLowerValueUnlimited = function (element) { var literalUnlimited = this.elementFactory.create('literalUnlimitedNatural', element); literalUnlimited.value = new Interfaces.UnlimitedNatural('*'); element.lowerValue = literalUnlimited; }; /** * Sets the lower value of the element to the specified integer. */ ModelDelegateImpl.prototype.setLowerValue = function (element, value) { element.lowerValue = this.createValueSpecificationFromValue(value, element); }; /** * Sets the upper value of the element to unlimited. */ ModelDelegateImpl.prototype.setUpperValueUnlimited = function (element) { var literalUnlimited = this.elementFactory.create('literalUnlimitedNatural', element); literalUnlimited.value = new Interfaces.UnlimitedNatural('*'); element.upperValue = literalUnlimited; }; /** * Sets the upper value of the element to the specified integer. */ ModelDelegateImpl.prototype.setUpperValue = function (element, value) { element.upperValue = this.createValueSpecificationFromValue(value, element); }; /** * Sets the enumeration literal to the specified value. */ ModelDelegateImpl.prototype.setSpecification = function (element, value) { element.specification = this.createValueSpecificationFromValue(value, element); }; ModelDelegateImpl.removeItems = function (array, predicate) { if (!array.length) return; var indices = []; // Create a reverse array of indices, then remove them in that order. for (var i = array.length - 1; i > -1; i--) { if (predicate(array[i])) indices.push(i); } indices.forEach(function (i) { array.splice(i, 1); }); }; return ModelDelegateImpl; }()); export { ModelDelegateImpl };