diff --git a/src/lib/export/export-utils.ts b/src/lib/export/export-utils.ts index 6ae0089..d8a2f35 100644 --- a/src/lib/export/export-utils.ts +++ b/src/lib/export/export-utils.ts @@ -1,16 +1,15 @@ import { Action, ActionType, - Arc, - ArcType, CaseLogic, Event, Expression, + Extension, I18nString, I18nWithDynamic, - Logic, Option, - PetriflowFunction, - XmlArcType + Logic, + Option, + PetriflowFunction } from '../model'; export class ExportUtils { @@ -48,7 +47,7 @@ export class ExportUtils { }] : undefined, value?.dynamic); } - public exportOption(doc: Element, name: string, value : Option | undefined): void { + public exportOption(doc: Element, name: string, value: Option | undefined): void { const attributes = []; if (value?.key) { attributes.push({ @@ -164,14 +163,6 @@ export class ExportUtils { element.appendChild(exportLogic); } - public exportArcType(type: ArcType): XmlArcType { - const xmlType = Arc.arcTypeMapping.get(type); - if (!xmlType) { - throw new Error(`Unknown export mapping for arc type ${type}`); - } - return xmlType; - } - public transformKebabCaseToCamelCase(stringToTransform: string): string { return stringToTransform.replace(/-./g, x => x[1].toUpperCase()); } @@ -191,4 +182,15 @@ export class ExportUtils { }])); doc.appendChild(tagsElement); } + + public exportExtension(doc: Element, name: string, extension: Extension | undefined): void { + if (extension === undefined) { + return + } + const extensionElement = this.xmlConstructor.createElement(name); + ['id', 'version'].forEach(extensionAttribute => { + this.exportTag(extensionElement, extensionAttribute, extension[extensionAttribute as keyof Extension]?.toString()); + }); + doc.appendChild(extensionElement); + } } diff --git a/src/lib/export/export.service.ts b/src/lib/export/export.service.ts index a830e5b..bd9f041 100644 --- a/src/lib/export/export.service.ts +++ b/src/lib/export/export.service.ts @@ -78,8 +78,8 @@ export class ExportService { this._exportUtils.exportTag(doc, 'icon', model.icon); this._exportUtils.exportTag(doc, 'defaultRole', model.defaultRole !== undefined ? (model.defaultRole.toString()) : ''); this._exportUtils.exportTag(doc, 'anonymousRole', model.anonymousRole !== undefined ? (model.anonymousRole.toString()) : ''); - this._exportUtils.exportTags(doc, model.tags); this._exportUtils.exportI18nWithDynamic(doc, 'caseName', model.caseName); + this._exportUtils.exportExtension(doc, 'extends', model.extends); } public exportRoles(doc: Element, model: PetriNet): void { @@ -276,7 +276,6 @@ export class ExportService { this._exportUtils.exportTag(exportTrans, 'x', trans.x?.toString(), true); this._exportUtils.exportTag(exportTrans, 'y', trans.y?.toString(), true); this._exportUtils.exportI18nString(exportTrans, 'title', trans.title, true); - this._exportUtils.exportTags(exportTrans, trans.tags); this._exportUtils.exportTag(exportTrans, 'icon', trans.icon ?? ''); this._exportUtils.exportTag(exportTrans, 'assignPolicy', trans.assignPolicy === AssignPolicy.MANUAL ? '' : trans.assignPolicy); this._exportUtils.exportTag(exportTrans, 'finishPolicy', trans.finishPolicy === FinishPolicy.MANUAL ? '' : trans.finishPolicy); @@ -672,7 +671,7 @@ export class ExportService { model.getArcs().sort((a, b) => a.compare(b)).forEach(arc => { const exportArc = this.xmlConstructor.createElement('arc'); this._exportUtils.exportTag(exportArc, 'id', arc.id, true); - this._exportUtils.exportTag(exportArc, 'type', this._exportUtils.exportArcType(arc.type)); + this._exportUtils.exportTag(exportArc, 'type', arc.type); this._exportUtils.exportTag(exportArc, 'sourceId', arc.source.id); this._exportUtils.exportTag(exportArc, 'destinationId', arc.destination.id); this._exportUtils.exportExpression(exportArc, 'multiplicity', arc.multiplicity); diff --git a/src/lib/import/import-utils.ts b/src/lib/import/import-utils.ts index 5940b05..20d655f 100644 --- a/src/lib/import/import-utils.ts +++ b/src/lib/import/import-utils.ts @@ -1,6 +1,7 @@ import { Action, ActionType, + ArcType, Component, DataEvent, DataEventType, @@ -9,6 +10,7 @@ import { Event, EventPhase, Expression, + Extension, FlexAlignContent, FlexAlignItems, FlexContainer, @@ -40,8 +42,7 @@ import { Property, TransitionPermissionRef, Trigger, - TriggerType, - XmlArcType + TriggerType } from '../model'; import {IdentifierBlacklist} from '../model/identifier-blacklist'; @@ -71,17 +72,24 @@ export class ImportUtils { return i18n; } - public parseIdentifier(xmlTag: Element | Document | null, child: string): string { - const xmlIdentifierString = this.tagValue(xmlTag, child); + public parseIdentifierFromChildElement(xmlTag: Element | Document | null, child: string): string { + return this.validateIdentifier(this.tagValue(xmlTag, child), xmlTag?.nodeName); + } + + public parseIdentifierFromAttribute(xmlTag: Element | null, child: string): string { + return this.validateIdentifier(this.tagAttribute(xmlTag, child), xmlTag?.nodeName); + } + + private validateIdentifier(xmlIdentifierString: string, nodeName: string | undefined) { if (xmlIdentifierString === '') { - throw new Error(`Id of ${xmlTag?.nodeName} must be defined`); + throw new Error(`Id of ${nodeName} must be defined`); } if (IdentifierBlacklist.identifierKeywords.has(xmlIdentifierString)) { - throw new Error(`Id of ${xmlTag?.nodeName} must not be Java or Groovy keyword, value [${xmlIdentifierString}]`); + throw new Error(`Id of ${nodeName} must not be Java or Groovy keyword, value [${xmlIdentifierString}]`); } const identifierRegex = new RegExp("^[$_a-zA-Z][_a-zA-Z0-9]*$"); if (!identifierRegex.test(xmlIdentifierString)) { - throw new Error(`Id of ${xmlTag?.nodeName} must be valid Java identifier, value [${xmlIdentifierString}]`); + throw new Error(`Id of ${nodeName} must be valid Java identifier, value [${xmlIdentifierString}]`); } return xmlIdentifierString; } @@ -173,7 +181,7 @@ export class ImportUtils { if (!xmlComponent?.children || xmlComponent.children.length === 0) { return undefined; } - const comp = new Component(this.parseIdentifier(xmlComponent, 'id')); + const comp = new Component(this.parseIdentifierFromChildElement(xmlComponent, 'id')); const properties = xmlComponent.getElementsByTagName('properties')[0]; if (properties?.children && properties.children.length > 0) { for (const prop of Array.from(properties.getElementsByTagName('property'))) { @@ -243,14 +251,14 @@ export class ImportUtils { public parseRoleRef(xmlRoleRef: Element): TransitionPermissionRef { const xmlRoleRefLogic = xmlRoleRef.getElementsByTagName('logic')[0]; - const roleRef = new TransitionPermissionRef(this.parseIdentifier(xmlRoleRef, 'id')); + const roleRef = new TransitionPermissionRef(this.parseIdentifierFromChildElement(xmlRoleRef, 'id')); this.resolveLogic(xmlRoleRefLogic, roleRef); roleRef.properties = this.parseProperties(xmlRoleRef); return roleRef; } public parseDataRef(xmlDataRef: Element): DataRef { - const dataRef = new DataRef(this.parseIdentifier(xmlDataRef, 'id')); + const dataRef = new DataRef(this.parseIdentifierFromChildElement(xmlDataRef, 'id')); for (const xmlEvent of Array.from(xmlDataRef.getElementsByTagName('event'))) { const event = new DataEvent(this.tagAttribute(xmlEvent, 'type') as DataEventType, ''); this.parseEvent(xmlEvent, event); @@ -271,7 +279,7 @@ export class ImportUtils { } public parseGrid(xmlGrid: Element): GridContainer { - const grid = new GridContainer(this.parseIdentifier(xmlGrid, 'id')); + const grid = new GridContainer(this.parseIdentifierFromChildElement(xmlGrid, 'id')); const properties = this.getChildElementByName(xmlGrid.children, 'properties'); if (properties) { @@ -482,7 +490,7 @@ export class ImportUtils { } public parseFlex(xmlFlex: Element) { - const flex = new FlexContainer(this.parseIdentifier(xmlFlex, 'id')); + const flex = new FlexContainer(this.parseIdentifierFromChildElement(xmlFlex, 'id')); const properties = this.getChildElementByName(xmlFlex.children, 'properties'); if (properties) { flex.properties = this.parseFlexContainerProperties(properties); @@ -616,16 +624,15 @@ export class ImportUtils { return isStatic; } - public parseArcType(xmlArc: Element): XmlArcType { - let parsedArcType = XmlArcType.REGULAR; - if (this.checkLengthAndNodes(xmlArc, 'type')) { - parsedArcType = xmlArc.getElementsByTagName('type')[0].childNodes[0].nodeValue as XmlArcType; + public parseArcType(xmlArc: Element): ArcType { + if (!this.checkLengthAndNodes(xmlArc, 'type')) { + throw new Error(`Type of arc with id ${this.parseIdentifierFromChildElement(xmlArc, 'id')} is undefined.`) } - return parsedArcType; + return xmlArc.getElementsByTagName('type')[0].childNodes[0].nodeValue as ArcType; } public parseEvent(xmlEvent: Element, event: Event): void { - event.id = this.parseIdentifier(xmlEvent, 'id'); + event.id = this.parseIdentifierFromChildElement(xmlEvent, 'id'); if (event.id === '') { event.id = event.type + "_event_" + this.getNextEventId(); } @@ -709,20 +716,11 @@ export class ImportUtils { this.resetActionId(); } - parseTags(xmlDoc: Element | Document): Map { - const tags = new Map(); - const tagsElement = xmlDoc.getElementsByTagName('tags')[0]; - if (tagsElement?.children && tagsElement.children.length > 0) { - for (const tagElement of Array.from(xmlDoc.getElementsByTagName('tag'))) { - this.parseTag(tags, tagElement); - } + public parseExtension(xmlDoc: Document): Extension | undefined { + const xmlExtension = xmlDoc.getElementsByTagName('extends')[0]; + if (xmlExtension === undefined) { + return undefined; } - return tags; - } - - parseTag(tags: Map, tagElement: Element): void { - const key = this.tagAttribute(tagElement, 'key'); - const value = tagElement.innerHTML; - tags.set(key, value); + return new Extension(this.tagValue(xmlExtension, 'id'), this.tagValue(xmlExtension, 'version')) } } diff --git a/src/lib/import/import.service.ts b/src/lib/import/import.service.ts index e664978..2d84c98 100644 --- a/src/lib/import/import.service.ts +++ b/src/lib/import/import.service.ts @@ -1,5 +1,5 @@ import { - Arc, + Arc, ArcType, AssignPolicy, Breakpoint, CaseEvent, @@ -33,8 +33,7 @@ import { Transition, TransitionEvent, TransitionEventType, - Validation, - XmlArcType + Validation } from '../model'; import {ImportUtils} from './import-utils'; import {PetriNetResult} from './petri-net-result'; @@ -58,8 +57,52 @@ export class ImportService { return xmlDoc; } - public parseFromXml(txt: string): PetriNetResult { + public parseFromXml(txt: string, parentModel?: PetriNet): PetriNetResult { const doc = this.parseXml(txt); + const result: PetriNetResult = this.parseFromDocument(doc, parentModel); + result.model.merge(); + return result; + } + + public parseMultipleFromXml(xmlPetriNets: string[]): Map { + const result = this.recursiveParseMultipleFromDocument(xmlPetriNets.map(xmlNet => this.parseXml(xmlNet))); + result.forEach((element: PetriNetResult) => { + element.model.merge(); + }) + return result; + } + + private recursiveParseMultipleFromDocument(documentPetriNets: Document[], results: Map = new Map()) { + const netsToReimport: Document[] = []; + for (const xmlDoc of documentPetriNets) { + const identifier = this.importUtils.parseIdentifierFromChildElement(xmlDoc, 'id'); + if (results.has(identifier)) { + continue; + } + + const extension = this.importUtils.parseExtension(xmlDoc); + if (extension === undefined) { + const petriNetResult = this.parseFromDocument(xmlDoc); + results.set(petriNetResult.model.id, petriNetResult); + continue; + } + + if (results.has(extension.id)) { + const petriNetResult = this.parseFromDocument(xmlDoc, results.get(extension.id)?.model); + petriNetResult.model.parentModel = results.get(extension.id)?.model; + results.set(petriNetResult.model.id, petriNetResult); + continue; + } + netsToReimport.push(xmlDoc); + } + + if (netsToReimport.length > 0) { + this.recursiveParseMultipleFromDocument(netsToReimport, results); + } + return results; + } + + private parseFromDocument(doc: Document, parentModel?: PetriNet): PetriNetResult { const parseError = doc.getElementsByTagName('parsererror'); // cspell:disable-line let result = new PetriNetResult(); @@ -78,15 +121,15 @@ export class ImportService { return result; } - result = this.importFromXml(doc, result); + result = this.importFromXml(doc, result, parentModel); this.importUtils.resetIds(); return result; } - private importFromXml(xmlDoc: Document, result: PetriNetResult): PetriNetResult { // TODO: return stream + private importFromXml(xmlDoc: Document, result: PetriNetResult, parentModel?: PetriNet): PetriNetResult { // TODO: return stream result.model = new PetriNet(); - this.importModel(result, xmlDoc); + this.importModel(result, xmlDoc, parentModel); this.importRoles(result, xmlDoc); this.importFunctions(result, xmlDoc); this.importEvents(result, xmlDoc); @@ -102,16 +145,19 @@ export class ImportService { return result; } - public importModel(modelResult: PetriNetResult, xmlDoc: Document): void { + public importModel(modelResult: PetriNetResult, xmlDoc: Document, parentModel?: PetriNet): void { try { - modelResult.model.id = this.importUtils.parseIdentifier(xmlDoc, 'id'); + modelResult.model.id = this.importUtils.parseIdentifierFromChildElement(xmlDoc, 'id'); modelResult.model.version = this.importUtils.tagValue(xmlDoc, 'version'); modelResult.model.icon = this.importUtils.tagValue(xmlDoc, 'icon'); modelResult.model.defaultRole = this.importUtils.tagValue(xmlDoc, 'defaultRole') === '' ? ImportService.DEFAULT_ROLE_DEFAULT_VALUE : this.importUtils.tagValue(xmlDoc, 'defaultRole') === 'true'; modelResult.model.anonymousRole = this.importUtils.tagValue(xmlDoc, 'anonymousRole') === '' ? ImportService.ANONYMOUS_ROLE_DEFAULT_VALUE : this.importUtils.tagValue(xmlDoc, 'anonymousRole') === 'true'; modelResult.model.title = this.importUtils.parseI18n(xmlDoc, 'title'); modelResult.model.caseName = this.importUtils.parseI18nWithDynamic(xmlDoc, 'caseName'); - modelResult.model.tags = this.importUtils.parseTags(xmlDoc); + modelResult.model.extends = this.importUtils.parseExtension(xmlDoc); + if (parentModel) { + modelResult.model.parentModel = parentModel; + } } catch (e: unknown) { modelResult.addError('Error happened during the importing model properties: ' + (e as Error).toString(), e as Error); } @@ -120,7 +166,8 @@ export class ImportService { public importRoles(modelResult: PetriNetResult, xmlDoc: Document): void { for (const xmlRole of Array.from(xmlDoc.getElementsByTagName('role'))) { try { - const role = new Role(this.importUtils.parseIdentifier(xmlRole, 'id')); + const roleId = this.importUtils.parseIdentifierFromChildElement(xmlRole, 'id'); + const role = new Role(roleId); this.parseRole(modelResult.model, xmlRole, role); } catch (e: unknown) { modelResult.addError('Error happened during the importing role [' + this.importUtils.tagValue(xmlRole, 'id') + ']: ' + (e as Error).toString(), e as Error); @@ -145,7 +192,7 @@ export class ImportService { if (scopeValue !== '') { role.scope = scopeValue as FunctionScope; } - role.properties = this.importUtils.parseProperties(xmlRole) + role.properties = this.importUtils.parseProperties(xmlRole); model.addRole(role); } @@ -187,7 +234,7 @@ export class ImportService { public importData(modelResult: PetriNetResult, xmlDoc: Document): void { for (const xmlData of Array.from(xmlDoc.getElementsByTagName('data'))) { try { - const id = this.importUtils.parseIdentifier(xmlData, 'id'); + const id = this.importUtils.parseIdentifierFromChildElement(xmlData, 'id'); const type = this.importUtils.tagAttribute(xmlData, 'type') as DataType; const data = new DataVariable(id, type); const scopeValue = this.importUtils.tagAttribute(xmlData, 'scope'); @@ -283,7 +330,7 @@ export class ImportService { public importTransitions(modelResult: PetriNetResult, xmlDoc: Document): void { for (const xmlTrans of Array.from(xmlDoc.getElementsByTagName('transition'))) { try { - const id = this.importUtils.parseIdentifier(xmlTrans, 'id'); + const id = this.importUtils.parseIdentifierFromChildElement(xmlTrans, 'id'); const xx = this.importUtils.parseNumberValue(xmlTrans, 'x') ?? 0; const yy = this.importUtils.parseNumberValue(xmlTrans, 'y') ?? 0; const trans = new Transition(xx, yy, id); @@ -307,7 +354,6 @@ export class ImportService { this.importTransitionTriggers(xmlTrans, trans, result); this.importTransitionContent(xmlTrans, trans, result) this.importTransitionEvents(xmlTrans, trans, result); - trans.tags = this.importUtils.parseTags(xmlTrans); } public importTransitionContent(xmlTrans: Element, trans: Transition, result: PetriNetResult) { @@ -393,7 +439,7 @@ export class ImportService { if (xmlRoleRefLogic === undefined) { continue; } - const roleRef = new ProcessPermissionRef(this.importUtils.parseIdentifier(xmlRoleRef, 'id')); + const roleRef = new ProcessPermissionRef(this.importUtils.parseIdentifierFromChildElement(xmlRoleRef, 'id')); this.importUtils.resolveCaseLogic(xmlRoleRefLogic, roleRef); modelResult.model.addRoleRef(roleRef); } catch (e) { @@ -408,7 +454,7 @@ export class ImportService { if (xmlUserRefLogic === undefined) { continue; } - const userRef = new ProcessPermissionRef(this.importUtils.parseIdentifier(xmlUserRef, 'id')); + const userRef = new ProcessPermissionRef(this.importUtils.parseIdentifierFromChildElement(xmlUserRef, 'id')); this.importUtils.resolveCaseLogic(xmlUserRefLogic, userRef); modelResult.model.addUserRef(userRef); } catch (e) { @@ -428,7 +474,7 @@ export class ImportService { } importPlace(modelResult: PetriNetResult, xmlPlace: Element) { - const placeId = this.importUtils.parseIdentifier(xmlPlace, 'id'); + const placeId = this.importUtils.parseIdentifierFromChildElement(xmlPlace, 'id'); let xx = this.importUtils.parseNumberValue(xmlPlace, 'x'); let yy = this.importUtils.parseNumberValue(xmlPlace, 'y'); if (xx === undefined || yy === undefined) { @@ -467,15 +513,17 @@ export class ImportService { } public parseArc(result: PetriNetResult, xmlArc: Element): Arc { - const arcId = this.importUtils.parseIdentifier(xmlArc, 'id'); + const arcId = this.importUtils.parseIdentifierFromChildElement(xmlArc, 'id'); const source = xmlArc.getElementsByTagName('sourceId')?.item(0)?.childNodes[0]?.nodeValue; - if (!source) + if (!source && result.model.extends === undefined) { throw new Error('Source of an arc must be defined!'); + } const target = xmlArc.getElementsByTagName('destinationId')?.item(0)?.childNodes[0]?.nodeValue; - if (!target) + if (!target && result.model.extends === undefined) { throw new Error('Target of an arc must be defined!'); + } const parsedArcType = this.importUtils.parseArcType(xmlArc); - const arc = this.resolveArc(source, target, parsedArcType, arcId, result); + const arc = this.resolveArc(source as string, target as string, parsedArcType, arcId, result); const scopeValue = this.importUtils.tagAttribute(xmlArc, 'scope'); if (scopeValue !== '') { arc.scope = scopeValue as FunctionScope; @@ -485,37 +533,41 @@ export class ImportService { return arc; } - resolveArc(source: string, target: string, parsedArcType: XmlArcType, arcId: string, result: PetriNetResult): Arc { + resolveArc(source: string, target: string, parsedArcType: ArcType, arcId: string, result: PetriNetResult): Arc { // TODO: refactor let place, transition; switch (parsedArcType) { - case XmlArcType.INHIBITOR: + case ArcType.INHIBITOR: [place, transition] = this.getPlaceTransition(result, source, target, arcId); return new InhibitorArc(place, transition, arcId); - case XmlArcType.RESET: + case ArcType.RESET: [place, transition] = this.getPlaceTransition(result, source, target, arcId); return new ResetArc(place, transition, arcId); - case XmlArcType.READ: + case ArcType.READ: [place, transition] = this.getPlaceTransition(result, source, target, arcId); return new ReadArc(place, transition, arcId); - case XmlArcType.REGULAR: - if (result.model.getPlace(source)) { - [place, transition] = this.getPlaceTransition(result, source, target, arcId); - return new RegularPlaceTransitionArc(place, transition, arcId); - } else { - [place, transition] = this.getPlaceTransition(result, target, source, arcId); - return new RegularTransitionPlaceArc(transition, place, arcId); - } + case ArcType.REGULAR_PT: + [place, transition] = this.getPlaceTransition(result, source, target, arcId); + return new RegularPlaceTransitionArc(place, transition, arcId); + case ArcType.REGULAR_TP: + [place, transition] = this.getPlaceTransition(result, target, source, arcId); + return new RegularTransitionPlaceArc(transition, place, arcId); } throw new Error(`Unknown type ${parsedArcType}`); } getPlaceTransition(result: PetriNetResult, placeId: string, transitionId: string, arcId: string): [Place, Transition] { - const place = result.model.getPlace(placeId); - const transition = result.model.getTransition(transitionId); - if (place === undefined || transition === undefined) { + let place = result.model.getPlace(placeId); + let transition = result.model.getTransition(transitionId); + if (!result.model.extends && (place === undefined || transition === undefined)) { throw new Error(`Could not find nodes ${placeId}->${transitionId} of arc ${arcId}`); } + if (place === undefined) { + place = new Place(-1, -1, false, placeId); + } + if (transition === undefined) { + transition = new Transition(-1, -1, transitionId); + } return [place, transition]; } diff --git a/src/lib/model/arc/arc-type.enum.ts b/src/lib/model/arc/arc-type.enum.ts index 8b734fc..4eea88c 100644 --- a/src/lib/model/arc/arc-type.enum.ts +++ b/src/lib/model/arc/arc-type.enum.ts @@ -5,10 +5,3 @@ export enum ArcType { RESET = 'reset', INHIBITOR = 'inhibitor', } - -export enum XmlArcType { - REGULAR = 'regular', - READ = 'read', - RESET = 'reset', - INHIBITOR = 'inhibitor', -} diff --git a/src/lib/model/arc/arc.ts b/src/lib/model/arc/arc.ts index 50ee03e..6d1983c 100644 --- a/src/lib/model/arc/arc.ts +++ b/src/lib/model/arc/arc.ts @@ -2,7 +2,7 @@ import {Expression} from '../data-variable/expression'; import {Element} from '../petrinet/element'; import {FunctionScope} from '../petrinet/function-scope.enum'; import {NodeElement} from '../petrinet/node-element'; -import {ArcType, XmlArcType} from './arc-type.enum'; +import {ArcType} from './arc-type.enum'; import {Breakpoint} from './breakpoint'; export abstract class Arc extends Element { @@ -20,14 +20,6 @@ export abstract class Arc extends this._breakpoints = []; } - public static arcTypeMapping: Map = new Map([ - [ArcType.REGULAR_TP, XmlArcType.REGULAR], - [ArcType.REGULAR_PT, XmlArcType.REGULAR], - [ArcType.READ, XmlArcType.READ], - [ArcType.RESET, XmlArcType.RESET], - [ArcType.INHIBITOR, XmlArcType.INHIBITOR], - ]); - abstract get type(): ArcType; get source(): S { diff --git a/src/lib/model/i18n/i18n-translations.ts b/src/lib/model/i18n/i18n-translations.ts index 739518f..467ffa1 100644 --- a/src/lib/model/i18n/i18n-translations.ts +++ b/src/lib/model/i18n/i18n-translations.ts @@ -37,6 +37,10 @@ export class I18nTranslations { this._i18ns.delete(name); } + getTranslationIds(): Array { + return Array.from(this._i18ns.keys()); + } + public clone(): I18nTranslations { const cloned = new I18nTranslations(this._locale); this._i18ns.forEach(i => cloned.addI18n(i.clone())); diff --git a/src/lib/model/index.ts b/src/lib/model/index.ts index da8fde7..ce97c8c 100644 --- a/src/lib/model/index.ts +++ b/src/lib/model/index.ts @@ -17,6 +17,7 @@ export * from './petrinet/process-event'; export * from './petrinet/process-event-type.enum'; export * from './petrinet/process-permission-ref'; export * from './identifier-blacklist'; +export * from './petrinet/extension'; export * from './arc/arc'; export * from './arc/arc-type.enum'; diff --git a/src/lib/model/petri-net.ts b/src/lib/model/petri-net.ts index 5e526fd..0c9411d 100644 --- a/src/lib/model/petri-net.ts +++ b/src/lib/model/petri-net.ts @@ -6,6 +6,7 @@ import {I18nTranslations} from './i18n/i18n-translations'; import {I18nWithDynamic} from './i18n/i18n-with-dynamic'; import {CaseEvent} from './petrinet/case-event'; import {CaseEventType} from './petrinet/case-event-type.enum'; +import {Extension} from './petrinet/extension'; import {NodeElement} from './petrinet/node-element'; import {PetriflowFunction} from './petrinet/petriflow-function'; import {Place} from './petrinet/place'; @@ -35,7 +36,8 @@ export class PetriNet { private _transitions: Map; private _places: Map; private _arcs: Map>; - private _tags: Map; + private _extends?: Extension; + private _parentModel?: PetriNet; constructor() { this._id = 'new_model'; @@ -57,7 +59,6 @@ export class PetriNet { this._i18ns = new Map(); this._processEvents = new Map(); this._caseEvents = new Map(); - this._tags = new Map(); } get id(): string { @@ -133,7 +134,7 @@ export class PetriNet { } addRoleRef(roleRef: ProcessPermissionRef) { - if (!this._roles.has(roleRef.id) && roleRef.id !== Role.DEFAULT && roleRef.id !== Role.ANONYMOUS) { + if (this._extends === undefined && !this._roles.has(roleRef.id) && roleRef.id !== Role.DEFAULT && roleRef.id !== Role.ANONYMOUS) { throw new Error(`Referenced role with id ${roleRef.id} does not exist`); } if (this._roleRefs.has(roleRef.id)) { @@ -335,12 +336,20 @@ export class PetriNet { this._arcs.delete(id); } - get tags(): Map { - return this._tags; + get extends(): Extension | undefined { + return this._extends; } - set tags(value: Map) { - this._tags = value; + set extends(value: Extension | undefined) { + this._extends = value; + } + + get parentModel(): PetriNet | undefined { + return this._parentModel; + } + + set parentModel(value: PetriNet | undefined) { + this._parentModel = value; } public clone(): PetriNet { @@ -362,19 +371,133 @@ export class PetriNet { this._transitions.forEach(t => cloned.addTransition(t.clone())); this._places.forEach(p => cloned.addPlace(p.clone())); this._arcs.forEach(a => { - const clonedArc = a.clone(); - if (clonedArc.source instanceof Place) { - clonedArc.source = cloned.getPlace(clonedArc.source.id) as Place; - clonedArc.destination = cloned.getTransition(clonedArc.destination.id) as Transition; - } else { - clonedArc.source = cloned.getTransition(clonedArc.source.id) as Transition; - clonedArc.destination = cloned.getPlace(clonedArc.destination.id) as Place; - } - cloned.addArc(clonedArc); + cloned.addArc(this.cloneArc(a, cloned)); }); this._roleRefs.forEach(ref => cloned.addRoleRef(ref.clone())); this._userRefs.forEach(ref => cloned.addUserRef(ref.clone())); - this._tags.forEach((value, key) => cloned.tags.set(key, value)); + cloned.extends = this._extends?.clone(); + cloned.parentModel = this._parentModel; return cloned; } + + public merge(): PetriNet { + if (!this.parentModel) { + return this.clone(); + } + return this.mergeWithParent(this.parentModel.merge()); + } + + protected mergeWithParent(parentNet: PetriNet) { + parentNet._id = this._id; + parentNet._version = this._version; + parentNet._lastChanged = this._lastChanged; + parentNet._title = this._title?.clone(); + parentNet._icon = this._icon; + parentNet._defaultRole = this._defaultRole; + parentNet._anonymousRole = this._anonymousRole; + parentNet._caseName = this._caseName?.clone(); + this.mergeEvents(parentNet); + this.mergeObjects(parentNet); + this._functions.forEach(f => parentNet.addFunction(f.clone())); + this._roleRefs.forEach(ref => parentNet.addRoleRef(ref.clone())); + this._userRefs.forEach(ref => parentNet.addUserRef(ref.clone())); + parentNet.extends = this._extends?.clone(); + parentNet.parentModel = this._parentModel; + return parentNet; + } + + private mergeEvents(parentNet: PetriNet) { + this._processEvents.forEach((event, type) => { + const parentProcessEvent = parentNet.getProcessEvent(type); + if (parentProcessEvent === undefined) { + parentNet.addProcessEvent(event.clone()); + return; + } + event.preActions.forEach(preAction => parentProcessEvent?.preActions.push(preAction.clone())); + event.postActions.forEach(postAction => parentProcessEvent?.postActions.push(postAction.clone())); + if (event.message) { + parentProcessEvent.message = event.message.clone(); + } + event?.properties.forEach(property => parentProcessEvent?.properties.push(property.clone())); + }); + + this._caseEvents.forEach((event, type) => { + const parentCaseEvent = parentNet.getCaseEvent(type); + if (parentCaseEvent === undefined) { + parentNet.addCaseEvent(event.clone()); + return; + } + event.preActions.forEach(preAction => parentCaseEvent?.preActions.push(preAction.clone())); + event.postActions.forEach(postAction => parentCaseEvent?.postActions.push(postAction.clone())); + if (event.message) { + parentCaseEvent.message = event.message.clone(); + } + event?.properties.forEach(property => parentCaseEvent?.properties.push(property.clone())); + }); + } + + private mergeObjects(parentNet: PetriNet): void { + const identifierIndex: string[] = []; + ['_roles', '_data', '_transitions', '_places', '_arcs'].forEach(netAttribute => { + identifierIndex.push(... Array.from((parentNet[netAttribute as keyof PetriNet] as unknown as Map).keys())); + }); + this.mergeObjectsWithIdentifiers(parentNet, identifierIndex); + } + + private mergeObjectsWithIdentifiers(parentNet: PetriNet, identifierIndex: string[]): void { + const i18nIdsIndex = new Set(); + this._i18ns.forEach((i18nTranslations) => { + let translations = parentNet.getI18n(i18nTranslations.locale); + if(translations === undefined) { + translations = new I18nTranslations(i18nTranslations.locale); + } + const i18nIdentifiers = translations.getTranslationIds(); + i18nTranslations.getI18ns().forEach(i18nString => { + if(identifierIndex.includes(i18nString.id as string)) { + throw new Error(`Cannot merge processes [${this._id}] and [${parentNet.id}]: object with identifier '${i18nString.id}' exists in both processes`); + } + if(i18nIdentifiers.includes(i18nString.id as string)) { + throw new Error(`Cannot merge processes [${this._id}] and [${parentNet.id}]: i18nString with identifier '${i18nString.id}' exists in both processes`); + } + translations?.addI18n(i18nString.clone()); + i18nIdsIndex.add(i18nString.id as string); + }); + }); + identifierIndex.push(...Array.from(i18nIdsIndex)); + ['_roles', '_data', '_transitions', '_places'].forEach(netAttribute => { + (this[netAttribute as keyof PetriNet] as unknown as Map>)?.forEach((_value, key) => { + if (identifierIndex.includes(key)) { + throw new Error(`Cannot merge processes [${this._id}] and [${parentNet.id}]: object with identifier '${key}' exists in both processes`); + } + (parentNet[netAttribute as keyof PetriNet] as unknown as Map>)?.set(key, _value.clone()); + }); + }); + this._arcs.forEach((arc, arcId) => { + if (identifierIndex.includes(arcId)) { + throw new Error(`Cannot merge processes [${this._id}] and [${parentNet.id}]: object with identifier '${arcId}' exists in both processes`); + } + parentNet.addArc(this.cloneArc(arc, parentNet)); + }); + } + + private cloneArc(arcToClone: Arc, targetNet: PetriNet): Arc { + const clonedArc = arcToClone.clone(); + const sourceExistsInNet = !!targetNet.getPlace(clonedArc.source.id) || !!targetNet.getTransition(clonedArc.source.id); + const destinationExistsInNet = !!targetNet.getPlace(clonedArc.destination.id) || !!targetNet.getTransition(clonedArc.destination.id); + if (sourceExistsInNet) { + clonedArc.source = this.resolveArcTargetOrDestination(clonedArc.source, targetNet); + } + if (destinationExistsInNet) { + clonedArc.destination = this.resolveArcTargetOrDestination(clonedArc.destination, targetNet); + } + + return clonedArc; + } + + private resolveArcTargetOrDestination(node: NodeElement, net: PetriNet = this): NodeElement { + if (node instanceof Place) { + return net.getPlace(node.id) as Place; + } + return net.getTransition(node.id) as Transition; + } } diff --git a/src/lib/model/petrinet/element.ts b/src/lib/model/petrinet/element.ts index 73719e8..35281c1 100644 --- a/src/lib/model/petrinet/element.ts +++ b/src/lib/model/petrinet/element.ts @@ -2,11 +2,11 @@ import {Property} from '../data-variable/property'; export abstract class Element { private _id: string; - private _properties?: Array; + private _properties: Array; - protected constructor(id: string) { + protected constructor(id: string, properties: Array = []) { this._id = id; - this._properties = [] + this._properties = properties } get id(): string { @@ -17,11 +17,11 @@ export abstract class Element { this._id = value; } - get properties(): Array | undefined{ + get properties(): Array{ return this._properties; } - set properties(value: Array | undefined) { + set properties(value: Array) { this._properties = value; } diff --git a/src/lib/model/petrinet/extension.ts b/src/lib/model/petrinet/extension.ts new file mode 100644 index 0000000..08dc628 --- /dev/null +++ b/src/lib/model/petrinet/extension.ts @@ -0,0 +1,30 @@ +export class Extension { + + private _id: string; + private _version: string; + + constructor(id: string, version: string) { + this._id = id; + this._version = version; + } + + get id(): string { + return this._id; + } + + set id(value: string) { + this._id = value; + } + + get version(): string { + return this._version; + } + + set version(value: string) { + this._version = value; + } + + public clone(): Extension { + return new Extension(this._id, this._version); + } +} diff --git a/src/lib/model/transition/transition.ts b/src/lib/model/transition/transition.ts index 1a94910..90230bc 100644 --- a/src/lib/model/transition/transition.ts +++ b/src/lib/model/transition/transition.ts @@ -19,7 +19,6 @@ export class Transition extends NodeElement { private _flex?: FlexContainer; private _eventSource: TransitionEventSource; private _scope: FunctionScope = FunctionScope.USECASE; - private _tags: Map; constructor(x: number, y: number, id: string) { super(id, x, y, new I18nString('')); @@ -28,7 +27,6 @@ export class Transition extends NodeElement { this._triggers = []; this._roleRefs = []; this._eventSource = new TransitionEventSource(); - this._tags = new Map(); } get icon(): string | undefined { @@ -79,14 +77,6 @@ export class Transition extends NodeElement { this._eventSource = value; } - get tags(): Map { - return this._tags; - } - - set tags(value: Map) { - this._tags = value; - } - get grid(): GridContainer | undefined { return this._grid; } @@ -124,7 +114,6 @@ export class Transition extends NodeElement { cloned.properties = this.properties?.map(p => p.clone()); cloned._scope = this._scope; this.eventSource.getEvents().forEach(event => cloned.eventSource.addEvent(event.clone())); - this.tags.forEach((value, key) => cloned.tags.set(key, value)); return cloned; } } diff --git a/src/lib/simulation/basic-simulation.ts b/src/lib/simulation/basic-simulation.ts index 86a3633..1b0867c 100644 --- a/src/lib/simulation/basic-simulation.ts +++ b/src/lib/simulation/basic-simulation.ts @@ -28,13 +28,13 @@ export class BasicSimulation extends Simulation { this.updateIOArc(arc); this.updateExpressionMapping(arc); } - this.updateDataReferences(); - this.updatePlaceReferences(); + this.updateReferences(); + this.updateReferences(); } updateData(dataVariables: Map): void { this.dataVariables = dataVariables; - this.updateDataReferences(); + this.updateReferences(); } assign(transitionId: string): void { @@ -50,7 +50,7 @@ export class BasicSimulation extends Simulation { this.consumedTokens.set(a.id, consumed); }); this.assignedTasks.add(transitionId); - this.updatePlaceReferences(); + this.updateReferences(); } finish(transitionId: string): void { @@ -60,7 +60,7 @@ export class BasicSimulation extends Simulation { const outputArcs = this.outputArcs.get(transitionId); outputArcs?.forEach(a => (a as TransitionPlaceArc).produce()); this.assignedTasks.delete(transitionId); - this.updatePlaceReferences(); + this.updateReferences(); } cancel(transitionId: string): void { @@ -76,7 +76,7 @@ export class BasicSimulation extends Simulation { } }); this.assignedTasks.delete(transitionId); - this.updatePlaceReferences(); + this.updateReferences(); } isEnabled(transitionId: string): boolean { @@ -125,22 +125,22 @@ export class BasicSimulation extends Simulation { return BasicSimulation.ARC_ORDER.indexOf(a.type) - BasicSimulation.ARC_ORDER.indexOf(b.type); } - updatePlaceReferences(): void { - this.updateReference(expression => `${this.dataVariables.get(expression)}`); - } - - updateDataReferences() { + updateReferences(): void { this.updateReference(expression => `${this.dataVariables.get(expression)}`); } updateReference(evaluate: (expression: string) => string): void { - this.simulationModel.getArcs().filter(arc => arc.multiplicity.dynamic) + this.simulationModel.getArcs().filter(arc => !arc.multiplicity.dynamic) .forEach(arc => { const arcExpression: string | undefined = this.expressionMapping.get(arc.id) if (arcExpression === undefined) { return; } - arc.multiplicity.expression = evaluate(arcExpression); + const multiplicity = evaluate(arcExpression); + if(multiplicity === 'undefined') { + return; + } + arc.multiplicity.expression = multiplicity; }); } diff --git a/src/lib/simulation/simulation.ts b/src/lib/simulation/simulation.ts index cf9b090..fb220fd 100644 --- a/src/lib/simulation/simulation.ts +++ b/src/lib/simulation/simulation.ts @@ -21,8 +21,8 @@ export abstract class Simulation { if (!model) { throw new Error('Model can not be undefined'); } - this._originalModel = model.clone(); - this._simulationModel = model.clone(); + this._originalModel = model; + this._simulationModel = model.merge(); // noinspection DuplicatedCode due to TS2564: Property has no initializer and is not definitely assigned in the constructor. this._inputArcs = new Map>>(); this._outputArcs = new Map>>(); @@ -50,7 +50,7 @@ export abstract class Simulation { public abstract assigned(): Array; public reset(): void { - this._simulationModel = this.originalModel.clone(); + this._simulationModel = this.originalModel.merge(); // noinspection DuplicatedCode due to TS2564: Property has no initializer and is not definitely assigned in the constructor. this._inputArcs = new Map>>(); this._outputArcs = new Map>>(); diff --git a/src/test/inheritance.spec.js b/src/test/inheritance.spec.js new file mode 100644 index 0000000..8eb2cdb --- /dev/null +++ b/src/test/inheritance.spec.js @@ -0,0 +1,118 @@ +const { + ImportService, + ExportService, + ProcessEventType, + CaseEventType, + BasicSimulation +} = require("../../dist/petriflow"); +const fs = require('fs'); +const {beforeEach, describe, expect, test} = require('@jest/globals'); + +const INHERITANCE_TEST_NETS_DIRECTORY = 'src/test/resources/inheritance/'; +const ERROR_INHERITANCE_TEST_NETS_DIRECTORY = INHERITANCE_TEST_NETS_DIRECTORY + 'errors/'; +const CHILD_NET_FILE_PATH = INHERITANCE_TEST_NETS_DIRECTORY + 'child_net.xml'; +const PARENT_NET_FILE_PATH = 'src/test/resources/petriflow_test.xml'; + +describe('Petriflow inheritance tests', () => { + + let importService; + let exportService; + let parentNet; + + beforeEach(() => { + importService = new ImportService(); + exportService = new ExportService(); + parentNet = importService.parseFromXml(fs.readFileSync(PARENT_NET_FILE_PATH).toString()).model; + }); + + test('should fail to import child nets containing object with same identifiers as in parent net', () => { + fs.readdirSync(ERROR_INHERITANCE_TEST_NETS_DIRECTORY).forEach((file) => { + expect(() => { + importService.parseFromXml(fs.readFileSync(ERROR_INHERITANCE_TEST_NETS_DIRECTORY + file).toString(), parentNet); + }).toThrow(); + }); + }); + + test('should set child net metadata correctly', () => { + const childModel = importChildModel().merge(); + expect(childModel.id).toEqual("child_net"); + expect(childModel.version).toEqual("2"); + expect(childModel.extends.id).toEqual("petriflow_test"); + expect(childModel.extends.version).toEqual("1"); + expect(childModel.title.value).toEqual("Child net"); + expect(childModel.icon).toEqual("test_child_icon"); + expect(childModel.defaultRole).toEqual(true); + expect(childModel.anonymousRole).toEqual(true); + expect(childModel.caseName.value).toEqual("child case"); + expect(childModel.parentModel.id).toEqual("petriflow_test"); + }); + + test('should merge parent and child nets', () => { + const childModel = importChildModel(); + const mergedModel = childModel.merge(); + + const childNetIdentifierIndex = createIdentifierIndex(childModel); + const parentNetIdentifierIndex = createIdentifierIndex(parentNet); + const mergedNetIdentifierIndex = createIdentifierIndex(mergedModel); + + const childInheritedIdentifiersLength = childNetIdentifierIndex.filter(identifier => mergedNetIdentifierIndex.includes(identifier)).length; + const parentInheritedIdentifiersLength = parentNetIdentifierIndex.filter(identifier => mergedNetIdentifierIndex.includes(identifier)).length; + + expect(mergedNetIdentifierIndex.length).toEqual(parentNetIdentifierIndex.length + childNetIdentifierIndex.length); + expect(parentInheritedIdentifiersLength).toEqual(parentNetIdentifierIndex.length); + expect(childInheritedIdentifiersLength).toEqual(childNetIdentifierIndex.length); + expect(mergedModel.functions.length).toEqual(3); + expect(mergedModel.getRoleRefs().length).toEqual(7); + expect(mergedModel.getProcessEvent(ProcessEventType.UPLOAD).preActions.length).toEqual(2); + expect(mergedModel.getProcessEvent(ProcessEventType.UPLOAD).postActions.length).toEqual(2); + expect(mergedModel.getCaseEvent(CaseEventType.CREATE).preActions.length).toEqual(2); + expect(mergedModel.getCaseEvent(CaseEventType.CREATE).postActions.length).toEqual(2); + expect(mergedModel.getCaseEvent(CaseEventType.DELETE).preActions.length).toEqual(2); + expect(mergedModel.getCaseEvent(CaseEventType.DELETE).postActions.length).toEqual(2); + }); + + test('arcs with inherited elements should work as normal', () => { + const childModel = importChildModel(); + const sim = new BasicSimulation(childModel); + sim.updateData(new Map([['newVariable_1',5],['p2',3]])) + for (let i = 0; i < 3; i++) { + sim.fire('trans1'); + expect(sim.simulationModel.getPlace('place1').marking).toEqual(1); + expect(sim.simulationModel.getPlace('p10').marking).toEqual(1); + expect(sim.simulationModel.getPlace('p1').marking).toEqual(5); + sim.fire('t1'); + expect(sim.simulationModel.getPlace('place1').marking).toEqual(2); + expect(sim.simulationModel.getPlace('p1').marking).toEqual(2); + expect(sim.simulationModel.getPlace('p3').marking).toEqual(5); + expect(sim.simulationModel.getPlace('place2').marking).toEqual(3); + expect(sim.simulationModel.getPlace('p7').marking).toEqual(5); + sim.fire('t5'); + expect(sim.simulationModel.getPlace('p10').marking).toEqual(0); + sim.fire('trans1'); + expect(sim.simulationModel.getPlace('p1').marking).toEqual(7); + expect(sim.simulationModel.getPlace('place1').marking).toEqual(1); + sim.fire('t1'); + expect(sim.simulationModel.getPlace('p7').marking).toEqual(10); + expect(sim.simulationModel.getPlace('place1').marking).toEqual(2); + expect(sim.simulationModel.getPlace('p1').marking).toEqual(4); + sim.reset(); + } + }) + + function createIdentifierIndex(net) { + const identifierIndex = []; + ['_roles', '_data', '_transitions', '_places', '_arcs'].forEach(netAttribute => { + identifierIndex.push(...Array.from((net[netAttribute]).keys())); + }); + const i18Set = new Set(); + net.getI18ns().forEach(translations => { + i18Set.add(...Array.from(translations._i18ns.keys)); + }); + identifierIndex.push(...i18Set); + return identifierIndex; + } + + function importChildModel() { + return importService.parseFromXml(fs.readFileSync(CHILD_NET_FILE_PATH).toString(), parentNet).model; + } +}); diff --git a/src/test/petriflow.spec.js b/src/test/petriflow.spec.js index 3042198..d37ce93 100644 --- a/src/test/petriflow.spec.js +++ b/src/test/petriflow.spec.js @@ -54,9 +54,6 @@ const MODEL_ICON = 'home'; const MODEL_DEFAULT_CASE_NAME_VALUE = '${new Date() as String}'; const MODEL_DEFAULT_ROLE = true; const MODEL_ANONYMOUS_ROLE = true; -const MODEL_TAGS_LENGTH = 2; -const MODEL_TAGS_FIRST = 'First'; -const MODEL_TAGS_SECOND = 'Second'; const PROCESS_EVENTS_LENGTH = 1; const PROCESS_EVENTS_UPLOAD_ID = 'process_upload'; const PROCESS_EVENTS_UPLOAD_PRE_LENGTH = 1; @@ -167,9 +164,6 @@ describe('Petriflow integration tests', () => { expect(model.caseName).not.toBeUndefined(); expect(model.caseName.value).toEqual(MODEL_DEFAULT_CASE_NAME_VALUE); expect(model.caseName.dynamic).toEqual(true); - expect(model.tags.size).toEqual(MODEL_TAGS_LENGTH); - expect(model.tags.get(MODEL_TAGS_FIRST)).toEqual(MODEL_TAGS_FIRST); - expect(model.tags.get(MODEL_TAGS_SECOND)).toEqual(MODEL_TAGS_SECOND); log('Model metadata OK'); expect(model.getProcessEvents().length).toEqual(PROCESS_EVENTS_LENGTH); @@ -491,9 +485,6 @@ describe('Petriflow integration tests', () => { expect(transitionT1.icon).toEqual(MODEL_ICON); expect(transitionT1.assignPolicy).toEqual(AssignPolicy.AUTO); expect(transitionT1.finishPolicy).toEqual(FinishPolicy.AUTO_NO_DATA); - expect(transitionT1.tags.size).toEqual(MODEL_TAGS_LENGTH); - expect(transitionT1.tags.get(MODEL_TAGS_FIRST)).toEqual(MODEL_TAGS_FIRST); - expect(transitionT1.tags.get(MODEL_TAGS_SECOND)).toEqual(MODEL_TAGS_SECOND); const t1AssignEvent = transitionT1.eventSource.getEvent(TransitionEventType.ASSIGN); expect(t1AssignEvent.id).toEqual('assign'); expect(t1AssignEvent.title.value).toEqual('t1_assign_title_value'); @@ -932,11 +923,11 @@ describe('Petriflow integration tests', () => { expect(model.getArc('a1').scope).toEqual(FunctionScope.NAMESPACE); expect(model.getArc('a2').scope).toEqual(FunctionScope.USECASE); assertArc(model.getArc('a1'), 'a1', ArcType.REGULAR_PT, 'p1', 't1', { - dynamic: true, + dynamic: false, expression: 'p2' }); assertArc(model.getArc('a2'), 'a2', ArcType.REGULAR_TP, 't1', 'p3', { - dynamic: true, + dynamic: false, expression: 'newVariable_1' }); assertArc(model.getArc('a3'), 'a3', ArcType.RESET, 'p4', 't2'); @@ -1012,7 +1003,7 @@ describe('Petriflow integration tests', () => { model.addPlace(p1); model.addTransition(t1); model.addArc(a1); - const xml = exportService.exportXml(model); + exportService.exportXml(model); }); test('event-source', () => { diff --git a/src/test/resources/inheritance/child_net.xml b/src/test/resources/inheritance/child_net.xml new file mode 100644 index 0000000..6e230a6 --- /dev/null +++ b/src/test/resources/inheritance/child_net.xml @@ -0,0 +1,202 @@ + + child_net + 2 + + petriflow_test + 1 + + Child net + test_child_icon + true + true + child case + + child_test_role + + true + false + + + + + newRole_4 + + true + false + + + + + child_process_upload + + + + + + + + + + + process_upload_message_value + + + + + child_case_create + + + + + + + + + + + case_create_message_value + + create_case_event_property_value + + + + child_case_delete + + + + + + + + + + + + + + child_test_role + Child test role + + + + return x + y + } + ]]> + + + child_variable + title + + + child_immediate_variable + newVariable_2_title_value + + + trans1 + 304 + 144 + +<!-- reference of parent role on child transiton --> + <roleRef> + <id>newRole_4</id> + <logic> + <assign>true</assign> + <cancel>false</cancel> + </logic> + </roleRef> + <flex> + <id>parent_dataRef_test</id> + <item> + <dataRef> + <id>newVariable_1</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </item> + </flex> + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>2</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> +<!-- normal arc--> + <arc> + <id>arc1</id> + <type>regular_pt</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> +<!-- inherited destination--> + <arc> + <id>arc2</id> + <type>regular_tp</type> + <sourceId>trans1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +<!-- inherited source--> + <arc> + <id>arc3</id> + <type>regular_tp</type> + <sourceId>t1</sourceId> + <destinationId>place1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <!-- inherited both source and destination--> + <arc> + <id>arc4</id> + <type>regular_pt</type> + <sourceId>p10</sourceId> + <destinationId>t5</destinationId> + <multiplicity>1</multiplicity> + </arc> +<!-- variable arc with data reference from parent process--> + <arc> + <id>arc5</id> + <type>regular_tp</type> + <sourceId>t1</sourceId> + <destinationId>p7</destinationId> + <multiplicity>newVariable_1</multiplicity> + </arc> +<!-- variable arc with place reference from parent process--> + <arc> + <id>arc6</id> + <type>regular_tp</type> + <sourceId>t1</sourceId> + <destinationId>place2</destinationId> + <multiplicity>p2</multiplicity> + </arc> + <arc> + <id>arc7</id> + <type>regular_tp</type> + <sourceId>trans1</sourceId> + <destinationId>p1</destinationId> + <multiplicity>newVariable_1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_arc_id.xml b/src/test/resources/inheritance/errors/duplicate_arc_id.xml new file mode 100644 index 0000000..a4497c9 --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_arc_id.xml @@ -0,0 +1,46 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title>Child net + device_hub + true + true + + t1 + 304 + 144 + + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_data_id.xml b/src/test/resources/inheritance/errors/duplicate_data_id.xml new file mode 100644 index 0000000..afe0a67 --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_data_id.xml @@ -0,0 +1,50 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title>Child net + device_hub + true + true + + newVariable_1 + title + + + trans1 + 304 + 144 + + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>arc1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>trans1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_i18n_id.xml b/src/test/resources/inheritance/errors/duplicate_i18n_id.xml new file mode 100644 index 0000000..ab26f6e --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_i18n_id.xml @@ -0,0 +1,49 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title id="title">Child net + device_hub + true + true + + UK_title + + + t1 + 304 + 144 + + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>arc1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_place_id.xml b/src/test/resources/inheritance/errors/duplicate_place_id.xml new file mode 100644 index 0000000..70a1e4a --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_place_id.xml @@ -0,0 +1,46 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title>Child net + device_hub + true + true + + trans1 + 304 + 144 + + </transition> + <place> + <id>p1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>arc1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>trans1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_role_id.xml b/src/test/resources/inheritance/errors/duplicate_role_id.xml new file mode 100644 index 0000000..f453e21 --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_role_id.xml @@ -0,0 +1,50 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title>Child net + device_hub + true + true + + newRole_1 + title + + + t1 + 304 + 144 + + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>arc1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/inheritance/errors/duplicate_transition_id.xml b/src/test/resources/inheritance/errors/duplicate_transition_id.xml new file mode 100644 index 0000000..ed10674 --- /dev/null +++ b/src/test/resources/inheritance/errors/duplicate_transition_id.xml @@ -0,0 +1,46 @@ +<process xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://netgrif.github.io/petriflow/petriflow.schema.xsd"> + <id>child_net</id> + <version>1.0.0</version> + <extends> + <id>petriflow_test</id> + <version>1.0.0</version> + </extends> + <title>Child net + device_hub + true + true + + t1 + 304 + 144 + + </transition> + <place> + <id>place1</id> + <x>176</x> + <y>144</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>place2</id> + <x>432</x> + <y>144</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>arc1</id> + <type>regular</type> + <sourceId>place1</sourceId> + <destinationId>trans1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>arc2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p10</destinationId> + <multiplicity>1</multiplicity> + </arc> +</process> diff --git a/src/test/resources/petriflow_test.xml b/src/test/resources/petriflow_test.xml index 46136b3..6566c2b 100644 --- a/src/test/resources/petriflow_test.xml +++ b/src/test/resources/petriflow_test.xml @@ -6,10 +6,6 @@ <icon>home</icon> <defaultRole>true</defaultRole> <anonymousRole>true</anonymousRole> - <tags> - <tag key="First">First</tag> - <tag key="Second">Second</tag> - </tags> <caseName dynamic="true">${new Date() as String}</caseName> <version>1</version> <!-- PROCESS ROLE REFS --> @@ -159,11 +155,6 @@ </actions> </event> </processEvents> - <!-- TRANSACTIONS --> - <transaction> - <id>trans1</id> - <title>Transaction 1 - newRole_1 @@ -543,10 +534,6 @@ 460 180 Task escape:&<> - - First - Second - home auto auto_no_data @@ -1291,17 +1278,17 @@ a1 - regular + regular_pt p1 t1 - p2 + p2 a2 - regular + regular_tp t1 p3 - newVariable_1 + newVariable_1 a3 @@ -1326,70 +1313,70 @@ a6 - regular + regular_tp t2 p7 20 a7 - regular + regular_tp t3 p8 1 a8 - regular + regular_tp t4 p9 1 a9 - regular + regular_pt p1 t2 1 a10 - regular + regular_pt p4 t3 1 a11 - regular + regular_pt p5 t4 1 a12 - regular + regular_pt p6 t3 1 a13 - regular + regular_pt p5 t2 1 a14 - regular + regular_tp t2 p8 1 a15 - regular + regular_tp t2 p3 1 @@ -1400,7 +1387,8 @@ variable t2 p3 - newVariable_1 + newVariable_1 + 1 10 10 @@ -1424,13 +1412,13 @@ invalid_source - regular + regular_tp invalid_source_id invalid_destination_id invalid_destination - regular + regular_tp t2 invalid_destination_id @@ -1442,7 +1430,7 @@ invalid_breakpont - regular + regular_tp t2 p3 1 diff --git a/src/test/resources/simulation_arc_order.xml b/src/test/resources/simulation_arc_order.xml index 7db059a..ff9df07 100644 --- a/src/test/resources/simulation_arc_order.xml +++ b/src/test/resources/simulation_arc_order.xml @@ -38,7 +38,7 @@ a2 - regular + regular_pt p1 t1 1 diff --git a/src/test/resources/simulation_ref_data.xml b/src/test/resources/simulation_ref_data.xml index bf6a74b..4c7e8ed 100644 --- a/src/test/resources/simulation_ref_data.xml +++ b/src/test/resources/simulation_ref_data.xml @@ -36,16 +36,16 @@ inputArc - regular + regular_tp t1 p1 - input + input outputArc - regular + regular_pt p1 t2 - output + output diff --git a/src/test/resources/simulation_sequence.xml b/src/test/resources/simulation_sequence.xml index 38f7e66..69a7d17 100644 --- a/src/test/resources/simulation_sequence.xml +++ b/src/test/resources/simulation_sequence.xml @@ -85,28 +85,28 @@ a1 - regular + regular_pt p1 t1 1 a2 - regular + regular_tp t1 p2 1 a3 - regular + regular_pt p2 t2 1 a5 - regular + regular_tp t2 p3 1 @@ -120,7 +120,7 @@ a9 - regular + regular_tp t1 p4 1 @@ -134,7 +134,7 @@ a12 - regular + regular_tp t4 p5 1 @@ -148,14 +148,14 @@ a16 - regular + regular_tp t4 p6 1 a17 - regular + regular_pt p6 t6 1 diff --git a/src/test/resources/simulation_task.xml b/src/test/resources/simulation_task.xml index f72ec62..b305ca2 100644 --- a/src/test/resources/simulation_task.xml +++ b/src/test/resources/simulation_task.xml @@ -50,14 +50,14 @@ a1 - regular + regular_pt p1 t1 1 a2 - regular + regular_pt p1 t2 1 @@ -71,7 +71,7 @@ a4 - regular + regular_tp t4 p1 1 diff --git a/src/test/resources/simulation_transition.xml b/src/test/resources/simulation_transition.xml index d104ccb..220d02e 100644 --- a/src/test/resources/simulation_transition.xml +++ b/src/test/resources/simulation_transition.xml @@ -203,35 +203,35 @@ a1 - regular + regular_pt p1 t1 1 a3 - regular + regular_pt p2 t2 5 a4 - regular + regular_pt p3 t3 5 a5 - regular + regular_pt p4 t4 1 a6 - regular + regular_pt p5 t5 2 diff --git a/src/test/resources/simulation_transition_multiple.xml b/src/test/resources/simulation_transition_multiple.xml index d99d270..758ad30 100644 --- a/src/test/resources/simulation_transition_multiple.xml +++ b/src/test/resources/simulation_transition_multiple.xml @@ -88,14 +88,14 @@ a7 - regular + regular_pt p1 t3 1 a8 - regular + regular_pt p3 t3 1