@yellicode/elements
Version:
The meta model API for Yellicode - an extensible code generator.
562 lines (561 loc) • 26.9 kB
JavaScript
/*
* 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 };