UNPKG

@finos/legend-graph

Version:
480 lines 24.1 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Class } from '../metamodel/pure/packageableElements/domain/Class.js'; import { CORE_PURE_PATH, ELEMENT_PATH_DELIMITER, RESERVERD_PACKAGE_NAMES, MILESTONING_STEREOTYPE, PRIMITIVE_TYPE, MULTIPLICITY_INFINITE, PURE_DEPRECATED_STEREOTYPE, ROOT_PACKAGE_NAME, MILESTONING_VERSION_PROPERTY_SUFFIX, FUNCTION_SIGNATURE_MULTIPLICITY_INFINITE_TOKEN, PURE_DOC_TAG, } from '../MetaModelConst.js'; import { Package } from '../metamodel/pure/packageableElements/domain/Package.js'; import { AssertionError, assertNonEmptyString, assertTrue, guaranteeNonNullable, guaranteeType, uniqBy, UnsupportedOperationError, returnUndefOnError, filterByType, } from '@finos/legend-shared'; import { createPath } from '../MetaModelUtils.js'; import { Measure, Unit, } from '../metamodel/pure/packageableElements/domain/Measure.js'; import { Enumeration } from '../metamodel/pure/packageableElements/domain/Enumeration.js'; import { PrimitiveType } from '../metamodel/pure/packageableElements/domain/PrimitiveType.js'; import { Property } from '../metamodel/pure/packageableElements/domain/Property.js'; import { DerivedProperty } from '../metamodel/pure/packageableElements/domain/DerivedProperty.js'; import { GenericType } from '../metamodel/pure/packageableElements/domain/GenericType.js'; import { Multiplicity } from '../metamodel/pure/packageableElements/domain/Multiplicity.js'; import { extractDependencyGACoordinateFromRootPackageName } from '../DependencyManager.js'; import { FunctionAnalysisInfo, FunctionAnalysisParameterInfo, } from './FunctionAnalysis.js'; import { generateFunctionPrettyName } from './PureLanguageHelper.js'; export const addElementToPackage = (parent, element) => { // To improve performance we won't do duplication check here parent.children.push(element); element.package = parent; }; export const deleteElementFromPackage = (parent, packageableElement) => { parent.children = parent.children.filter((child) => child !== packageableElement); }; const getDescendantsOfPackage = (parent) => { const descendants = new Set(); parent.children.forEach((c) => { if (c instanceof Package) { getDescendantsOfPackage(c).forEach((e) => descendants.add(e)); } else { descendants.add(c); } }); return descendants; }; export const getAllDescendantsOfPackage = (parent, graph) => new Set(graph .getPackages(parent.path) .map((p) => [...getDescendantsOfPackage(p)]) .flat()); export const elementBelongsToPackage = (element, parent) => { const elementPackage = element instanceof Package ? element : element.package; if (!elementPackage) { return false; } const elementPackagePath = elementPackage.path; const parentPackage = parent.path; return (elementPackagePath + ELEMENT_PATH_DELIMITER).startsWith(parentPackage + ELEMENT_PATH_DELIMITER); }; export const getElementRootPackage = (element) => !element.package ? guaranteeType(element, Package) : getElementRootPackage(element.package); /** * If package name is a path, continue to recursively * traverse the package chain to find the leaf package * * NOTE: if we do not allow create new packages, errorcould be * thrown if a package with the specified path is not found */ const _getOrCreatePackage = (parentPackage, relativePackagePath, createNewPackageIfNotFound, cache) => { const index = relativePackagePath.indexOf(ELEMENT_PATH_DELIMITER); const packageName = index === -1 ? relativePackagePath : relativePackagePath.substring(0, index); // try to resolve when there is a cache miss let pkg; pkg = parentPackage.children.find((child) => child instanceof Package && child.name === packageName); if (!pkg) { if (!createNewPackageIfNotFound) { throw new AssertionError(`Can't find child package '${packageName}' in package '${parentPackage.path}'`); } // create the node if it is not in parent package assertTrue(!RESERVERD_PACKAGE_NAMES.includes(packageName), `Can't create package with reserved name '${packageName}'`); pkg = new Package(packageName); pkg.package = parentPackage; // NOTE: here we directly push the element to the children array without any checks rather than use `addUniqueEntry` to improve performance. // Duplication checks should be handled separately for speed parentPackage.children.push(pkg); } // populate cache after resolving the package if (cache) { cache.set(createPath(parentPackage.path, packageName), pkg); } // traverse the package chain if (index !== -1) { return _getOrCreatePackage(pkg, relativePackagePath.substring(index + ELEMENT_PATH_DELIMITER.length), createNewPackageIfNotFound, cache); } return pkg; }; export const getOrCreatePackage = (parentPackage, relativePackagePath, createNewPackageIfNotFound, cache) => { // check cache to find the shortest chain of packages to find/build if (cache) { // short-circuit const cachedPackage = cache.get(createPath(parentPackage.path, relativePackagePath)); if (cachedPackage) { return cachedPackage; } // NOTE: to check the cache, we need to traverse from the full package path // up its ancestor chain till we find a cache hit let immediateParentPackageRelativePath = relativePackagePath; while (immediateParentPackageRelativePath !== '') { const fullPath = createPath(parentPackage.path, immediateParentPackageRelativePath); const cachedParentPackage = cache.get(fullPath); if (cachedParentPackage) { return _getOrCreatePackage(cachedParentPackage, relativePackagePath.substring(immediateParentPackageRelativePath.length + ELEMENT_PATH_DELIMITER.length, relativePackagePath.length), createNewPackageIfNotFound, cache); } const index = immediateParentPackageRelativePath.lastIndexOf(ELEMENT_PATH_DELIMITER); immediateParentPackageRelativePath = index !== -1 ? immediateParentPackageRelativePath.substring(0, index) : ''; } } return _getOrCreatePackage(parentPackage, relativePackagePath, createNewPackageIfNotFound, cache); }; export const getOrCreateGraphPackage = (graph, packagePath, cache) => { assertNonEmptyString(packagePath, 'Package path is required'); return getOrCreatePackage(graph.root, packagePath, true, cache); }; export const getRawGenericType = (genericType, clazz) => guaranteeType(genericType.rawType, clazz); export const isMainGraphElement = (element) => returnUndefOnError(() => getElementRootPackage(element))?.name === ROOT_PACKAGE_NAME.MAIN; export const isElementReadOnly = (element) => !isMainGraphElement(element); export const isDependencyElement = (element) => { const rootPackage = returnUndefOnError(() => getElementRootPackage(element)); return (rootPackage?.name === ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT || (rootPackage !== undefined && Boolean(extractDependencyGACoordinateFromRootPackageName(rootPackage.name)))); }; export const isGeneratedElement = (element) => returnUndefOnError(() => getElementRootPackage(element))?.name === ROOT_PACKAGE_NAME.MODEL_GENERATION; export const isSystemElement = (element) => { const elementRootPackageName = returnUndefOnError(() => getElementRootPackage(element))?.name; return (element instanceof PrimitiveType || elementRootPackageName === ROOT_PACKAGE_NAME.SYSTEM || elementRootPackageName === ROOT_PACKAGE_NAME.CORE); }; /** * Extract the type of temporal milestone the class is associated with (using stereotype). * * Whatever the type, it means the class is milestoned, which means: * 1. properties of this type will now be considered milestoned, and will be automatically * converted into a derived property which accept some temporal parameters (depending * on the milestoning type) * 2. when we query properties of this type, we can provide the values for these parameters */ export const getMilestoneTemporalStereotype = (val, graph) => { const milestonedProfile = graph.getProfile(CORE_PURE_PATH.PROFILE_TEMPORAL); let stereotype; const profile = val.stereotypes.find((st) => st.ownerReference.value === milestonedProfile); stereotype = Object.values(MILESTONING_STEREOTYPE).find((value) => value === profile?.value.value); if (stereotype !== undefined) { return stereotype; } val.generalizations.forEach((generalization) => { const superType = generalization.value.rawType; if (superType instanceof Class) { const milestonedStereotype = getMilestoneTemporalStereotype(superType, graph); if (milestonedStereotype !== undefined) { stereotype = Object.values(MILESTONING_STEREOTYPE).find((value) => value === milestonedStereotype); } } }); return stereotype; }; export const getTag = (profile, value) => guaranteeNonNullable(profile.p_tags.find((tag) => tag.value === value), `Can't find tag '${value}' in profile '${profile.path}'`); export const getStereotype = (profile, value) => guaranteeNonNullable(profile.p_stereotypes.find((stereotype) => stereotype.value === value), `Can't find stereotype '${value}' in profile '${profile.path}'`); export const getEnumValueNames = (enumeration) => enumeration.values.map((value) => value.name).filter(Boolean); export const getEnumValue = (enumeration, name) => guaranteeNonNullable(enumeration.values.find((value) => value.name === name), `Can't find enum value '${name}' in enumeration '${enumeration.path}'`); export const getFirstAssociatedProperty = (association) => guaranteeNonNullable(association.properties[0]); export const getSecondAssociatedProperty = (association) => guaranteeNonNullable(association.properties[1]); export const getOtherAssociatedProperty = (association, property) => { const idx = association.properties.findIndex((p) => p === property); assertTrue(idx !== -1, `Can't find property '${property.name}' in association '${association.path}'`); return guaranteeNonNullable(association.properties[(idx + 1) % 2]); }; export const getAssociatedPropertyClass = (association, property) => { if (property instanceof Property) { return guaranteeType(getOtherAssociatedProperty(association, property).genericType .ownerReference.value, Class, `Association property '${property.name}' must be of type 'class'`); } else if (property instanceof DerivedProperty) { throw new UnsupportedOperationError(`Derived property is not currently supported in association`); } throw new UnsupportedOperationError(`Can't get associated class of property`, property); }; /** * Get all superclasses of a class, accounted for loop and duplication (which should be caught by compiler) * NOTE: we intentionally leave out `Any` */ export const getAllSuperclasses = (c) => { const visitedClasses = new Set(); visitedClasses.add(c); const resolveSuperTypes = (_class) => { _class.generalizations.forEach((gen) => { const superType = getRawGenericType(gen.value, Class); if (!visitedClasses.has(superType)) { visitedClasses.add(superType); resolveSuperTypes(superType); } }); }; resolveSuperTypes(c); visitedClasses.delete(c); return Array.from(visitedClasses); }; /** * Get all subclasses of a class, accounted for loop and duplication (which should be caught by compiler) * NOTE: we intentionally leave out `Any` */ export const getAllSubclasses = (c) => { const visitedClasses = new Set(); visitedClasses.add(c); const resolveSubclasses = (_class) => { _class._subclasses.forEach((subclass) => { if (!visitedClasses.has(subclass)) { visitedClasses.add(subclass); resolveSubclasses(subclass); } }); }; resolveSubclasses(c); visitedClasses.delete(c); return Array.from(visitedClasses); }; export const getMilestoningGeneratedProperties = (_class) => _class._generatedMilestonedProperties.filter(filterByType(Property)); /** * Get class and its supertypes' properties recursively, duplications and loops are handled (Which should be caught by compiler) */ export const getAllClassProperties = (_class, includeGeneratedMilestoning) => uniqBy(getAllSuperclasses(_class) .concat(_class) .map((c) => c.propertiesFromAssociations.concat(c.properties)) .flat() .concat(includeGeneratedMilestoning ? getMilestoningGeneratedProperties(_class) : []), (property) => property.name); export const getAllClassDerivedProperties = (_class) => uniqBy(getAllSuperclasses(_class) .concat(_class) .map((c) => c.derivedProperties) .flat(), (property) => property.name); export const getClassProperty = (_class, name) => guaranteeNonNullable(getAllClassProperties(_class, true).find((property) => property.name === name), `Can't find property '${name}' in class '${_class.path}'`); export const getAllOwnClassProperties = (_class) => _class.properties .concat(_class.propertiesFromAssociations) .concat(_class.derivedProperties); export const getOwnClassProperty = (_class, name) => guaranteeNonNullable(getAllOwnClassProperties(_class).find((property) => property.name === name), `Can't find property '${name}' in class '${_class.path}'`); export const getAllClassConstraints = (_class) => // Perhaps we don't need to care about deduping constraints here like for properties getAllSuperclasses(_class) .concat(_class) .map((c) => c.constraints) .flat(); export const getOwnProperty = (propertyOwner, name) => guaranteeNonNullable(propertyOwner instanceof Class ? getAllOwnClassProperties(propertyOwner).find((property) => property.name === name) : propertyOwner.properties.find((property) => property.name === name), `Can't find property '${name}' of '${propertyOwner.path}'`); /** * Check if the first type subtype of the second type * * NOTE: Use this for contravariant and covariant check * See https://www.originate.com/cheat-codes-for-contravariance-and-covariance * See https://en.wikipedia.org/wiki/Covariance_and_contravariance_of_vectors */ export const isSubType = (type1, type2) => { if (type1 === type2) { return true; } if (type1 instanceof Unit) { return type1.measure === type2; } else if (type1 instanceof Measure) { return false; } else if (type1 instanceof Enumeration) { return false; } else if (type1 instanceof PrimitiveType) { if (!(type2 instanceof PrimitiveType)) { return false; } if (type2.name === PRIMITIVE_TYPE.NUMBER) { return (type1.name === PRIMITIVE_TYPE.INTEGER || type1.name === PRIMITIVE_TYPE.FLOAT || type1.name === PRIMITIVE_TYPE.DECIMAL); } if (type2.name === PRIMITIVE_TYPE.DATE) { return (type1.name === PRIMITIVE_TYPE.STRICTDATE || type1.name === PRIMITIVE_TYPE.DATETIME || type1.name === PRIMITIVE_TYPE.LATESTDATE); } } else if (type1 instanceof Class) { return (type1.path === CORE_PURE_PATH.ANY || (type2 instanceof Class && getAllSuperclasses(type2).includes(type1))); } return false; }; /** * Check if the first type supertype of the second type * * NOTE: Use this for contravariant and covariant check * See https://www.originate.com/cheat-codes-for-contravariance-and-covariance * See https://en.wikipedia.org/wiki/Covariance_and_contravariance_of_vectors */ export const isSuperType = (type1, type2) => { if (type1 === type2) { return true; } if (type1 instanceof Unit) { return false; } else if (type1 instanceof Measure) { return type2 instanceof Unit && type2.measure === type1; } else if (type1 instanceof Enumeration) { return false; } else if (type1 instanceof PrimitiveType) { if (!(type2 instanceof PrimitiveType)) { return false; } if (type1.name === PRIMITIVE_TYPE.NUMBER) { return (type2.name === PRIMITIVE_TYPE.INTEGER || type2.name === PRIMITIVE_TYPE.FLOAT || type2.name === PRIMITIVE_TYPE.DECIMAL); } if (type1.name === PRIMITIVE_TYPE.DATE) { return (type2.name === PRIMITIVE_TYPE.STRICTDATE || type2.name === PRIMITIVE_TYPE.DATETIME || type2.name === PRIMITIVE_TYPE.LATESTDATE); } } else if (type1 instanceof Class) { return (type2.path === CORE_PURE_PATH.ANY || (type2 instanceof Class && getAllSubclasses(type2).includes(type1))); } return false; }; export const getMultiplicityDescription = (multiplicity) => { if (multiplicity.lowerBound === multiplicity.upperBound) { return `[${multiplicity.lowerBound.toString()}] - Must have exactly ${multiplicity.lowerBound.toString()} value(s)`; } else if (multiplicity.lowerBound === 0 && multiplicity.upperBound === undefined) { return `[${MULTIPLICITY_INFINITE}] - May have many values`; } return `[${multiplicity.lowerBound}..${multiplicity.upperBound ?? MULTIPLICITY_INFINITE}] - ${multiplicity.upperBound ? `Must have from ${multiplicity.lowerBound} to ${multiplicity.upperBound} value(s)` : `Must have at least ${multiplicity.lowerBound} values(s)`}`; }; export const getMultiplicityPrettyDescription = (multiplicity) => { if (multiplicity === Multiplicity.ONE) { return `[${multiplicity.lowerBound.toString()}] - Required`; } else if (multiplicity === Multiplicity.ZERO_MANY) { return `[${MULTIPLICITY_INFINITE}] - List`; } else if (multiplicity === Multiplicity.ZERO_ONE) { return `[${multiplicity.lowerBound}..${multiplicity.upperBound ?? MULTIPLICITY_INFINITE}] - Optional`; } return `[${multiplicity.lowerBound}..${multiplicity.upperBound ?? MULTIPLICITY_INFINITE}] - ${multiplicity.upperBound ? `Must have from ${multiplicity.lowerBound} to ${multiplicity.upperBound} value(s)` : `Must have at least ${multiplicity.lowerBound} values(s)`}`; }; export const areMultiplicitiesEqual = (mul1, mul2) => mul1.upperBound === mul2.upperBound && mul1.lowerBound === mul2.lowerBound; export const isElementDeprecated = (element, graph) => element.stereotypes.some((st) => st.value === graph .getProfile(CORE_PURE_PATH.PROFILE_DOC) .p_stereotypes.find((s) => s.value === PURE_DEPRECATED_STEREOTYPE)); export const extractAnnotatedElementDocumentation = (el) => { let result = undefined; for (const taggedValue of el.taggedValues) { if (taggedValue.tag.ownerReference.value.path === CORE_PURE_PATH.PROFILE_DOC && taggedValue.tag.value.value === PURE_DOC_TAG) { result = taggedValue.value; break; } } return result; }; /** * Gets the generated milestoned properties of a property owner */ export const getGeneratedMilestonedPropertiesForAssociation = (propertyOwner, property) => propertyOwner._generatedMilestonedProperties.filter((prop) => prop.name !== property.name && prop.name !== `${property.name}${MILESTONING_VERSION_PROPERTY_SUFFIX.ALL_VERSIONS}` && prop.name !== `${property.name}${MILESTONING_VERSION_PROPERTY_SUFFIX.ALL_VERSIONS_IN_RANGE}`); const getMultiplicityString = (lowerBound, upperBound) => { if (lowerBound === upperBound) { return lowerBound.toString(); } else if (lowerBound === 0 && upperBound === undefined) { return FUNCTION_SIGNATURE_MULTIPLICITY_INFINITE_TOKEN; } return `$${lowerBound}_${upperBound ?? 'MANY'}$`; }; export const getFunctionSignature = (func) => `_${func.parameters .map((p) => `${p.type.value.name}_${getMultiplicityString(p.multiplicity.lowerBound, p.multiplicity.upperBound)}_`) .join('_')}_${func.returnType.value.rawType.name}_${getMultiplicityString(func.returnMultiplicity.lowerBound, func.returnMultiplicity.upperBound)}_`; export const getFunctionName = (func, name) => name.substring(0, name.indexOf(getFunctionSignature(func))); export const getFunctionNameWithPath = (func) => func.package?.path + ELEMENT_PATH_DELIMITER + func.functionName; export const buildFunctionAnalysisInfoFromConcreteFunctionDefinition = (funcs, graph) => { const functionInfos = funcs.map((func) => { const functionInfo = new FunctionAnalysisInfo(); functionInfo.functionPath = func.path; functionInfo.name = func.name; functionInfo.functionName = func.functionName; functionInfo.functionPrettyName = generateFunctionPrettyName(func, { fullPath: true, spacing: false, }); functionInfo.packagePath = func.package?.path ?? ''; functionInfo.returnType = func.returnType.value.rawType.name; functionInfo.parameterInfoList = func.parameters.map((param) => { const paramInfo = new FunctionAnalysisParameterInfo(); paramInfo.multiplicity = graph.getMultiplicity(param.multiplicity.lowerBound, param.multiplicity.upperBound); paramInfo.name = param.name; paramInfo.type = param.type.value.name; return paramInfo; }); return functionInfo; }); return functionInfos; }; const _classHasCycle = (_class, __classesIndex, options) => { if (__classesIndex.has(_class.path)) { return true; } const excludedProperties = options?.excludedPaths?.get(_class.path) ?? []; const properties = options?.traverseNonRequiredProperties ? getAllClassProperties(_class) : getAllClassProperties(_class).filter((property) => property.multiplicity.lowerBound); const complexPropertyTypes = properties .filter((property) => !excludedProperties.includes(property.name)) .map((property) => property.genericType.value.rawType) .filter(filterByType(Class)); if (complexPropertyTypes.length > 0) { // we only count classes with complex properties in the cycle __classesIndex.add(_class.path); } // we only check unique complex property classes; 2 same property classes on the same level do not count as a cycle return Boolean(Array.from(new Set(complexPropertyTypes)).find((type) => _classHasCycle(type, __classesIndex, options))); }; export const classHasCycle = (_class, options) => _classHasCycle(_class, new Set(), options); export const getElementOrigin = (element, graph) => { if (isSystemElement(element)) { return 'system elements'; } else if (isGeneratedElement(element)) { return 'generation elements'; } else if (isDependencyElement(element)) { const name = graph.dependencyManager.getElementOrigin(element); if (name) { return `project dependency ${name}`; } } return ''; }; export const newGenericType = (rawType, typeArguments) => { const genericType = new GenericType(rawType); genericType.typeArguments = typeArguments; return genericType; }; //# sourceMappingURL=DomainHelper.js.map