UNPKG

@finos/legend-application-pure-ide

Version:
496 lines 21.5 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 { ClassView, Diagram, GeneralizationView, Point, PropertyView, Rectangle, _relationshipView_simplifyPath, } from '@finos/legend-extension-dsl-diagram/graph'; import { Class, CoreModel, DerivedProperty, ELEMENT_PATH_DELIMITER, Enumeration, GenericType, GenericTypeExplicitReference, Multiplicity, PackageableElementExplicitReference, Profile, Property, PropertyExplicitReference, PureModel, resolvePackagePathAndElementName, Stereotype, StereotypeExplicitReference, SystemModel, Tag, TaggedValue, TagExplicitReference, getOrCreatePackage, addElementToPackage, getTag, getStereotype, getOwnProperty, AggregationKind, } from '@finos/legend-graph'; import { addUniqueEntry, guaranteeNonNullable, } from '@finos/legend-shared'; import { createModelSchema, primitive, object, list, optional, deserialize, custom, SKIP, } from 'serializr'; import { SourceInformation } from './SourceInformation.js'; // ----------------------------------- Shared PURE serialization model --------------------------------------- // // We don't intend to build Pure graph from these serialization models, hence, we never really want to export them // to use outside of this file; their sole purpose is to get the result from the diagram info endpoints // to convert to Legend protocol model to use in Legend Studio diagram renderer /** * Unfortunately, diagram analysis endpoint now return malformed source-information so we need to have this hacky * surgery before properly deserialize it. */ const TEMPORARY__diagramInfoSourceInformationSerializationSchema = custom(() => SKIP, (json) => { json.sourceId = json.source; return deserialize(SourceInformation, json); }); class PURE__Profile { package; name; tags = []; stereotypes = []; } createModelSchema(PURE__Profile, { name: primitive(), package: primitive(), stereotypes: list(primitive()), tags: list(primitive()), }); class PURE__Steoreotype { profile; value; } createModelSchema(PURE__Steoreotype, { profile: primitive(), value: primitive(), }); class PURE__Tag { profile; value; } createModelSchema(PURE__Tag, { profile: primitive(), value: primitive(), }); class PURE__TaggedValue { tag; value; } createModelSchema(PURE__TaggedValue, { tag: object(PURE__Tag), value: primitive(), }); class PURE__GenericType { rawType; typeParameter; // this will be specified when for generics case } createModelSchema(PURE__GenericType, { rawType: optional(primitive()), typeParameter: optional(primitive()), }); class PURE__Property { name; stereotypes = []; taggedValues = []; aggregation; multiplicity; // parameters // this is meant for qualified properties only genericType; } createModelSchema(PURE__Property, { aggregation: primitive(), genericType: object(PURE__GenericType), multiplicity: primitive(), name: primitive(), stereotypes: list(object(PURE__Steoreotype)), taggedValues: list(object(PURE__TaggedValue)), }); class PURE__PackageableElementPointer { package; name; sourceInformation; } createModelSchema(PURE__PackageableElementPointer, { name: primitive(), package: primitive(), sourceInformation: TEMPORARY__diagramInfoSourceInformationSerializationSchema, }); class PURE__Class { package; name; sourceInformation; stereotypes = []; taggedValues = []; // typeParameters: string[] = []; generalizations = []; properties = []; qualifiedProperties = []; } createModelSchema(PURE__Class, { generalizations: list(object(PURE__GenericType)), name: primitive(), package: primitive(), properties: list(object(PURE__Property)), qualifiedProperties: list(object(PURE__Property)), sourceInformation: TEMPORARY__diagramInfoSourceInformationSerializationSchema, stereotypes: list(object(PURE__Steoreotype)), taggedValues: list(object(PURE__TaggedValue)), }); class PURE__Enumeration { package; name; // sourceInformation!: SourceInformation; enumValues = []; } createModelSchema(PURE__Enumeration, { name: primitive(), package: primitive(), enumValues: list(primitive()), }); // -------------------------------------- Diagram ----------------------------------------- class PURE__Point { x; y; } createModelSchema(PURE__Point, { x: primitive(), y: primitive(), }); class PURE__Rectangle { height; width; } createModelSchema(PURE__Rectangle, { height: primitive(), width: primitive(), }); class PURE__Geometry { points = []; } createModelSchema(PURE__Geometry, { points: list(object(PURE__Point)), }); class PURE__GeneralizationView { id; source; target; geometry; } createModelSchema(PURE__GeneralizationView, { geometry: object(PURE__Geometry), id: primitive(), source: primitive(), target: primitive(), }); class PURE__PropertyViewPropertyPointer { name; owningType; } createModelSchema(PURE__PropertyViewPropertyPointer, { name: primitive(), owningType: primitive(), }); class PURE__PropertyView { id; source; target; property; geometry; } createModelSchema(PURE__PropertyView, { geometry: object(PURE__Geometry), id: primitive(), property: object(PURE__PropertyViewPropertyPointer), source: primitive(), target: primitive(), }); class PURE__TypeView { id; type; position; rectangleGeometry; } createModelSchema(PURE__TypeView, { id: primitive(), position: object(PURE__Point), rectangleGeometry: object(PURE__Rectangle), type: primitive(), }); class PURE__Diagram { package; name; stereotypes = []; taggedValues = []; // associationViews generalizationViews = []; propertyViews = []; typeViews = []; sourceInformation; } createModelSchema(PURE__Diagram, { name: primitive(), generalizationViews: list(object(PURE__GeneralizationView)), package: primitive(), propertyViews: list(object(PURE__PropertyView)), sourceInformation: TEMPORARY__diagramInfoSourceInformationSerializationSchema, stereotypes: list(object(PURE__Steoreotype)), taggedValues: list(object(PURE__TaggedValue)), typeViews: list(object(PURE__TypeView)), }); // ----------------------------------- Diagram Info --------------------------------------- class DiagramDomainInfo { // associations // skip these for now as we don't support association views classes = []; enumerations = []; profiles = []; } createModelSchema(DiagramDomainInfo, { // associations classes: list(object(PURE__Class)), enumerations: list(object(PURE__Enumeration)), profiles: list(object(PURE__Profile)), }); export class DiagramInfo { name; diagram; domainInfo; } createModelSchema(DiagramInfo, { diagram: object(PURE__Diagram), name: primitive(), domainInfo: optional(object(DiagramDomainInfo)), }); export class DiagramClassInfo { // associations class; enumerations = []; profiles = []; specializations = []; } createModelSchema(DiagramClassInfo, { // associations class: object(PURE__Class), enumerations: list(object(PURE__Enumeration)), profiles: list(object(PURE__Profile)), specializations: list(object(PURE__PackageableElementPointer)), }); // ----------------------------------------- Serializer -------------------------------------------- /** * Serialize the diagram in Studio to Pure grammar for M2 DSL Diagram * so we can persist it. */ export const serializeDiagram = (diagram) => { const typeViews = diagram.classViews.map((cv) => ` TypeView ${cv.id}(\n` + ` type=${cv.class.value.path},\n` + ` position=(${cv.position.x.toFixed(5)}, ${cv.position.y.toFixed(5)}),\n` + ` width=${cv.rectangle.width.toFixed(5)},\n` + ` height=${cv.rectangle.height.toFixed(5)},\n` + ` stereotypesVisible=true,\n` + ` attributesVisible=true,\n` + ` attributeStereotypesVisible=true,\n` + ` attributeTypesVisible=true,\n` + ` color=#FFFFCC,\n` + ` lineWidth=1.0)`); const generalizationViews = diagram.generalizationViews.map((gv, idx) => // NOTE: the relationship views in Diagram protocols don't have an ID ` GeneralizationView gview_${idx}(\n` + ` source=${gv.from.classView.value.id},\n` + ` target=${gv.to.classView.value.id},\n` + ` points=[${gv .buildFullPath() .map((pos) => `(${pos.x.toFixed(5)},${pos.y.toFixed(5)})`) .join(',')}],\n` + ` label='',\n` + ` color=#000000,\n` + ` lineWidth=-1.0,\n` + ` lineStyle=SIMPLE)`); const propertyViews = diagram.propertyViews.map((pv, idx) => ` PropertyView pview_${idx}(\n` + ` property=${pv.property.value._OWNER.path}.${pv.property.value.name},\n` + ` source=${pv.from.classView.value.id},\n` + ` target=${pv.to.classView.value.id},\n` + ` points=[${pv .buildFullPath() .map((pos) => `(${pos.x.toFixed(5)},${pos.y.toFixed(5)})`) .join(',')}],\n` + ` label='',\n` + ` propertyPosition=(0.0,0.0),\n` + ` multiplicityPosition=(0.0,0.0),\n` + ` color=#000000,\n` + ` lineWidth=-1.0,\n` + ` stereotypesVisible=true,\n` + ` nameVisible=true,\n` + ` lineStyle=SIMPLE)`); return (`Diagram ${diagram.path}(width=0.0, height=0.0)\n` + `{\n` + `${[...typeViews, ...generalizationViews, ...propertyViews].join('\n\n')}\n` + `}`); }; const getOrCreateClass = (path, graph, diagramClasses, sourceInformation) => { const existingClass = graph.getOwnNullableClass(path); if (!existingClass) { const [_package, name] = resolvePackagePathAndElementName(path); const _class = new Class(name); addElementToPackage(getOrCreatePackage(graph.root, _package, true, new Map()), _class); graph.setOwnType(path, _class); diagramClasses.set(path, { isStubbed: true, sourceInformation, }); return _class; } return existingClass; }; const parseMultiplicty = (text) => { if (text === '*') { return new Multiplicity(0, undefined); } else { const parts = text.split('..'); if (parts.length === 1) { return new Multiplicity(parseInt(guaranteeNonNullable(parts[0]), 10), parseInt(guaranteeNonNullable(parts[0]), 10)); } else if (parts.length === 2) { return new Multiplicity(parseInt(guaranteeNonNullable(parts[0]), 10), parts[1] === '*' ? undefined : parseInt(guaranteeNonNullable(parts[1]), 10)); } throw new Error(`Can't parse multiplicity value '${text}'`); } }; const buildClass = (_class, classData, graph, diagramClasses) => { classData.taggedValues.forEach((taggedValueData) => { addUniqueEntry(_class.taggedValues, new TaggedValue(TagExplicitReference.create(getTag(graph.getProfile(taggedValueData.tag.profile), taggedValueData.tag.value)), taggedValueData.value)); }); classData.stereotypes.forEach((stereotypeData) => { addUniqueEntry(_class.stereotypes, StereotypeExplicitReference.create(getStereotype(graph.getProfile(stereotypeData.profile), stereotypeData.value))); }); classData.generalizations .filter((superTypeData) => Boolean(superTypeData.rawType)) .forEach((superTypeData) => { const superClass = getOrCreateClass(guaranteeNonNullable(superTypeData.rawType), graph, diagramClasses, undefined); addUniqueEntry(_class.generalizations, GenericTypeExplicitReference.create(new GenericType(superClass))); addUniqueEntry(superClass._subclasses, _class); }); classData.properties .filter((propertyData) => Boolean(propertyData.genericType.rawType)) .forEach((propertyData) => { const newProperty = new Property(propertyData.name, parseMultiplicty(propertyData.multiplicity), GenericTypeExplicitReference.create(new GenericType(graph.getOwnNullableEnumeration(guaranteeNonNullable(propertyData.genericType.rawType)) ?? getOrCreateClass(guaranteeNonNullable(propertyData.genericType.rawType), graph, diagramClasses, undefined))), _class); newProperty.aggregation = propertyData.aggregation === 'Composite' ? AggregationKind.COMPOSITE : propertyData.aggregation === 'Shared' ? AggregationKind.SHARED : undefined; addUniqueEntry(_class.properties, newProperty); }); classData.qualifiedProperties .filter((propertyData) => propertyData.genericType.rawType) .forEach((propertyData) => { addUniqueEntry(_class.derivedProperties, new DerivedProperty(propertyData.name, parseMultiplicty(propertyData.multiplicity), GenericTypeExplicitReference.create(new GenericType(graph.getOwnNullableEnumeration(guaranteeNonNullable(propertyData.genericType.rawType)) ?? getOrCreateClass(guaranteeNonNullable(propertyData.genericType.rawType), graph, diagramClasses, undefined))), _class)); }); }; /** * Since the diagram renderer uses Studio metamodel, here we build * Studio metamodel graph and diagram from the Pure IDE diagram info * to make use of the renderer. */ export const buildGraphFromDiagramInfo = (diagramInfo) => { const graph = new PureModel(new CoreModel([]), new SystemModel([]), []); const diagramClasses = new Map(); // domain if (diagramInfo.domainInfo) { const domain = diagramInfo.domainInfo; // first pass: add all the listed types and do really basic processing domain.classes.forEach((classData) => { const _class = new Class(classData.name); addElementToPackage(getOrCreatePackage(graph.root, classData.package, true, new Map()), _class); graph.setOwnType(_class.path, _class); diagramClasses.set(_class.path, { sourceInformation: classData.sourceInformation, isStubbed: false, }); }); domain.profiles.forEach((profileData) => { const profile = new Profile(profileData.name); addElementToPackage(getOrCreatePackage(graph.root, profileData.package, true, new Map()), profile); graph.setOwnProfile(profile.path, profile); profileData.tags.forEach((value) => addUniqueEntry(profile.p_tags, new Tag(profile, value))); profileData.stereotypes.forEach((value) => addUniqueEntry(profile.p_stereotypes, new Stereotype(profile, value))); }); domain.enumerations.forEach((enumerationData) => { const enumeration = new Enumeration(enumerationData.name); addElementToPackage(getOrCreatePackage(graph.root, enumerationData.package, true, new Map()), enumeration); graph.setOwnType(enumeration.path, enumeration); // NOTE: there is no need to pocess enumeration enum values since diagram does not need them }); // second pass domain.classes.forEach((classData) => { const fullPath = `${classData.package}${classData.package === '' ? '' : ELEMENT_PATH_DELIMITER}${classData.name}`; const _class = graph.getClass(fullPath); buildClass(_class, classData, graph, diagramClasses); }); } // diagram const diagramData = diagramInfo.diagram; const diagram = new Diagram(diagramData.name); addElementToPackage(getOrCreatePackage(graph.root, diagramData.package, true, new Map()), diagram); diagramData.typeViews.forEach((typeViewData) => { const classView = new ClassView(diagram, typeViewData.id, PackageableElementExplicitReference.create(graph.getClass(typeViewData.type))); classView.position = new Point(typeViewData.position.x, typeViewData.position.y); classView.rectangle = new Rectangle(typeViewData.rectangleGeometry.width, typeViewData.rectangleGeometry.height); addUniqueEntry(diagram.classViews, classView); }); diagramData.propertyViews.forEach((propertyViewData) => { const propertyView = new PropertyView(diagram, PropertyExplicitReference.create(getOwnProperty(graph.getClass(propertyViewData.property.owningType), propertyViewData.property.name)), guaranteeNonNullable(diagram.classViews.find((cv) => cv.id === propertyViewData.source)), guaranteeNonNullable(diagram.classViews.find((cv) => cv.id === propertyViewData.target))); propertyView.path = propertyViewData.geometry.points.map((pointData) => new Point(pointData.x, pointData.y)); _relationshipView_simplifyPath(propertyView); // transform the line because we store only 2 end points that are inside points and we will calculate the offset addUniqueEntry(diagram.propertyViews, propertyView); }); diagramData.generalizationViews.forEach((generationViewData) => { const generalizationView = new GeneralizationView(diagram, guaranteeNonNullable(diagram.classViews.find((cv) => cv.id === generationViewData.source)), guaranteeNonNullable(diagram.classViews.find((cv) => cv.id === generationViewData.target))); generalizationView.path = generationViewData.geometry.points.map((pointData) => new Point(pointData.x, pointData.y)); _relationshipView_simplifyPath(generalizationView); // transform the line because we store only 2 end points that are inside points and we will calculate the offset addUniqueEntry(diagram.generalizationViews, generalizationView); }); return [diagram, graph, diagramClasses]; }; export const addClassToGraph = (diagramClassInfo, graph, diagramClasses) => { // profiles diagramClassInfo.profiles.forEach((profileData) => { const profilePath = `${profileData.package}${profileData.package === '' ? '' : ELEMENT_PATH_DELIMITER}${profileData.name}`; if (!graph.getOwnNullableProfile(profilePath)) { const profile = new Profile(profileData.name); addElementToPackage(getOrCreatePackage(graph.root, profileData.package, true, new Map()), profile); graph.setOwnProfile(profile.path, profile); profileData.tags.forEach((value) => addUniqueEntry(profile.p_tags, new Tag(profile, value))); profileData.stereotypes.forEach((value) => addUniqueEntry(profile.p_stereotypes, new Stereotype(profile, value))); } }); // enumerations diagramClassInfo.enumerations.forEach((enumerationData) => { const enumerationPath = `${enumerationData.package}${enumerationData.package === '' ? '' : ELEMENT_PATH_DELIMITER}${enumerationData.name}`; if (!graph.getOwnNullableEnumeration(enumerationPath)) { const enumeration = new Enumeration(enumerationData.name); addElementToPackage(getOrCreatePackage(graph.root, enumerationData.package, true, new Map()), enumeration); graph.setOwnType(enumeration.path, enumeration); // NOTE: there is no need to pocess enumeration enum values since diagram does not need them } }); const classData = diagramClassInfo.class; const classPath = `${classData.package}${classData.package === '' ? '' : ELEMENT_PATH_DELIMITER}${classData.name}`; let _class = graph.getOwnNullableClass(classPath); if (!_class) { _class = new Class(classData.name); addElementToPackage(getOrCreatePackage(graph.root, classData.package, true, new Map()), _class); graph.setOwnType(_class.path, _class); } const isCurrentlyStubbed = diagramClasses.get(_class.path)?.isStubbed ?? true; diagramClasses.set(_class.path, { sourceInformation: classData.sourceInformation, isStubbed: false, }); diagramClassInfo.specializations.forEach((subTypePointer) => { const currentClass = guaranteeNonNullable(_class); const subClass = getOrCreateClass(guaranteeNonNullable(`${subTypePointer.package}${subTypePointer.package === '' ? '' : ELEMENT_PATH_DELIMITER}${subTypePointer.name}`), graph, diagramClasses, subTypePointer.sourceInformation); addUniqueEntry(currentClass._subclasses, subClass); if (!subClass.generalizations .map((generalization) => generalization.value.rawType) .includes(currentClass)) { addUniqueEntry(subClass.generalizations, GenericTypeExplicitReference.create(new GenericType(currentClass))); } }); if (isCurrentlyStubbed) { buildClass(_class, classData, graph, diagramClasses); } return _class; }; //# sourceMappingURL=DiagramInfo.js.map